thread
std::thread
类代表一个单独的执行线程。在创建与线程对象相关联时,线程会立即开始执行(在等待操作系统调度的延迟之后),从构造函数参数中提供的顶层函数开始执行。顶层函数的返回值被忽略,如果它通过抛出异常终止,则会调用 std::terminate
。
std::thread
对象也可以处于不表示任何线程的状态(默认构造、移动、分离或加入之后),而且执行线程可能不与任何 std::thread
对象关联(分离之后)。
std::thread
不可复制构造或复制赋值,但是可以移动构造和移动赋值。
构造函数:
- 默认构造函数。创建一个不表示任何线程的新的std::thread对象。
- 移动构造函数。将
other
所表示的线程转移给新的std::thread
对象。在此调用后,other
不再表示一个线程。 - 创建一个新的
std::thread
对象并将其与一个线程关联起来,且新的线程开始执行。
析构函数:
销毁线程对象。 如果 *this 有关联的线程 (joinable() == true),则调用 std::terminate()。
移动构造函数:
如果 *this
仍然关联着一个正在运行的线程(即 joinable() == true
),则调用 std::terminate()
终止程序。否则,将 other
的状态赋值给 *this
,并将 other
设为默认构造状态。
Observers:
joinable:
joinable()
函数用于检查 std::thread
对象是否标识着一个活动的执行线程。一个默认构造的线程是不可结合(joinable)的。
get_id:
返回 std::thread::id 的值。
native_handle:
返回实现定义的底层线程句柄。对于LInux系统来说,就是返回一个Linux系统中定义的线程类型值。
hardware_concurrency(static):
返回实现支持的并发线程数。该值应仅被视为提示。
Operations:
join:
阻塞当前线程,直到由 *this
标识的线程执行完成。
detach:
将执行线程与线程对象分开,允许独立继续执行。调用 detach *this 后不再拥有任何线程。
swap:
交换两个线程对象的底层句柄
Non-member functions:
std::swap:
重载了次类的std::swap算法,其实现相当于调用成员函数swap。
管理线程的非成员函数:
std::this_thread::yield();
重新调度线程的执行,让其他线程运行。
std::this_thread:get_id();
返回当前线程的id。
std::this_thread::sleep_for();
阻塞当前线程的执行,至少持续指定的 sleep_duration。
由于调度或资源争用延迟的原因,此函数可能会阻塞的时间超过 sleep_duration。
std::sleep_until();
阻塞当前线程的执行,直到达到指定的 sleep_time。
锁
std::mutex
互斥锁, 一种独占性资源,因此没有复制构造函数和复制运算符。线程通过调用它的成员函数lock或try_lock才能拥有该互斥量。如果一个互斥量在任何线程仍然拥有它的情况下被销毁,或者一个线程在拥有互斥量时终止,程序的行为是未定义的。
Member types:
构造函数:
只有默认构造函数。复制构造函数被删除。也没有移动构造函数。
Locking
lock:
对互斥量加锁。
try_lock:
尝试锁定互斥锁。立即返回,成功锁定后返回true,否则返回false。
unlock:
对互斥锁解决。
Native handle:
native_handle();
返回底层实现定义的本地句柄对象。
std::timed_mutex
timed_mutex 提供了mutex的全部功能,除此之外,还提供了超时的情况下锁的是否锁定的问题。在Linux系统中,它和std::mutex的native_handle()的返回值是一样的。
它提供了std::mutex的全部的成员函数。以下是std::mutex没有的两个成员函数。
Locking:
try_lock_for:
尝试锁定互斥。阻塞直到指定的持续时间 timeout_duration 已过(超时)或锁被获取(拥有互斥),以先到者为准。成功获取锁后返回 true,否则返回 false。
try_lock_until:
尝试锁定互斥量。阻塞直到达到指定的超时时间(timeout_time)或成功获取锁定(拥有互斥量),以先到者为准。成功获取锁定时返回 true,否则返回 false。
如果超时时间已经过去,此函数的行为类似于 try_lock()。
std::shared_mutex
shared_mutex
具有两个级别的访问权限:
- 共享访问:多个线程可以共享对同一个互斥量的拥有权。
- 独占访问:只有一个线程可以拥有互斥量。
在同一个线程内,同一时间只能获取一个锁(共享锁或独占锁)。它不能被拷贝。
构造函数:
默认构造函数;没有拷贝构造函数和移动构造函数。
没有赋值运算符。
Exclusive locking
lock:
获得对 shared_mutex 的独占所有权。如果另一个线程在同一个 shared_mutex 上持有独占锁或共享锁,调用锁定将阻塞执行,直到所有此类锁都被释放。当 shared_mutex 以独占模式锁定时,不能同时持有其他任何类型的锁。
try_lock:
尝试锁定互斥。立即返回。成功锁定后返回 true,否则返回 false。即使当前互斥项未被任何其他线程锁定,该函数也允许假失败并返回 false。如果 try_lock 被一个已经在任何模式(共享或独占)下拥有该互斥项的线程调用,其行为将是未定义的。
unlock:
对此锁解锁。
shared locking:
lock_shared:
获取互斥项的共享所有权。
try_lock_shared:
尝试在共享模式下锁定互斥。立即返回。成功锁定后返回 true,否则返回 false。即使互斥当前未被任何其他线程独占锁定,该函数也允许错误地失败并返回 false。
unlock_shared:
从调用线程的共享所有权中释放互斥。在共享模式下,mutex 必须被当前执行线程锁定,否则其行为将是未定义的。
std::shared_timed_mutex
和std::shared_mutex相似,相比于它,没有native_handle()这样获取底层资源的函数,且多了这四个参数:
try_lock_for:
尝试获取互斥量的锁。阻塞直到指定的持续时间 timeout_duration
经过(超时)或者成功获取锁(拥有互斥量),以先到者为准。如果成功获取锁,则返回 true,否则返回 false。由于调度或资源争用延迟,此函数可能会阻塞超过 timeout_duration
的时间。与 try_lock()
类似,即使在 timeout_duration
的某个时间点上互斥量没有被其他线程锁定,此函数也可以出现虚假失败并返回 false。
try_lock_until:
尝试获取互斥量的锁。阻塞直到达到指定的超时时间 timeout_time
(到达超时)或成功获取锁(拥有互斥量),以先到者为准。如果成功获取锁,则返回 true,否则返回 false。如果超时时间 timeout_time
已经过去,则此函数的行为类似于 try_lock()
。与 try_lock()
类似,即使在超时时间之前互斥量没有被其他线程锁定,该函数也可以出现虚假失败并返回 false。
try_lock_shared_for:
与try_lock_for类似,只是获取的是共享锁。
try_lock_shared_until:
与try_lock_until类似,只是获取的是共享锁。
锁管理
std::lock_guard
lock_guard 类是一个互斥包装器, 当创建 lock_guard 对象时,它会尝试获取所给互斥的所有权。当控制权离开创建 lock_guard 对象的作用域时,lock_guard 会被析构,mutex 也会被释放。lock_guard类不可被拷贝,没有赋值运算符。
它的模板参数必须符合BasicLockable要求。
构造函数:
- 接受一个锁参数,然后立刻调用其lock()成员函数。
- 获取 mutex m 的所有权,但不试图锁定它。如果当前线程未持有 m 上的非共享锁,则该行为未定义。
析构函数:
调用拥有的锁的unlock()。
std::unique_lock
类unique_lock
是一个通用的互斥量所有权包装器,允许延迟锁定、限时尝试锁定、递归锁定、锁的所有权转移以及与条件变量一起使用。类unique_lock
是可移动的,但不可复制。
构造函数:
- 默认构造函数,构造一个没有关联互斥量的对象。
- 移动构造函数,用其他对象的内容初始化
unique_lock
对象,同时将其他对象置为没有关联互斥量。 - 使用互斥量
m
构造一个关联的unique_lock
对象,然后可以通过选择是否传参以及传递什么参数,使互斥量被锁定、不锁定、调用try_lock()锁定、认为互斥量是非共享锁定状态的、调用try_lock_for或调用try_lock_until的。
析构函数:
如果 *this 有关联的互锁体,并且已获得其所有权,则互锁体被解锁。
移动赋值操作符:
使用移动语义将内容替换为其他内容。如果在调用 *this 之前已关联了一个互斥项并获得了它的所有权,则互斥项将被解锁。
Locking:
lock:
锁定拥有的互斥。实际上是调用拥有互斥的lock成员函数。
try_lock:
尝试在不阻塞的情况下锁定(即获取)相关的互斥。实际上是调用 mutex()->try_lock()。
try_lock_for:
实际上调用了 mutex()->try_lock_for(timeout_duration)。
try_lock_until:
实际上调用了 mutex()->try_lock_until(timeout_time)。
unlock:
解锁。实际上调用了mutex()->unlock();
Modifiers:
swap;
交换锁对象的内部状态。
release():
中断相关互斥(如果有)与 *this 的关联。不会解锁任何锁。
Observers:
mutex();
返回指向相关静态代理的指针,如果没有相关静态代理,则返回空指针。
owns_lock();
检查 *this 是否拥有锁定的互斥。
std::shared_lock
类shared_lock
是一个通用的共享互斥量所有权包装器,允许延迟锁定、定时锁定和锁的所有权转移。通过对shared_lock
进行锁定,将以共享模式锁定关联的共享互斥量. shared_lock
类是可移动的,但不可复制。
构造函数:
- 默认构造函数,构造一个没有关联互斥量的对象。
- 移动构造函数,用其他对象的内容初始化
shared_lock
对象,同时将其他对象置为没有关联互斥量。 - 使用互斥量
m
构造一个关联的shared_lock
对象,然后可以通过选择是否传参以及传递什么参数,使互斥量以共享模式被锁定、不锁定、调用try_lock_shared()锁定、认为互斥量是共享锁定状态的、调用try_lock_shared_for或调用try_lock_shared_until的。
析构函数:
如果 *this 有关联的互锁体,并且已获得其所有权,则通过调用 unlock_shared() 来解锁互锁体。
赋值运算符:
使用移动语义将内容替换为其他内容。如果在调用 *this 之前已关联了一个互斥项并获得了它的所有权,则会通过调用 unlock_shared() 来解锁互斥体。
Locking:
lock:
以共享模式锁定相关的互斥。实际上是调用拥有互斥的lock_shared成员函数。
try_lock:
尝试在不阻塞的情况下以共享模式锁定相关的互斥。实际上是调用 mutex()->try_lock_shared()。
try_lock_for:
实际上调用了 mutex()->try_lock_shared_for(timeout_duration)。
try_lock_until:
实际上调用了 mutex()->try_lock_shared_until(timeout_time)。
unlock:
解锁。实际上调用了mutex()->unlock_shared();
Modifiers:
swap;
交换锁对象的内部状态。
release():
中断相关互斥(如果有)与 *this 的关联。不会解锁任何锁。
Observers:
mutex();
返回指向相关静态代理的指针,如果没有相关静态代理,则返回空指针。
owns_lock();
检查 *this 是否拥有共享互斥量的共享所有权。
std::scoped_lock
类scoped_lock
是一个互斥量包装器, 用于在作用域块的持续时间内拥有零个或多个互斥量。如果给定了多个互斥量,则会使用避免死锁的算法,类似于std::lock
的行为。scoped_lock
类是不可复制的。
构造函数:
- 获取给定互斥量的所有权,即调用对应的lock成员函数。
- 在不尝试锁定任何互斥量的情况下获取互斥量
m...
的所有权。除非当前线程对m...
中的每个对象都持有非共享锁,否则行为是未定义的。
析构函数:
释放所有互斥的所有权,及调用每个互斥体的unlock()成员函数。
赋值运算符已经被删除。
Call once
call_once:
once_flag:
Condition variables
条件变量的唤醒丢失与虚假唤醒问题。
std::condition_variable
condition_variable 类是与 std::mutex 一起使用的同步原语,用于阻塞一个或多个线程,直到另一个线程修改了共享变量(条件)并通知 condition_variable。std::condition_variable仅适用于std::unique_lockstd::mutex,这样可以在某些平台上实现最高效率。
它不可被移动或复制。
构造函数:
默认构造函数,构造一个std::condition_variable对象。
析构函数:
销毁 std::condition_variable 类型的对象。只有在所有线程都已被通知的情况下才能安全地调用析构函数。
Notification:
notify_one():
如果有线程正在等待 *this,调用 notify_one 会解除其中一个等待线程的阻塞。
notify_all():
解除当前等待 *this 的所有线程的阻塞。
Waiting:
wait:
wait
函数会导致当前线程阻塞,直到条件变量被通知或出现虚假唤醒。它有两个重载。
- 只有参数锁:原子性地解锁
lock
,阻塞当前执行的线程,并将其添加到等待 *this 的线程列表中。它被唤醒而解除阻塞时,都会重新获取互斥锁并退出 wait 函数。 - 有参数锁和谓词:等价于如下代码:
while (!stop_waiting()) { // stop_waiting函数是谓词
wait(lock);
}
这个重载函数可以避免虚假唤醒的问题。
这里的锁必须是std::unique_lockstd::mutex
wait_for: wait_until:
前者是最多阻塞的相对超时时间,后者是最多阻塞到某个时间点。它们其余部分和wait一致。
Native handle:
native_handle():访问 *this 的本地句柄。
std::condition_variable_any
class condition_variable_any 是 std::condition_variable 的一个更通用的版本。而 std::condition_variable 只能与 std::unique_lockstd::mutex 一起使用,condition_variable_any 可以操作任何满足 BasicLockable 要求的锁。
class std::condition_variable_any 是一个 StandardLayoutType。它不可被移动或复制。
如果锁是 std::unique_lockstd::mutex,则 std::condition_variable 可能提供更好的性能。
下面列出些std::condition_variable没有的功能:
wait;wait_for; wait_until:
都多了第三个重载,多了一个stoken变量:多了一个 std::stop_token 对象,用于支持取消等待操作。当外部发出取消请求时,可以通过该对象检查并中止等待。
Latches and Barriers
Futures
Futures 功能是并发编程机制,旨在简化多线程编程和异步操作的处理。Futures 提供了一种在一个线程中计算值或执行任务,并在另一个线程中获取结果的方法。
它的核心组件时std::future
和 std::promise
。std::future
对象与 std::promise
对象之间共享一个状态。通过 std::promise
对象设置的值可以通过与其关联的 std::future
对象来获取。这样就可以使一个线程使用 std::promise
对象来设置异步操作的结果,而另一个线程使用对应的 std::future
对象来获取结果。
std::futuer
std::future
提供了一种访问异步操作结果的机制, 通过std::async、std::packaged_task或std::promise创建的异步操作可以向该异步操作的创建者提供一个std::future对象。异步操作的创建者可以使用各种方法来查询、等待或从std::future
中提取值。
构造函数:
- 默认构造函数,创建一个没有共享状态的 std::future 对象。
- 移动构造函数,构造后,被移动的对象没有共享状态
- 拷贝构造函数被删除。
析构函数:
释放任何共享状态不会阻塞等待共享状态变为就绪,除非以下所有条件都成立时可能会阻塞:(一定会阻塞吗?????)
- 共享状态是通过调用
std::async
创建的。 - 共享状态尚未就绪。
- 当前对象是对共享状态的最后一个引用。
这些操作只有在任务的启动策略为 std::launch::async
时才会阻塞
只有移动赋值运算符,没有拷贝赋值运算符。移动后,被移动的对象没有共享状态。
share():
将 *this 的共享状态(如果有的话)转移给一个 std::shared_future
对象。在调用 share
后,valid() == false
。
Getting the result:
get():
阻塞当前线程,直到future准备妥当并返回该值,或者抛出异步运行的函数的异常或被设置的异常. 在调用此成员函数后,共享状态会被释放, valid()
为 false。如果在调用此函数之前 valid()
为 false,则行为是未定义的。
State:
valid:
检查 future
是否引用共享状态。
wait:
阻塞直到结果变为可用。
wait_for:
等待结果变为可用。阻塞直到指定的 timeout_duration
经过或结果可用,以先到者为准。返回值标识结果的状态。如果 future
是通过使用lazy evaluation的方式调用 std::async
的结果,该函数会立即返回而不等待。
wait_until:
wait_until
函数等待结果变为可用。它会阻塞直到达到指定的 timeout_time
或结果可用,以先到者为准。返回值指示 wait_until
返回的原因。如果 future
是通过使用lazy evaluation的方式调用 std::async
的结果,该函数会立即返回而不等待。
std::shared_future
std::shared_future类似于 std::future
,但允许多个线程等待相同的共享状态。std::shared_future
是既可移动,也可复制的,多个共享的 std::shared_future
对象可以引用相同的共享状态。通过各自的 std::shared_future
对象,多个线程可以安全地访问相同的共享状态。
构造函数:
- 默认构造函数。构造一个空的shared_future, 不引用共享状态。
- 拷贝构造函数,构造一个引用与other相同共享状态(如果有的话)的shared_future.
- 移动构造函数,将
other
持有的共享状态转移给*this
。构造后,other.valid() == false
,且this->valid()
的返回值与在构造之前other.valid()
的返回值相同。
赋值运算符:
- 拷贝赋值运算符:1. 释放任何共享状态,并将
other
的内容赋值给*this
。赋值后,this->valid() == other.valid()
。 - 移动赋值运算符:1. 释放任何共享状态,并将
other
的内容移动赋值给*this
。赋值后,other.valid() == false
,并且this->valid()
的返回值与赋值之前的other.valid()
的返回值相同。
Getting the result:
get:
get
成员函数会等待直到 shared_future
有一个有效的结果,并且(根据使用的模板)获取它。它实际上会调用 wait()
来等待结果。与 std::future
不同,当调用 get()
时,std::shared_future
的共享状态不会无效。
State:
valid();
检查 shared_future
是否引用共享状态。
wait():
调用后 valid() == true。如果在调用此函数之前 valid() == false,则行为未定义。
wait_for();
等待结果可用。阻塞直到指定的 timeout_duration 已过或结果可用,以先到者为准。返回值标识结果的状态。如果 future 是调用 std::async 并使用了懒评估的结果,则此函数无需等待,立即返回。
wait_until();
wait_until 等待结果可用。它会阻塞,直到达到指定的 timeout_time 或结果可用为止,以先到者为准。返回值说明 wait_until 返回的原因。如果 future 是调用 async 时使用了懒评估的结果,则此函数会立即返回而无需等待。
std::async()
函数模板 std::async
异步地运行函数 f
(可能在一个独立的线程中,该线程可能是线程池的一部分),并返回一个 std::future
,最终将保存该函数调用的结果。它可以根据系统情况自动选择是独立启动一个线程运行,还是在对应的future调用get时运行;也可以由调用者指定任务调用策略。
任务调用策略:
- std::launch::async; 开启一个独立线程执行任务
- std::launch::deferred; 使用延迟批评估。
请注意,除了调用 std::async 之外,通过其他方式获得的 std::futures 的析构函数永远不会阻塞。
构造函数:
- 不加策略的构造函数
- 加策略的构造函数
std::package_task<>
类模板 std::packaged_task
可以封装任何可调用目标,以便可以异步调用它。它的返回值或抛出的异常被存储在一个共享状态中,可以通过 std::future
对象访问该状态。它不可被复制。
默认构造函数:
- 构造一个没有任务和共享状态的
std::packaged_task
对象。 - 构造一个具有共享状态和任务副本的
std::packaged_task
对象,使用std::forward<F>(f)
进行初始化。 - 复制构造函数被删除,
std::packaged_task
是只可移动的 - 移动构造函数,构造一个具有共享状态和任务的
std::packaged_task
,该状态和任务原先由rhs
拥有,将rhs
置于无共享状态和已移动的任务状态。
析构函数:
放弃共享状态并销毁存储的任务对象。如果共享状态在准备就绪之前被放弃,则会存储一个 std::future_error 异常,错误代码为 std::future_errc::broken_promise。
复制运算符:
只有移动赋值运算符。移动赋值运算符:释放共享状态(如果有),销毁之前持有的任务,并将共享状态和 rhs 拥有的任务移入 *this。
valid();
检查是否有一个共享状态,即是否包含一个有效的任务。
swap():
将"*this"和"other"的共享状态和存储的任务进行交换。
Getting the result:
get_future:
用于获取一个 std::future
对象,该对象与当前 std::packaged_task
共享相同的共享状态。get_future()
函数只能调用一次.
Execution:
operator():
执行存储中的任务。任务的返回值或抛出的任何异常都会存储在共享状态中。
make_ready_at_thread_exit:
共享状态只有在当前线程退出并且所有具有线程局部存储期的对象被销毁后,才会准备就绪。
reset():
重置状态,放弃之前的执行结果。构建新的共享状态。
std::promise<>
类模板 std::promise
提供了一种存储值或异常的机制,这些值或异常可以通过由 std::promise
对象创建的 std::future
对象在异步方式下获取。std::promise
对象只能使用一次,因此不可被复制。
每个 promise 关联一个共享状态, 它可以对共享状态执行三种操作:
- 准备就绪:
promise 将结果或异常存储在共享状态中。标记状态为就绪,并解除任何等待与共享状态相关联的 future 的线程的阻塞。 - 释放:
promise 放弃对共享状态的引用。如果这是最后一个对共享状态的引用,那么共享状态将被销毁。 - 放弃:
promise 将类型为 std::future_error,错误代码为 std::future_errc::broken_promise 的异常存储在共享状态中,使共享状态变为就绪状态,然后释放它。
构造函数:(拷贝构造函数被删除)
- 默认构造函数。使用空的共享状态构造promise。
- 移动构造函数。使用移动语义构造 promise,使用其他 promise 的共享状态。构造完成后,其他 promise 不再具有共享状态。
析构函数:
- 如果共享状态已就绪,则释放共享状态。
- 如果共享状态未就绪,则存储一个 std::future_error 类型的异常对象(错误条件为 std::future_errc::broken_promise),使共享状态就绪并释放它。
赋值运算符:(没有拷贝运算符)
移动赋值操作符。首先,放弃共享状态,然后通过执行 std::promise(std::move(other)).swap(*this) 赋值给 other 的共享状态。
Getting the result:
get_future:
返回与 *this 相同共享状态相关联的std::future。
set_value; set_value_at_thread_exit;
原子式地将值存储到共享状态中,并使状态就绪。如果没有共享状态或共享状态已存储值或异常,则会抛出异常。
前者是立即让共享状态就绪,后者是等当前线程退出时,所有具有线程本地存储期限的变量都已销毁,状态才会就绪。
set_exception; set_exception_at_thread_exit;
除了存储的是异常,其余和set_value; set_value_at_thread_exit;完全一致。
set_value: 保存值
set_exception: 保存异常