@@ -73,11 +73,13 @@ std::thread t1{f}, t2{f}, t3{f}; // 未定义行为
73
73
74
74
## 使用互斥量
75
75
76
- 互斥量(Mutex),又称为互斥锁, 是一种用来保护**临界区**的特殊对象,它可以处于锁定(locked) 状态, 也可以处于解锁(unlocked) 状态:
76
+ 互斥量(Mutex),又称为互斥锁,是一种用来保护**临界区**[^1] 的特殊对象,它可以处于锁定(locked)状态,也可以处于解锁(unlocked)状态:
77
77
78
- 1、 如果互斥锁是锁定的, 通常说某个特定的线程正持有这个互斥锁
78
+ 1. 如果互斥锁是锁定的, 通常说某个特定的线程正持有这个互斥锁
79
79
80
- 2、如果没有线程持有这个互斥锁,那么这个互斥锁就处于解锁状态
80
+ 2. 如果没有线程持有这个互斥量,那么这个互斥量就处于解锁状态
81
+
82
+ [^1]: "***[临界区](https://zh.wikipedia.org/wiki/%E8%87%A8%E7%95%8C%E5%8D%80%E6%AE%B5)***"指的是一个访问共享资源的程序片段,而这些共享资源又无法同时被多个线程访问的特性。在临界区中,通常会使用同步机制,比如我们要讲的互斥量(Mutex)。
81
83
82
84
---
83
85
@@ -128,6 +130,8 @@ int main() {
128
130
129
131
看一遍描述就可以了,简而言之,被 ` lock() ` 和 ` unlock() ` 包含在其中的代码是线程安全的,同一时间只有一个线程执行,不会被其它线程的执行所打断。
130
132
133
+ ### ` std::lock_guard `
134
+
131
135
不过一般不推荐这样显式的 ` lock() ` 与 ` unlock() ` ,我们可以使用 C++11 标准库引入的“管理类” [ ` std::lock_guard ` ] ( https://zh.cppreference.com/w/cpp/thread/lock_guard ) :
132
136
133
137
``` cpp
@@ -257,6 +261,43 @@ std::scoped_lock lc{ m }; // std::scoped_lock<std::mutex>
257
261
258
262
我们在后续管理多个互斥量,会详细了解这个类。
259
263
264
+ ### ` try_lock `
265
+
266
+ ` try_lock ` 是互斥量中的一种尝试上锁的方式。与常规的 ` lock ` 不同,` try_lock ` 会尝试上锁,但如果锁已经被其他线程占用,则** 不会阻塞当前线程,而是立即返回** 。
267
+
268
+ 它的返回类型是 ` bool ` ,如果上锁成功就返回 ` true ` ,失败就返回 ` false ` 。
269
+
270
+ 这种方法在多线程编程中很有用,特别是在需要保护临界区的同时,又不想线程因为等待锁而阻塞的情况下。
271
+
272
+ ``` cpp
273
+ std::mutex mtx;
274
+
275
+ void threadFunction (int id) {
276
+ // 尝试加锁
277
+ if (mtx.try_lock()) {
278
+ std::cout << "线程:" << id << " 获得锁" << std::endl;
279
+ // 临界区代码
280
+ std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟临界区操作
281
+ mtx.unlock(); // 解锁
282
+ std::cout << "线程:" << id << " 释放锁" << std::endl;
283
+ } else {
284
+ std::cout << "线程:" << id << " 获取锁失败 处理步骤" << std::endl;
285
+ }
286
+ }
287
+ ```
288
+
289
+ 如果有两个线程运行这段代码,必然有一个线程无法成功上锁,要走 else 的分支。
290
+
291
+ ```cpp
292
+ std::thread t1(threadFunction, 1);
293
+ std::thread t2(threadFunction, 2);
294
+
295
+ t1.join();
296
+ t2.join();
297
+ ```
298
+
299
+ > [ 运行] ( https://godbolt.org/z/ajjxnPGMG ) 测试。
300
+
260
301
## 保护共享数据
261
302
262
303
互斥量主要也就是为了保护共享数据,上一节的* 使用互斥量* 也已经为各位展示了一些。
@@ -310,7 +351,7 @@ void foo(){
310
351
311
352
试想一下,有一个玩具,这个玩具有两个部分,必须同时拿到两部分才能玩。比如一个遥控汽车,需要遥控器和玩具车才能玩。有两个小孩,他们都想玩这个玩具。当其中一个小孩拿到了遥控器和玩具车时,就可以尽情玩耍。当另一个小孩也想玩,他就得等待另一个小孩玩完才行。再试想,遥控器和玩具车被放在两个不同的地方,并且两个小孩都想要玩,并且一个拿到了遥控器,另一个拿到了玩具车。问题就出现了,除非其中一个孩子决定让另一个先玩,他把自己的那个部分给另一个小孩。但如果他们都不愿意,那么这个遥控汽车就谁都没有办法玩。
312
353
313
- 我们当然不在于小孩抢玩具 ,我们要聊的是线程对锁的竞争:*两个线程需要对它们所有的互斥量做一些操作,其中每个线程都有一个互斥量,且等待另一个线程的互斥量解锁。因为它们都在等待对方释放互斥量,没有线程工作。* 这种情况就是死锁。
354
+ 我们当然不在乎小孩抢玩具 ,我们要聊的是线程对锁的竞争:*两个线程需要对它们所有的互斥量做一些操作,其中每个线程都有一个互斥量,且等待另一个线程的互斥量解锁。因为它们都在等待对方释放互斥量,没有线程工作。* 这种情况就是死锁。
314
355
315
356
- **多个互斥量才可能遇到死锁问题**。
316
357
@@ -781,8 +822,8 @@ void recursiveFunction(int count) {
781
822
782
823
如果你的标准达到 ** C++11** ,要求下列** 函数** 是线程安全的:
783
824
784
- - [ ** ` new ` 运算符** ] ( https://zh.cppreference.com/w/cpp/memory/new/operator_new ) 和 [ ` delete ` 运算符] ( https://zh.cppreference.com/w/cpp/memory/new/operator_delete ) 的** 库** 版本
785
- - 全局 ** ` new ` 运算符 ** 和 ` delete ` 运算符的用户替换版本
825
+ - [ ` new ` 运算符] ( https://zh.cppreference.com/w/cpp/memory/new/operator_new ) 和 [ ` delete ` 运算符] ( https://zh.cppreference.com/w/cpp/memory/new/operator_delete ) 的** 库** 版本
826
+ - 全局 ` new ` 运算符和 ` delete ` 运算符的用户替换版本
786
827
- [ std::calloc] ( https://zh.cppreference.com/w/cpp/memory/c/calloc ) 、[ std::malloc] ( https://zh.cppreference.com/w/cpp/memory/c/malloc ) 、[ std::realloc] ( https://zh.cppreference.com/w/cpp/memory/c/realloc ) 、[ std::aligned_alloc] ( https://zh.cppreference.com/w/cpp/memory/c/aligned_alloc ) (C++17 起)、[ std::free] ( https://zh.cppreference.com/w/cpp/memory/c/free )
787
828
788
829
所以以下函数在多线程运行是线程安全的:
0 commit comments