mutex and condition_variable[c++11]

本文深入解析并发编程中互斥锁和条件变量的使用,包括mutex、lock_guard、unique_lock、condition_variable的基本操作及应用场景,通过代码实例展示如何在多线程环境下实现高效同步。

一、mutex

mutex: 提供最基本的lock和unlock

lock_guard<mutex> locker(mtx):离开作用域,自动析构的时候会释放锁,省了一句话。其次,提供exception safety。出现异常的时候也能释放锁。

unique_lock<mutex> locker(mtex):作用和lock_guard一样的。多了一个灵活性,在作用域内可以自由lock和unlock,而lock_guard不支持。所以condition_variable只能采用unique_lock或者mutex。


整体上,最简单就用直接用mutex是没问题的,主要mutex内的东西都是最简短的代码,毕竟不能并发。

如果代码对稳定性要求高,考虑到exception safe,就后两个混用。

如果用到condition_variable,只用最后一个。


二、condition_variable

主要用wait方法,有阻塞时间限制再用wait_for或者wait_until

只能用unique_lock, condition_variable_any也支持普通mutex。


wait或wait_for的逻辑是这样的:

1.释放当前锁(unique_lock, 或者mutex)

2.阻塞等待notify

3.阻塞超过时间,跳到5

4.收到notify信号,跳到5

5.阻塞等待锁

6.获取锁成功,语句结束


一般情况会写成这样while(flag==true)conditon.wait(); 因为被notify和获取锁的以后,有些场景中可能条件又不满足了,这样可以保证一定是满足判断条件的。



#include <thread>
#include <mutex>
#include <condition_variable>
class TestThread
{
public:
	void start(){ 
		thread t(std::bind(&TestThread::run,this));
		t.detach();
	}

	void run(){
			cout << "test thread waits. id:" << std::this_thread::get_id()<<endl;
			waitToRace(); 
	}

	void waitToRace(){
			unique_lock<mutex> locker(mtx);
			condition.wait(locker);
			cout << "test thread is racing. id:" << std::this_thread::get_id() << endl;
	}
};




void go(){
	int k = 10;
	while(k--){
		cout << "go one:" << std::this_thread::get_id() << endl;;
		condition.notify_one();
		this_thread::sleep_for(chrono::milliseconds(1500));
	}
}


int main(void)
{
	cout << "main thread id:" << std::this_thread::get_id() << endl;
	
	for (int i = 0; i < 10; i++){
		TestThread testThread;
		testThread.start();
	}

	go();

	getchar();
	return 0;
}





