要点
-
锁+双重判断的技法
-
竟态条件:多线程程序执行的结果一致,不会随着CPU对线程不同的调用顺序
线程间安全实例——3个窗口同时卖票
线程不安全的代码如下
int ticketCount = 100; // 100张车票
// 模拟10个窗口同时卖票
void sellTicket(int index)
{
while (ticketCount > 0)
{
//cout << "窗口:" << index << "卖出第:" << ticketCount << "张票" << endl;
cout << ticketCount << endl; // 打印当前剩余票数
ticketCount--;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
int main()
{
list<std::thread> tlist;
for (int i = 1; i <= 3; ++i)
{
tlist.push_back(std::thread(sellTicket, i));
}
for (std::thread& t : tlist)
{
t.join();
}
cout << "所有窗口卖票结束!" << endl;
return 0;
}
输出的部分结果里有很多重复的数字,相当于同一张票被卖出多次,原因在于
ticketCount–; 是线程不安全的,理由如下
某一时刻ticketCount = 99,thread1此时调用ticketCount–,执行到sub eax时执行线程切换到另一个线程thread2中,此时ticketCount仍为99,执行完3条汇编后ticketCount = 98,此时再切换回thread1,继续执行完后面汇编,也使ticketCount = 98,这里就导致同时输出两次98;
解决方法
保证某一线程ticketCount–没做完,其他线程不允许做ticketCount–操作,可以使用加mutex互斥锁的方法:
void sellTicket(int index)
{
mtx.lock();
while (ticketCount > 0)
{
//cout << "窗口:" << index << "卖出第:" << ticketCount << "张票" << endl;
cout << ticketCount << endl; // 打印当前剩余票数
ticketCount--;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
mtx.unlock();
}
但是这样的加锁问题在于锁粒度太大,可以进一步缩小,采用锁+双重判断的方法:
void sellTicket(int index)
{
while (ticketCount > 0)
{
mtx.lock();
if (ticketCount > 0) // !!!
{
cout << "窗口:" << index << "卖出第:" << ticketCount << "张票" << endl;
//cout << ticketCount << endl; // 打印当前剩余票数
ticketCount--;
}
mtx.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
这里if (ticketCount
> 0) 判断必须加,如果不加,当ticketCount = 1时,切换到其他线程卖完后ticketCount = 0 ,切换回原线程继续卖票就变成可以卖第0张票,不合法
lock_guard和unique_lock
lock_guard
lock_guard
不能用在函数参数传递或返回过程中,只能用在简单的临界区代码段互斥操作;
类似于scoped_str
;
lock_guard
是对std::mutex
的封装,拷贝构造和赋值函数被delete
,它是RAII
技术的实践,创建对象时就加锁,出作用域析构调用解锁,用lock_guard
替换上面案例的mutex
:
void sellTicket(int index)
{
while (ticketCount > 0)
{
//mtx.lock();
{
lock_guard<std::mutex> lock(mtx); //
if (ticketCount > 0)
{
cout << "窗口:" << index << "卖出第:" << ticketCount << "张票" << endl;
ticketCount--;
}
}
//mtx.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
unique_lock
unique_lock
不仅可用在函数参数传递或返回过程中,还能用在函数调用中,如 和条件变量函数一起使用:
unique_lock<std::mutex> lck(mtx);
cv.wait(lck); // => #1.使线程进入等待状态 #2.lck.unlock可以把mtx给释放掉
unique_lock
也是对mutex
的封装,它也可以像lock_guard
一样使用,同时它支持手动调用lock
和unlock
,会帮助检查是否忘记调用unlock
,使用例子如下:
void sellTicket(int index)
{
while (ticketCount > 0)
{
{
unique_lock<std::mutex> lck(mtx);
lck.lock();
if (ticketCount > 0)
{
cout << "窗口:" << index << "卖出第:" << ticketCount << "张票" << endl;
ticketCount--;
}
lck.unlock();
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}