很久之前,我就相关内容写了一篇博客。今日,重读相关内容,还是觉得自己要就相关内容,再写一篇。不为别的,权当加深印象之用吧。C++11 提供了标准化的多线程支持,其中包含四种不同类型的互斥锁,用于保护共享资源并确保线程安全。这次,我将详细介绍 C++11 提供的四种互斥锁及其使用场景,并附上相关代码示例。
C++11 提供的四种互斥锁#
mutex
:标准互斥锁,用于控制对共享资源的独占访问。
timed_mutex
:带超时机制的互斥锁,支持在指定时间内尝试加锁。
recursive_mutex
:递归互斥锁,允许同一线程多次锁定资源,防止递归函数加锁时发生死锁。
recursive_timed_mutex
:带超时机制的递归互斥锁,结合了递归锁和超时机制。
1
2
|
#include <mutex> // 包含互斥锁类的头文件
#include <thread> // 包含线程类的头文件
|
一、mutex
类 —— 标准互斥锁#
1.1 lock()
—— 加锁操作#
lock()
函数用于申请互斥锁,线程会尝试获取锁的所有权。如果锁已经被另一个线程持有,则当前线程会阻塞,直到锁可用。
1.2 unlock()
—— 解锁操作#
unlock()
函数用于释放锁的所有权,其他等待锁的线程可以继续获取该锁。
1.3 try_lock()
—— 尝试加锁#
try_lock()
函数不阻塞,立即尝试获取锁。如果锁是空闲的,则成功加锁并返回 true
;如果锁已经被其他线程持有,则返回 false
。
示例代码:使用 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
28
29
30
31
32
33
|
#include <iostream>
#include <thread>
#include <mutex> // 互斥锁类的头文件
using namespace std;
mutex mtx; // 创建互斥锁,保护共享资源 cout 对象
// 普通函数,模拟多线程环境下的资源访问
void func(int bh, const string& str) {
for (int ii = 1; ii <= 10; ii++) {
mtx.lock(); // 加锁,保护共享资源
cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << endl;
mtx.unlock(); // 解锁,释放共享资源
this_thread::sleep_for(chrono::seconds(1)); // 休眠 1 秒
}
}
int main() {
// 创建多个线程,每个线程访问共享资源 cout
thread t1(func, 1, "我是一只傻傻鸟。");
thread t2(func, 2, "我是一只傻傻鸟。");
thread t3(func, 3, "我是一只傻傻鸟。");
thread t4(func, 4, "我是一只傻傻鸟。");
thread t5(func, 5, "我是一只傻傻鸟。");
// 等待所有线程完成
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
}
|
代码解析#
在这个例子中,多个线程同时访问共享的 cout
对象输出信息。为了防止多个线程同时访问共享资源而造成数据竞争,使用了 mtx.lock()
加锁和 mtx.unlock()
解锁来保护 cout
。
二、timed_mutex
类 —— 带超时机制的互斥锁#
timed_mutex
提供了 try_lock_for()
和 try_lock_until()
两个函数,允许线程在指定的时间段内尝试获取锁。如果超过指定时间锁依然被其他线程持有,则加锁失败。
try_lock_for(时间长度)
:尝试在指定时间长度内获取锁。
try_lock_until(时间点)
:尝试在指定时间点之前获取锁。
示例代码:使用 timed_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
28
|
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;
timed_mutex tmtx; // 创建带超时机制的互斥锁
void func(int bh, const string& str) {
for (int ii = 1; ii <= 5; ii++) {
if (tmtx.try_lock_for(chrono::milliseconds(100))) { // 尝试加锁,最多等待 100 毫秒
cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << endl;
tmtx.unlock(); // 解锁
} else {
cout << "第" << ii << "次尝试加锁失败,线程" << bh << "放弃了操作。" << endl;
}
this_thread::sleep_for(chrono::milliseconds(150)); // 休眠 150 毫秒
}
}
int main() {
thread t1(func, 1, "我是一只傻傻鸟。");
thread t2(func, 2, "我有一只小小鸟。");
t1.join();
t2.join();
}
|
代码解析#
在此示例中,每个线程尝试使用 timed_mutex
加锁。如果在 100 毫秒内加锁成功,线程会输出信息;如果锁定失败,线程会跳过此次操作,继续执行下一轮。
三、recursive_mutex
类 —— 递归互斥锁#
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>
using namespace std;
class AA {
recursive_mutex rmtx; // 创建递归互斥锁
public:
void func1() {
rmtx.lock();
cout << "调用了 func1()\n";
rmtx.unlock();
}
void func2() {
rmtx.lock();
cout << "调用了 func2()\n";
func1(); // 在 func2 内部调用 func1
rmtx.unlock();
}
};
int main() {
AA aa;
aa.func2(); // 调用 func2,它会递归调用 func1
}
|
代码解析#
递归互斥锁允许同一线程在不解锁的情况下多次加锁,这对递归调用非常有用。在这个例子中,func2()
调用了 func1()
,但两者都使用了相同的递归互斥锁,因此不会导致死锁。
四、lock_guard
类 —— RAII 风格的加锁管理#
lock_guard
是一个模板类,它采用 RAII(资源获取即初始化)思想来简化互斥锁的使用。当 lock_guard
对象被创建时,它自动加锁;当 lock_guard
对象超出作用域时,它会自动解锁,这避免了手动加锁/解锁带来的潜在错误。
示例代码:使用 lock_guard
简化加锁#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx; // 创建互斥锁
void func(int bh, const string& str) {
for (int ii = 1; ii <= 5; ii++) {
lock_guard<mutex> lg(mtx); // 自动加锁,在作用域结束时自动解锁
cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << endl;
this_thread::sleep_for(chrono::seconds(1)); // 休眠 1 秒
}
}
int main() {
thread t1(func, 1, "我是一只傻傻鸟。");
thread t2(func, 2, "我有一只小小鸟。");
t1.join();
t2.join();
}
|
代码解析#
在这个示例中,lock_guard
自动处理互斥锁的加锁和解锁,确保每次加锁操作都能被正确管理,从而避免手动解锁时可能出现的错误。
C++11 提供的四种互斥锁为多线程编程提供了强大的支持。通过使用 mutex
、timed_mutex
、recursive_mutex
和 lock_guard
,开发者能够在多线程环境下有效保护共享资源,防止数据竞争。同时,lock_guard
的 RAII 机制大大简化了锁的使用,减少了手动加锁和解锁的错误风险。