在多线程环境下,多个线程同时使用同一个资源可能会发生意想不到的结果,因此需要对线程操作进行同步操作,保证资源使用行为符合预期
C++提供了互斥库<mutex>
、条件变量库<condition_variable>
、异步操作库<future>
。由于C++标准还在不断完善线程同步工具,因此这里只涉及C++11标准
mutex
简介
定义于<mutex>
,是能用于保护共享数据免受从多个线程同时访问的同步原语。
mutex 提供排他性非递归所有权语义:
- 调用方线程从它成功调用 lock 或 try_lock 开始,到它调用 unlock 为止占有 mutex 。
- 线程占有 mutex 时,所有其他线程若试图要求 mutex 的所有权,则将阻塞(对于 lock 的调用)或收到 false 返回值(对于 try_lock ).
- 调用方线程在调用 lock 或 try_lock 前必须不占有 mutex 。
- 若 mutex 在仍为任何线程所占有时即被销毁,或在占有 mutex 时线程终止,则行为未定义。
std::mutex 既不可复制亦不可移动。
mutex
成员函数
lock
:锁定互斥,若互斥不可用则阻塞
try_lock
:尝试锁定互斥,若互斥不可用则返回
unlock
:解锁互斥
通用互斥管理
lock_guard
:实现严格基于作用域的互斥体所有权包装器
类 lock_guard 是互斥体包装器,为在作用域块期间占有互斥提供便利 RAII 风格机制。创建 lock_guard 对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard 对象的作用域时,销毁 lock_guard 并释放互斥。lock_guard 类不可复制。
unique_lock
:实现可移动的互斥体所有权包装器
类 unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。类 unique_lock 可移动-满足可移动构造和可移动赋值,但不可复制。类 unique_lock 满足基本可锁定(简单理解为可以lock()和unlock())要求
区别
lock_guard创建时加锁,离开作用域解锁
unique_lock创建时加锁,离开作用域解锁。可以维持锁的状态供后续自行加锁解锁(类似mutex),相对lock_guard更灵活,但要付出额外代价保存状态
条件变量
条件变量是允许多个线程相互交流的同步原语。它允许一定量的线程等待(可以定时)另一线程的提醒,然后再继续。条件变量始终关联到一个互斥
condition_variable 类是同步原语,定义于头文件condition_variable
,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知
成员函数
- 通知
notify_one 通知一个等待的线程
notify_all 通知所有等待的线程 - 等待
wait 阻塞当前线程,直到条件变量被唤醒
wait_for 阻塞当前线程,直到条件变量被唤醒,或到指定时限时长后
wait_until 阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点
Future
标准库提供了一些工具来获取异步任务(即在单独的线程中启动的函数)的返回值,并捕捉其所抛出的异常。这些值在共享状态中传递,其中异步任务可以写入其返回值或存储异常,而且可以由持有该引用该共享态的 std::future 或 std::shared_future 实例的线程检验、等待或是操作这个状态。
定义于头文件
成员函数
- promise 存储一个值以进行异步获取
- future 等待被异步设置的值
std::promise 和 std::future 是 C++ 进行单向数据传递的一种方式。std::promise 是数据的输入端,std::future 是数据的输出端。 - shared_future 等待被异步设置的值(可能为其他 future 所引用)。类似 std::future ,除了允许多个线程等候同一共享状态
- packaged_task 打包一个函数,存储其返回值以进行异步获取
类模板 std::packaged_task 包装任何可调用 (Callable) 目标(函数、 lambda 表达式、 bind 表达式或其他函数对象),使得能异步调用它。其返回值或所抛异常被存储于能通过 std::future 对象访问的共享状态中。 - async 异步运行一个函数(有可能在新线程中执行),并返回保有其结果的 std::future
它解耦了线程的创建和执行,使得我们可以在需要的时候获取异步 操作的结果;其次它还提供了线程的创建策略(比如可以通过延迟加载的方式去创建线程),使得我们可以以多种方式去创建线程。参考