线程的互斥和同步(8)- C++11中的互斥锁和条件变量


1. 互斥锁

之前讲过使用Windows的API和Qt中如何创建和使用互斥锁。接下来,主要说明一下C++11中的互斥锁。
c++11中的互斥锁主要有如下几种:

互斥锁说明
mutex最基本的互斥锁,不可重入
timed_mutex具有超时机制的互斥锁
recursive_mutex可重入的互斥锁
recursive_timed_mutex结合 timed_mutex 和 recursive_mutex 特点的互斥量

他们都有加锁(lock)、尝试加锁(trylock)和解锁(unlock)的方法。

(1) 递归锁和非递归锁

这里说明一下 递归锁非递归锁

  • 递归锁 ,可重入锁。表示同一线程中,可以多次加锁,要解锁相应的次数才能够解锁。
  • 非递归锁 ,不可重入。表示如果多次加锁,会阻塞导致线程死锁。

Windows API CreateCreateMutex 和临界区都属于 递归锁
而Qt中的 QMutex 创建时默认是 非递归的锁 ,构造中传入参数 Recursive (可递归),如果不传则默认为 NonRecursive (非递归)。

接下来举一个例子来说明:

std::mutex g_mutex;
int g_number = 0;

void func2(void);
void func(void)
{
	g_mutex.lock();
	++g_number;
	func2();
	g_mutex.unlock();
}

void func2(void)
{
	g_mutex.lock();
	++g_number;
	g_mutex.unlock();
}

如果在线程中,调用函数 func() 就会引起线程 死锁 ,因为它加锁两次。
而使用 std::recursive_mutex 替换 std::mutex 就没有死锁的问题。
但是,这并不意味着,我们就需要使用 递归锁 ,使用 非递归锁 更容易定位我们程序中的问题。

(2) 互斥锁管理类

之前我们在j讲 QMutex 的时候,同时讲解了RALL的 QMutexLocker ;C++11中也提供了类似的方法。
Qt中的互斥锁可参照:线程的互斥和同步(4)- Qt中的互斥锁(QMutex和QMutexLocker)

互斥锁管理类说明
lock_guard基于作用域的互斥量管理
unique_lock更加灵活的互斥量管理
  • lock_guard , 没有 lockunlock 仅提供构造时加锁,析构时解锁。
  • unique_lock , 允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。

比如可以这么使用一个互斥锁管理器:

void func(void)
{
	std::unique_lock<std::mutex> locker(g_mutex, std::defer_lock);
	locker.lock();

	++g_number;
}

locker 在构造的时候可以指定为构造时加锁还是不加锁。
这里使用 std::defer_lock 表明构造的时候不加锁,使用 adopt_lock 表示构造的时候同时加锁。
析构时,根据当前的锁属性判断是否解锁,如果当前为加锁状态,析构时就解锁。

unique_lock 它满足可移动构造 (MoveConstructible) 和可移动赋值 (MoveAssignable) 但不满足可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) 。
同时还有方法 swap (与另一 std::unique_lock 交换状态) 和 release (将关联互斥解关联而不解锁它)

关于 unique_lock 的更多使用,可参照:
https://zh.cppreference.com/w/cpp/thread/unique_lock


2. std::condition_variable

之前讲了使用Qt创建和使用条件变量,C++11中也提供了对于条件变量的操作方法
线程的互斥和同步(7)- Qt的条件变量QWaitCondition

它同样提供了wait和唤醒的方法

  • 等待:waitwait_forwait_until
  • 唤醒:notify_one (唤醒一个) 和 notify_all (唤醒全部)

这里需要注意的是 虚假唤醒
虚假唤醒 是指可能一个线程没有发送唤醒通知,但是该线程已经被唤醒。

通常防止虚假唤醒的做法是,使用一个 while 循环,如果是虚假唤醒,则继续等待:

std::mutex g_mutex;
std::condition_variable var;
bool g_pred = false;

void func(void)
{
	std::unique_lock<std::mutex> locker(g_mutex);

	// 循环等待,防止虚假唤醒
	while (!g_pred)
	{
		// 当唤醒时,g_pred 将设置为true
		var.wait(locker);
	}
}

也可以写成如下方式:

std::mutex g_mutex;
std::condition_variable var;
bool g_pred = false;

void func(void)
{
	std::unique_lock<std::mutex> locker(g_mutex);

	// 防止虚假唤醒
	var.wait(locker, [] {return g_pred; });
}

此处的 wait 函数的第二个参数,为一个仿函数(lambda表达式就是一个匿名仿函数),如果返回值为 true 则忽略虚假唤醒;否则继续等待。

关于 std::condition_variable 更多介绍,可参考:
https://zh.cppreference.com/w/cpp/thread/condition_variable


作者:douzhq
个人博客:不会飞的纸飞机
文章同步页:线程的互斥和同步(8)- C++11中的互斥锁和条件变量

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
互斥锁条件变量C++常用的线程同步机制,用于控制多个线程之间的并发访问和操作。 1.互斥锁Mutex互斥锁是一种互斥对象,用于保护共享资源,确保同时只有一个线程访问该资源。当一个线程获得了互斥锁后,其他线程就无法再访问该资源,直到该线程释放了互斥锁。在C++11标准,可以使用std::mutex类来创建互斥锁。 示例代码: ``` #include <iostream> #include <thread> #include <mutex> std::mutex mtx; // 创建互斥锁 void print(char ch) { mtx.lock(); // 加 for (int i = 0; i < 5; ++i) { std::cout << ch; } std::cout << std::endl; mtx.unlock(); // 解 } int main() { std::thread t1(print, 'A'); std::thread t2(print, 'B'); t1.join(); t2.join(); return 0; } ``` 2.条件变量(Condition Variable) 条件变量是一种同步机制,用于协调多个线程之间的操作。当一个线程等待某个条件成立时,可以通过条件变量使其进入休眠状态,等待其他线程满足该条件并唤醒它。在C++11标准,可以使用std::condition_variable类来创建条件变量。 示例代码: ``` #include <iostream> #include <thread> #include <mutex> #include <condition_variable> std::mutex mtx; std::condition_variable cv; bool ready = false; void print(char ch) { std::unique_lock<std::mutex> lock(mtx); // 创建unique_lock对象 while (!ready) { // 等待条件成立 cv.wait(lock); // 进入休眠状态 } for (int i = 0; i < 5; ++i) { std::cout << ch; } std::cout << std::endl; } int main() { std::thread t1(print, 'A'); std::thread t2(print, 'B'); std::this_thread::sleep_for(std::chrono::seconds(2)); // 等待2秒钟 { std::lock_guard<std::mutex> lock(mtx); // 创建lock_guard对象 ready = true; // 修改条件变量的值 } cv.notify_all(); // 唤醒所有等待的线程 t1.join(); t2.join(); return 0; } ``` 上述代码,创建了一个条件变量cv和一个bool类型的变量ready。在print函数线程会进入while循环,等待ready变量为true,然后打印字符。在主线程,等待2秒钟后,将ready变量设置为true,并通过notify_all()函数唤醒所有等待的线程

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值