假设我们有100张门票,有三个售卖窗口,我们希望以多线程的方式将这些票卖出去,这样效率会更高一些。
首先我们需要有一个全局的门票变量。
int tickts = 100;
卖票的函数,由于每个窗口卖出一张票都需要花费一些时间,假设是100ms,那么可以通过线程睡眠函数实现这一时间的花费。
void selltickts(int number)
{while (tickts > 0){std::cout << tickts<<std::endl;tickts--;std::this_thread::sleep_for(std::chrono::milliseconds(100));//卖一张票花费100ms}
}
main函数,创建窗口。
int main()
{std::vector < std::thread >v;for (int i = 1; i <= 3; i++){v.push_back(std::thread(selltickts, i));}for (int i = 0; i < 3; i++){v[i].join();}return 0;
}
运行结果如下:
这个运行结果显然是出问题的,基本上每张票都重复售卖了3次。这主要是因为我们使用了tickts--;这一操作,--和++这两个运算符,本身就是线程不安全的,它们都是由三条汇编机器指令组成的。实际上,一个线程在执行一个函数时,不一定会将一个函数的一行代码都完整执行掉,比如tickts--就没有完整执行,但是它一定会将一行代码中的一行汇编指令完整执行掉,执行完了这条汇编指令之后,这个线程就有可能将自己的时间片归还给操作系统,自身陷入阻塞状态,等待下次获取时间片后进行执行剩下的汇编指令。这样就会导致多个线程同时操作一张门票,三个窗口同时在卖一张门票,这是不允许的,我们需要将这三个线程设置为互斥状态,这样它们就不能同时操作一个资源了。这时候就需要用到互斥锁了。
我们添加一个全局的互斥锁。
std::mutex mtx;
将售票函数用互斥锁进行锁定。
void selltickts(int number)
{mtx.lock();while (tickts > 0){std::cout << tickts<<std::endl;tickts--;std::this_thread::sleep_for(std::chrono::milliseconds(100));//卖一张票花费100ms}mtx.unlock();
}
运行程序。
这次结果虽然正确了,但是由于我们在while循环外面,进行互斥锁定,当有一个线程拿到这个锁时,就进入到了while循环中,其他线程拿不到这个锁,就进入不到while循环里面了,所以这个程序从始至终都只是一个单线程程序,与我们的预期不符合。
我们将锁定行为放入while循环中。
void selltickts(int number)
{while (tickts > 0){mtx.lock();std::cout << tickts<<std::endl;tickts--;mtx.unlock();std::this_thread::sleep_for(std::chrono::milliseconds(100));//卖一张票花费100ms}
}
运行程序。
显然程序还是不对,因为最后卖出了第0张票,这种情况可能是这样的,当票数还有1张时,假如一号窗口进入了while循环拿到了锁,还没有进行--操作,二号窗口也进入了while循环,因为这时候还有1张票,所以二号窗口可以进入while循环,但是拿不到锁,所以while循环里面的代码执行不了,就只能在while循环里面等待锁的到来,于是1号窗口卖完这最后一张票后,将票数减为0,释放了锁,二号直接拿到这个锁,就可以对0继续--了,于是就得到了-1张票。
解决方式,就是双重条件判断,在while循环里面再次判断一次票数。
void selltickts(int number)
{while (tickts > 0){mtx.lock();if(tickts>0){std::cout << tickts << std::endl;tickts--;}mtx.unlock();std::this_thread::sleep_for(std::chrono::milliseconds(100));//卖一张票花费100ms}
}
运行结果。
这次总算达到了,我们想要的结果。
但是这段代码还是有一些隐患,假如我while的if语句里面有一个return语句的话,整个程序就变成一个死锁程序了,一个线程拿到锁后并且触发了这个return,就不会释放这个锁了。要解决这个问题,我们需要用到智能锁。
void selltickts(int number)
{while (tickts > 0){{std::lock_guard<std::mutex>loc(mtx);if (tickts > 0){std::cout <<number<<"号窗口卖出了第"<< tickts << "号票"<<std::endl;tickts--;}}std::this_thread::sleep_for(std::chrono::milliseconds(100));//卖一张票花费100ms}
}
由于lock_guard析构是离开一个作用域才会触发的,我们可以给它一个作用域(大括号),代表卖完了这张牌,这把锁就自动释放掉了,这样就可以有效避免死锁问题了。
运行结果。
达到了我们想要的效果,三个窗口都在卖票,票数没有异常。