一般,使线程阻塞我们可以使用 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 。
有意修改变量的线程必须
- 获得 std::mutex (常通过 std::lock_guard )
- 在保有锁时进行修改
- 在 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);
}
};