<think> 我们被要求回答关于mutexcondition_variable和unique_lock的使用方法和原理。根据站内引用,我们可以总结以下几点: 1. 引用[1]和[2]指出,condition_variable的wait函数要求传入一个unique_lock<std::mutex>类型的锁,而不是scoped_lock(虽然C++17引入了scoped_lock,但condition_variable的接口仍然要求unique_lock)。 2. 引用[3]解释了condition_variable的wait函数会做两件事:将当前线程置为睡眠状态,并释放传入的锁(通过调用unique_lock的unlock方法),以便其他线程可以获得锁。 3. 引用[4]提到了condition_variable的两种通知函数:notify_one和notify_all,以及wait的两种形式(带谓词和不带谓词)。 下面我们将详细解释mutex、unique_lock和condition_variable的使用方法和原理。 ### Mutex(互斥锁) Mutex(互斥量)是用于保护共享数据免受多线程同时访问的同步原语。当一个线程锁定了互斥量,其他尝试锁定该互斥量的线程将被阻塞,直到该互斥量被解锁。 使用方法: ```cpp #include <mutex> std::mutex mtx; void critical_section() { mtx.lock(); // 访问共享资源 mtx.unlock(); } ``` 但是,直接使用lock和unlock容易因异常或忘记解锁而导致死锁。因此,通常使用RAII(Resource Acquisition Is Initialization)包装类来自动管理锁。 ### unique_lock `unique_lock`是一个通用的互斥量所有权包装器,它提供了延迟锁定、时间锁定、递归锁定等更灵活的功能。它也比`lock_guard`更重,因为需要维护锁的状态。 使用方法: ```cpp #include <mutex> std::mutex mtx; void critical_section() { std::unique_lock<std::mutex> lck(mtx); // 访问共享资源 // 离开作用域时,lck析构自动解锁 } ``` `unique_lock`可以移动但不能复制,因此它可以在函数之间传递锁的所有权。 ### condition_variable 条件变量用于多线程间的同步,它允许线程在某个条件不满足时阻塞,直到被其他线程通知。通常与互斥锁一起使用。 条件变量的工作原理: 1. 线程首先获取一个互斥锁(通常通过`unique_lock`)。 2. 检查条件是否满足,如果不满足,则调用`wait`函数,该函数会释放锁并将线程挂起。 3. 当其他线程修改了条件并调用`notify_one`或`notify_all`时,等待的线程被唤醒,重新尝试获取锁(在`wait`内部重新获取锁),然后检查条件。 使用方法示例(生产者-消费者模型): ```cpp #include <mutex> #include <condition_variable> std::mutex mtx; std::condition_variable cv; bool ready = false; // 消费者线程 void consumer() { std::unique_lock<std::mutex> lck(mtx); // 使用带谓词的wait,避免虚假唤醒 cv.wait(lck, []{ return ready; }); // 条件满足后,继续执行 } // 生产者线程 void producer() { { std::unique_lock<std::mutex> lck(mtx); ready = true; } cv.notify_one(); // 通知一个等待的线程 } ``` 注意:条件变量可能受到虚假唤醒(spurious wakeup)的影响,因此通常使用带谓词的wait函数: ```cpp cv.wait(lck, predicate); ``` 相当于: ```cpp while (!predicate()) { cv.wait(lck); } ``` ### 为什么condition_variable搭配unique_lock使用? 1. 条件变量的`wait`函数需要能够释放锁(在等待时)和重新获取锁(在唤醒时)。`unique_lock`提供了`lock`和`unlock`的公共接口,而`lock_guard`则没有(它的作用域内始终持有锁)。 2. `unique_lock`可以临时释放锁(通过`unlock`方法)并在需要时重新锁定,提供了更灵活的锁管理。 ### 原理总结 - **Mutex**:提供最基本的互斥功能。 - **unique_lock**:管理互斥锁的RAII类,提供更灵活的锁控制(如延迟锁定、手动解锁和条件变量配合)。 - **condition_variable**:用于线程间同步,需要配合互斥锁和条件判断,避免忙等待。 通过以上机制,我们可以有效地实现线程同步。 §§相关问题§§ 1. 什么是虚假唤醒(spurious wakeup)?如何避免? 2. 在C++中,lock_guard和unique_lock的主要区别是什么? 3. 条件变量除了notify_one和notify_all,还有哪些使用注意事项? </think> ### Mutexcondition_variable 和 unique_lock 的使用方法与原理 #### 1. **Mutex(互斥锁)** **原理**: Mutex 是最基本的线程同步原语,通过二进制锁机制防止多线程同时访问共享资源。当一个线程持有锁时,其他尝试获取锁的线程会被阻塞,直到锁被释放[^3]。 **使用方法**: ```cpp #include <mutex> std::mutex mtx; // 声明互斥锁 void thread_function() { mtx.lock(); // 加锁 // 访问共享资源 mtx.unlock(); // 解锁 } ``` **关键点**: - 必须手动调用 `lock()`/`unlock()`,但容易遗漏解锁导致死锁。 - 通常配合 RAII 包装类(如 `lock_guard` 或 `unique_lock`)使用以实现自动解锁。 --- #### 2. **unique_lock** **原理**: 基于 RAII(资源获取即初始化)机制,在构造时加锁,析构时自动解锁。与 `lock_guard` 相比,提供更灵活的锁管理: - 支持延迟加锁(`defer_lock`) - 允许手动加锁/解锁(`lock()`/`unlock()`) - 可移动但不可复制[^3] **使用方法**: ```cpp std::mutex mtx; std::unique_lock<std::mutex> ulock(mtx); // 自动加锁 // 手动控制锁 ulock.unlock(); // 临时释放锁 // ...其他操作... ulock.lock(); // 重新加锁 ``` **为什么优于 `lock_guard`**: - 支持条件变量(`condition_variable`)的 `wait()` 接口。 - 允许临时释放锁(如等待条件变量时)[^1][^3]。 --- #### 3. **condition_variable** **原理**: 用于线程间同步,解决「等待-通知」场景。线程在条件不满足时主动阻塞并释放锁,被唤醒后重新获取锁[^2][^3]。 **核心操作**: - `wait(unique_lock, 谓词)`: 释放锁 → 阻塞线程 → 被唤醒后重新加锁 → 检查谓词(避免虚假唤醒)。 - `notify_one()`:唤醒一个等待线程。 - `notify_all()`:唤醒所有等待线程。 **使用方法**(生产者-消费者模型): ```cpp std::mutex mtx; std::condition_variable cv; bool data_ready = false; // 消费者线程 std::unique_lock<std::mutex> ulock(mtx); cv.wait(ulock, []{ return data_ready; }); // 等待条件满足 // 生产者线程 { std::lock_guard<std::mutex> lg(mtx); data_ready = true; } cv.notify_one(); // 通知消费者 ``` **为何必须搭配 `unique_lock`**: `wait()` 需在内部释放锁(避免死锁),唤醒后需重新加锁。`unique_lock` 支持手动 `unlock()`/`lock()`,而 `lock_guard` 无法实现此操作[^1][^2][^3]。 --- ### 总结与关系图 ```mermaid graph LR A[Mutex] -->|提供基本锁机制| B[unique_lock] B -->|锁管理+灵活控制| C[condition_variable] C -->|依赖锁释放/重入| B ``` - **Mutex**:基础锁,需手动管理。 - **unique_lock**:自动管理锁,支持条件变量。 - **condition_variable**:依赖 `unique_lock` 实现线程挂起/唤醒。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值