欢迎访问我的个人博客
https://vincillau.github.io/
文章目录
C++多线程编程(二):使用互斥锁
上回书说到,我们可以用C++11中的thread类
来创建和管理线程。在多线程编程中,我们常常遇到对线程间共享数据访问的各种线程安全问题。在这篇博文中我将向大家介绍如何利用C++11提供的<mutex>
头文件提供的相关工具来保护线程间共享数据。
竞争条件
首先来看一段代码:
#include <iostream>
#include <thread>
using namespace std;
int n = 0;
void func(void)
{
for (int i = 0; i < 10000; i++)
{
int x = n;
n++;
if (x + 1 == n)
{
n--;
}
}
}
int main(void)
{
thread t1(func);
thread t2(func);
thread t3(func);
t1.join();
t2.join();
t3.join();
cout << n << endl;
return 0;
}
读者不妨先猜测一下输出结果。
一种可能的猜测:
在函数
func
中,我们先把n赋值给x,然后让n递增1。按理说,表达式x + 1 == n
的结果应该为true
,那么n又会递减1。也就是说,执行一次循环,n的值应该不会改变。所以程序最终的输出结果应该是0。
几句就
我们运行一下这个程序看一下结果:
548 // 第一次运行结果
96 // 第二次运行结果
162 // 第三次运行结果
肿么肥四?为什么每次运行结果都不一致?要想搞清楚这样的结果的原因,首先要从线程调度
说起:
操作系统在调度线程时采用的是抢占式调度
的方式,也就是说,每一个线程在执行一段时间后会被操作系统中断,然后调用另一个线程。操作系统对线程的调度几乎是随机
的。在上面的例子中,当我们的线程在执行n++
后,操作系统有可能会终端当前线程去执行另一个线程。而另一个线程如果恰好也执行了n++
,x + 1 == n
就不为true
了,所以结果也就不是预期的0
了。这就是多线程编程中的竞争条件
(也叫竞争冒险
、竞态条件
)
那么如果我们想让结果为0
需要怎么做呢?这就需要C++中的mutex
来保护循环中的代码,让程序中的三个线程在同一时间只有一个线程执行循环中的代码,来避免竞争条件
。
mutex类
用途
mutex
常被称作互斥锁
,互斥量
,位于<mutex>
头文件中。mutex的用途就是对可能出现竞争条件的代码段(临界区)“加锁”。线程要进入临界区,首先要获取锁,如果成功获取锁,线程可以进入临界区执行代码。如果线程想要获取的锁已经被其他线程占用,则线程会阻塞
,直至其他线程释放这个锁。
创建mutex对象
mutex类只有一个默认构造函数
,它会创建一个没有被加锁的mutex对象。mutex类没有拷贝和移动构造函数
,所以mutex不能被拷贝
或者移动
。
锁定mutex
线程调用mutex对象的lock
成员函数会尝试获取这个锁。如果这个mutex对象没有被其他线程占有,当前线程就会获取这个锁。如果这个mutex已经被其他线程占用,调用线程会被阻塞直到其他线程释放这个锁。
释放mutex
当线程执行完临界区的代码后,应当释放锁以便让其他线程能够获取这个锁。释放mutex通过调用mutex对象的unlock
成员函数。
使用mutex的栗子
下面的代码利用mutex改写了上一节的例子:
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
int