C++线程同步——阻塞线程的方法

2023-05-16

一般,使线程阻塞我们可以使用 while(condition); for(;condition;); 等循环条件使之线程内语句执行在循环处无法向下继续执行,但这样并不是真正意义上的线程阻塞,当前线程仍然在执行,只是在循环语句处不断空耗CPU

在C中有信号量、互斥量、条件变量、读写锁等可用于线程同步,他们都有对应的可以使之线程阻塞的方法。

例如C的信号量。C头文件 <semaphore.h>中

  • sem_wait() 会阻塞当前线程
  • sem_trywait() 返回错误而不是阻塞调用
  • sem_timedwait() sem_timedwait的abs_timeout参数指定了调用应该阻塞的时间限制

在C++中主要有以下方法:
信号量
信号量 (semaphore) 是一种轻量的同步原件,用于制约对共享资源的并发访问。在可以使用两者时,信号量能比条件变量更有效率。

定义于头文件 <semaphore>
counting_semaphore 实现非负资源计数的信号量
binary_semaphore 仅拥有二个状态的信号量(typedef)

其中

  • acquire 减少内部计数器或阻塞到直至能如此
  • try_acquire 尝试减少内部计数器而不阻塞
  • try_acquire_for 尝试减少内部计数器,至多阻塞一段时长
  • try_acquire_until 尝试减少内部计数器,阻塞直至一个时间点

互斥
互斥算法避免多个线程同时访问共享资源。这会避免数据竞争,并提供线程间的同步支持。

定义于头文件 <mutex>

  • lock 锁定互斥,若互斥不可用则阻塞 ,其中lock可锁定多个mutex对象,并内置免死锁算法避免死锁。
  • try_lock 尝试锁定互斥,若互斥不可用则返回 (不阻塞)
  • unlock 解锁互斥

通常不直接使用 std::mutex ,一般使用 std::unique_lock 、 std::lock_guard 或 std::scoped_lock 互斥器管理器使用。

  • lock_guard 实现严格基于作用域的互斥体所有权包装器
  • scoped_lock 用于多个互斥体的免死锁 RAII 封装器
  • unique_lock 实现可移动的互斥体所有权包装器

条件变量
条件变量是允许多个线程相互交流的同步原语。它允许一定量的线程等待(可以定时)另一线程的提醒,然后再继续。条件变量始终关联到一个互斥。

定义于头文件 <condition_variable>

condition_variable 类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable 。

有意修改变量的线程必须

  1. 获得 std::mutex (常通过 std::lock_guard )
  2. 在保有锁时进行修改
  3. 在 std::condition_variable 上执行 notify_one 或 notify_all (不需要为通知保有锁)
    即使共享变量是原子的,也必须在互斥下修改它,以正确地发布修改到等待的线程。

由上可得,条件变量在线程同步时,需要获得互斥量才能进行。而 unique_lock 这个互斥器管理器允许自由的unlock,所以一般条件变量与unique_lock一起使用。

  • wait 、 wait_for 或 wait_until ,等待操作自动释放互斥,并悬挂线程的执行(阻塞)。
    在这里插入图片描述

Future
标准库提供了一些工具来获取异步任务(即在单独的线程中启动的函数)的返回值,并捕捉其所抛出的异常。这些值在共享状态中传递,其中异步任务可以写入其返回值或存储异常,而且可以由持有该引用该共享态的 std::future 或 std::shared_future 实例的线程检验、等待或是操作这个状态。

  • get 返回结果 ,get 方法等待直至 future 拥有合法结果并(依赖于使用哪个模板)获取它。它等效地调用 wait() 等待结果。(阻塞
  • wait 等待结果变得可用 。阻塞直至结果变得可用。调用后 valid() == true 。
  • wait_for 等待结果,如果在指定的超时间隔后仍然无法得到结果,则返回。 阻塞直至经过指定的 timeout_duration,或结果变为可用
  • wait_until 等待结果,如果在已经到达指定的时间点时仍然无法得到结果,则返回。阻塞直至抵达指定的 timeout_time ,或结果变为可用

this_thread
在this_thread命名空间下,有this_thread::sleep_for、this_thread::sleep_until、this_thread::yield 三个方法。

其中前两个可以通过让线程睡眠一段时间而达到阻塞线程的目的。

后者,可以通过一定的条件使得当前线程让度给其他线程达到阻塞的目的。while (condition) this_thread::yield();

原子操作(CAS)

此外,使用原子量实现自旋锁,也可以在一定时间内阻塞线程。

class CAS	// 自旋锁
{
private:
	std::atomic<bool> flag;	// true 加锁、false 无锁
public:
	CAS() :flag(true) {}   // 注意这里初始化为 true,因此,第一次调用 lock()就会阻塞
	~CAS() {}
	CAS(const CAS&) = delete;
	CAS& operator=(const CAS&) = delete;

	void lock()	// 加锁
	{
		bool expect = false;
		while (!flag.compare_exchange_strong(expect, true))
		{
			expect = false;
		}
	}
	void unlock()
	{	
		flag.store(false);
	}
};
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++线程同步——阻塞线程的方法 的相关文章

随机推荐