C++11 Mutex的风险(译)

Mutex风险与死锁
  • 点击查看原文 The Risks of Mutexes
  • 文章主要通过介绍直接调用 mutex 的 lock 和 unlock 容易导致死锁,引出 lock(另外的博文,结尾链接)。

The Risks of Mutexes

Mutex的风险

Usage of mutexes seems extremely simple. There is a critical section in the code, which can only be accessed by a single thread at any point of time. It’s ensured by a mutex m. The calls m.lock() and m.unlock() guarantee this exclusivity. But, the devil is in the details.

  • mutex 的用法似乎非常简单。代码中有一个关键部分,该部分在任何时间点都只能由单个线程访问。它是由一个 mutex m 确保的。m.lock() 和 m.unlock() 的调用保证了这种排他性。但是,细节决定成败。
Deadlock

The different names for deadlocks are frightening. Some call them deadly embrace (hug of death 😃? or kiss of death. But wait, what is a deadlock?

  • 死锁的不同名称是可怕的。有些称它们为致命的拥抱(死亡拥抱)?或死亡之吻。但是等等,什么是死锁?

Deadlock
A deadlock is a state, in which at least two threads are blocked, because each thread is waiting for release of some resource with which other thread works, before it releases its own resource.
The result of a deadlock is total standstill. The Thread and usually the whole program is blocked forever. It is easy to produce a deadlock. Curious?

  • 死锁是一种状态,其中至少有两个线程被阻塞,因为每个线程在释放它们自己的资源前 都在等待其他线程释放正在使用的资源。
  • 死锁的结果是完全停滞。线程和通常整个程序永远被阻塞。产生死锁很容易。好奇?
Exceptions and unknown code
std::mutex m;
m.lock();
sharedVariable= getVar();
m.unlock();

In case the unknown code in the function getVar() throws an exception, m.unlock() will not be called. Every attempt to to ask for the mutex m will fail and the program will block. Forever. But that is not the only issue with that piece of code. It calls some (unknown to us) function get.Var(), while m.lock() is active. What will happen if the function getVar() tries to get the same lock? Of course, you know it. A deadlock.
Do you want to have a more visual example?

  • 例子中,如果函数 getVar() 抛出异常,m.unlock()将不会被调用。每个尝试请求 mutex m 都将失败,程序将永远阻塞。但是,这并不是该代码唯一的问题。在 m.lock() 处于活动状态时,函数 getVar() 调用了一些(对我们来说未知的)代码。 如果函数 getVar() 尝试获取相同的锁将发生什么?当然你知道的,死锁。
  • 是否要有一个更直观的例子?
Lock mutexes in different order

在这里插入图片描述

Thread 1 and Thread 2 need access to two resources in order to finish their work. Unfortunately, they ask for the resources which are protected by two mutexes in different order. In this case the thread executions will interleave in such a way that thread 1 gets mutex 1, then thread 2 gets mutex 2, and we have a standstill. Each thread wants to get the other’s mutex. For this, thread has to wait for the release of the resource.

It’s easy to express the picture in code.

  • 线程1 和线程2 需要访问两个资源才能完成其工作。不幸的是,他们需要的资源被两个 mutex 以不同的顺序保护。这种情况下,线程将以下面的方式交错执行:线程1 获得 mutex1, 然后线程2 获得 mutex2, 然后程序被阻塞。每个线程想得到另一个线程的 mutex。为此,线程必须等待资源的释放。

  • 很容易用代码来描述上述图片。

    // deadlock.cpp
    
    #include <iostream>
    #include <chrono>
    #include <mutex>
    #include <thread>
    
    struct CriticalData{
      std::mutex mut;
    };
    
    void deadLock(CriticalData& a, CriticalData& b){
    
      a.mut.lock();
      std::cout << "get the first mutex" << std::endl;
      std::this_thread::sleep_for(std::chrono::milliseconds(1));
      b.mut.lock();
      std::cout << "get the second mutex" << std::endl;
      // do something with a and b
      a.mut.unlock();
      b.mut.unlock();
      
    }
    
    int main(){
    
      CriticalData c1;
      CriticalData c2;
    
      std::thread t1([&]{deadLock(c1,c2);});
      std::thread t2([&]{deadLock(c2,c1);});
    
      t1.join();
      t2.join();
    
    }
    

Thread t1 and thread t2 call the function deadlock (line 12 - 20). To process deadlock, both functions need the CriticalData c1 and c2 (line 27 and 28). Because the objects c1 and c2 must be protected from shared access, they have a mutex (to keep this example code short and simple CriticalData doesn’t have any other methods or members apart from mutex)
Only a sleep about 1 Millisecond in line 16, and we have the deadlock.

  • 线程 t1 和 线程 t2 调用函数 deadlock (12-20行)。要处理 deadlock,这两个函数都需要 CriticalData c1 和 c2 ( 27- 28行)。由于对象 c1 和 c2 不能被共享访问,它们 有 一个 mutex (要保持此例代码简短 且 简单的 CriticalData 除了 mutex 没有其他 方法或成员)
  • 在 16 行 睡眠 1 毫秒,然后我们得到一个死锁。
    在这里插入图片描述

The only choice now is the press CTRL+C to kill the process.

  • 唯一的办法是 按下 CTRL+C 结束程序。
What’s next?

Honestly, the example will not boost your confidence in writing multithreading programs. Additionally, the complexity will increase to the power of 2 which each new mutex. The solution to the problem are locks, because they encapsulate mutexes in a safe manner.

  • 老实说,这个例子并不会增强你编写多线程程序的信心。此外,每增加一个 mutex,复杂性将以 2 的幂级增长。问题的解决方案是 locks, 因为它们以安全的方式封装 mutex。 点击 这里
<think>我们正在讨论C++中使用Mutex进行线程同步。根据引用内容,我们可以总结如下: 1. Mutex(互斥量)是C++标准库中用于线程同步的重要工具,位于<mutex>头文件中。 2. 基本使用方式:通过lock()和unlock()方法手动控制,但更推荐使用RAII风格的lock_guard或unique_lock来自动管理锁的生命周期,避免忘记解锁。 引用[2]提供了一个使用mutex的代码示例,展示了如何通过mutex保护共享变量counter。但手动调用lock/unlock容易出错,因此引用[1]和引用[5]都推荐使用lock_guard或unique_lock。 引用[5]展示了使用lock_guard的示例: ```cpp #include <mutex> std::mutex mtx; void safeFunction() { std::lock_guard<std::mutex> lock(mtx); // 临界区 } ``` 此外,引用[1]还提到了其他类型的互斥量: - std::recursive_mutex:允许同一线程多次加锁(递归锁) - std::shared_mutex:读写锁,支持共享读和独占写 因此,回答应包含: 1. 基本mutex使用方法(手动lock/unlock) 2. 推荐使用RAII包装器(lock_guard, unique_lock) 3. 其他类型的mutex简介 同时,根据系统指令,需要生成相关问题。 下面开始组织回答。</think>### C++ 线程同步 Mutex 使用方法 Mutex(互斥量)是 C++ 中最基础的线程同步工具,用于保护共享资源被多个线程同时访问。以下是详细使用方法: #### 1. **基础用法(手动加锁/解锁)** ```cpp #include <iostream> #include <thread> #include <mutex> std::mutex mtx; // 声明互斥量 int shared_data = 0; // 共享资源 void increment() { mtx.lock(); // 手动加锁(阻塞其他线程) shared_data++; // 操作共享资源 mtx.unlock(); // 手动解锁 } int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout << "结果: " << shared_data; // 输出 2 } ``` **说明**: - `lock()`:获取锁,若锁已被占用则阻塞当前线程 - `unlock()`:释放锁,允许其他线程获取 - **风险**:忘记解锁会导致死锁,推荐直接使用[^2] #### 2. **推荐用法:RAII 风格(自动管理锁)** 使用 `std::lock_guard` 或 `std::unique_lock` 自动管理锁生命周期: ```cpp void safe_increment() { std::lock_guard<std::mutex> lock(mtx); // 构造时自动加锁 shared_data++; // 操作共享资源 } // 析构时自动解锁(即使发生异常) ``` **优势**: - 避免忘记解锁 - 异常安全(作用域结束自动释放)[^1][^5] #### 3. **其他互斥量类型** | 类型 | 用途 | |--------------------------|-------------------------------| | `std::recursive_mutex` | 允许同一线程重复加锁(递归锁) | | `std::shared_mutex` | 读写锁(共享读/独占写) | | `std::scoped_lock` (C++17)| 同时锁定多个互斥量 | #### 4. **关键注意事项** 1. **锁粒度**:锁范围应尽量小,仅覆盖共享资源操作 ```cpp // 错误示例(锁范围过大) { std::lock_guard lock(mtx); read_file(); // 耗时操作(阻塞其他线程) update_data(); } ``` 2. **死锁预防**: - 避免嵌套锁(如需多个锁,按固定顺序获取) - 使用 `std::scoped_lock` 同时锁定多个互斥量[^1] 3. **性能影响**:锁竞争会降低并发性,高并发场景考虑无锁编程 > **最佳实践**:优先使用 `std::lock_guard`,复杂场景(如条件变量)用 `std::unique_lock`[^4][^5]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值