「C++ 多线程」std::recursive_mutex 基本用法

文章大图来源:pixiv_id=123093870

1 std::recursive_mutex 基本概念

std::recursive_mutex 是 C++ 11 引入的一种 独占递归 的互斥量类型,用于控制多个线程对共享资源的并发互斥访问。

std::recursive_mutex 包含了 std::mutex 的基本功能,包括 lock()unlock() 函数进行加锁和解锁、try_lock() 以非阻塞的方式尝试获得锁。

std::mutex 不同的是,std::recursive_mutex 允许线程拥有当前互斥锁的期限内,可以重复进行尝试加锁(调用 lock()try_lock() 函数)。需要注意的是,线程 需要进行对应数量次数的解锁(调用 unlock())才可以完全释放当前互斥量的锁。

std::recursive_mutex 提供了可以重复加锁的机制,通常也可以称之为 递归锁

由于提供了重复(递归)加锁的特性,std::recursive_mutex 的效率是比较低的。当使用了递归锁时,我们通常要考虑代码是否还有优化空间,尽量避免使用递归锁。递归次数通常有限制(爆栈),但在 C++ 中一般较少出现此情况。

2 std::mutex 重复加锁出错

我们如果采用普通的 std::mutex,进行重复尝试加锁,一般会出现死锁的状况,出现未定义行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>

std::mutex mtx;
int count;

void dfs(int k){
if(k == 0) return;
mtx.lock(); // 加锁
count ++ ;
std::cout << "thread id = " << std::this_thread::get_id();
std::cout << ", count = " << count << "\n";
dfs(k - 1); // 递归
mtx.unlock(); // 解锁
}

int main(){
std::vector<std::thread> threads(3);
for(auto& t : threads) t = std::thread(dfs, 5);
for(auto& t : threads) t.join();

std::cout << "final count = " << count << "\n";

return 0;
}

在上面的代码中,写了一个递归函数 dfs(),实现了嵌套的重复尝试加锁。创建了 3 个线程,每个线程递归 5 层,递归的每一层需要先进行加锁,然后对共享数据 count 进行 +1,并输出数据的值。递归的每一层结束进行解锁。直到 k0 时退出递归。

在 Windows 系统中运行可能的结果如下:

在 Windows 系统中,系统可能会通过采取一些优化策略、线程调度等,看似正确运行起来了,但实际上代码还是错误的。

在 Linux 系统中运行可能的结果如下:

在 Linux 系统中,显然程序运行不动了,表明出现了死锁。

3 std::recursive_mutex 实现重复加锁

我们可以将 std::mutex 重复加锁出错 代码中的 std::mutex 全部换成 std::recursive_mutex 即可。修改后就可以使得线程对一个互斥量进行重复加锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>

std::mutex r_mtx;
int count;

void dfs(int k){
if(k == 0) return;
r_mtx.lock(); // 加锁
count ++ ;
std::cout << "thread id = " << std::this_thread::get_id();
std::cout << ", count = " << count << "\n";
dfs(k - 1); // 递归
r_mtx.unlock(); // 解锁
}

int main(){
std::vector<std::thread> threads(3);
for(auto& t : threads) t = std::thread(dfs, 5);
for(auto& t : threads) t.join();

std::cout << "final count = " << count << "\n";

return 0;
}

可能的运行结果如下:

参考

  1. cppreference.com recursive_mutex

「C++ 多线程」std::recursive_mutex 基本用法
https://marisamagic.github.io/2024/12/28/20241228_/
作者
MarisaMagic
发布于
2024年12月28日
许可协议