举个例子:
刚参加工作的你,只能租房住,嫌房租贵就和别人合租了,两个人住一起只有一个洗手间,每天早上起床的时候,如果你室友在洗手间,你就只能等着,如果你强行进去,那画面就不可描述了。同样的问题,如果多个线程共享一个数据,并且对数据有读有写,那就需要注意共享数据的保护了。
使用互斥量保护共享数据:
当访问共享数据前,使用互斥量奖相关数据锁住,当访问结束后,再将数据解锁。互斥量是C++中一种最通用的数据保护机制。
C++中使用互斥量:
std::mutex : C++通过实例化std::mutex创建互斥量,通过成员函数lock进行上锁,unlock进行解锁。
#include <mutex> void func() { std::mutex my_mutex; my_mutex.lock(); if (do_something() != success) { // ……………… my_mutex.unlock() //异常分支一定要记得解锁 } my_mutex.unlock(); }
std::lock_guard : 使用mutex必须在锁范围的每个分支都记得解锁,如果不小心忘记将会产生错误。所以可以用lock_guard来替换,它是一个类模块,在构造时加锁,析构时解锁。
#include <mutex> void func() { std::mutex my_mutex; std::lock_guard<std::mutex> guard(my_mutex); if (do_something() != success) { // ……………… } }
规则:切勿将受保护数据的指针或引用传递到互斥锁作用域之外,无论是函数返回值,还是存储在外部可见内存,亦或是以参数的形式传递到用户提供的函数中去。
死锁:
试想有一个玩具,这个玩具由两部分组成,必须拿到这两个部分,才能够玩。例如,一个玩具鼓,需要一个鼓锤和一个鼓才能玩。现在有两个小孩,他们都很喜欢玩这个玩具。当其中一个孩子拿到了鼓和鼓锤时,那就可以尽情的玩耍了。当另一孩子想要玩,他就得等待另一孩子玩完才行。再试想,鼓和鼓锤被放在不同的玩具箱里,并且两个孩子在同一时间里都想要去敲鼓。之后,他们就去玩具箱里面找这个鼓。其中一个找到了鼓,并且另外一个找到了鼓锤。现在问题就来了,除非其中一个孩子决定让另一个先玩,他可以把自己的那部分给另外一个孩子;但当他们都紧握着自己所有的部分而不给对方,那么这个鼓谁都没法玩。
现在没有孩子去抢玩具,但线程有对锁的竞争:一对线程需要对他们所有的互斥量做一些操作,其中每个线程都有一个互斥量,且等待另一个解锁。这样没有线程能工作,因为他们都在等待对方释放互斥量。这种情况就是死锁,它的最大问题就是由两个或两个以上的互斥量来锁定一个操作。