互斥锁和条件变量 c++11

本文详细介绍了C++11中互斥锁(mutex)的相关概念,包括互斥类型、锁对象如lock_guard和unique_lock,以及如何进行锁定、尝试锁定和解锁操作。同时,文章涵盖了递归互斥锁(recursive_mutex)和定时互斥锁(timed_mutex)的使用。此外,还讨论了条件变量(condition_variable)的用法,如wait、notify_one和notify_all等函数,以及如何与不同类型的锁配合使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

互斥锁

mutex.h

允许代码关键部分并发执行的互斥(mutex),允许显式地避免数据竞争。

它包含互斥对象类型、锁类型和特定的函数:

 

互斥类型

是一种可锁定的类型(Lockable),用于保护对关键代码部分的访问。锁定互斥可以防止其他线程锁定它(排他访问),直到它被解锁。

mutex、recursive_mutex、timed_mutex、recursive_timed_mutex。

 

是管理互斥锁的对象,通过将互斥锁访问关联到它们自己的生命周期。

lock_guard, unique_lock。

 

函数:

同时锁定多个互斥锁(try_lock, lock),和直接防止某个特定函数的并发执行(call_once)。

 

相关类

Mutexes

mutex - Mutex class (class )

recursive_mutex - Recursive mutex class (class )

timed_mutex - Timed mutex class (class )

recursive_timed_mutex - Recursive timed mutex (class )

 

Locks

lock_guard - Lock guard (class template )

unique_lock - Unique lock (class template )

 

Other types

once_flag - Flag argument type for call_once (class )

adopt_lock_t - Type of adopt_lock (class )

defer_lock_t - Type of defer_lock (class )

try_to_lock_t - Type of try_to_lock (class )

 

相关函数

try_lock - Try to lock multiple mutexes (function template )

lock - Lock multiple mutexes (function template )

call_once - Call function once (public member function )

BasicLockable - Basic lockable type

基本可锁定类型是支持锁定和解锁的基本可锁定类型。

标准库定义了以下基本锁类型:

值m属于BasicLockable类型(如果以下表达式格式符合语法规则):

m.lock ()

效果: 阻塞,直到当前执行代理可以获得一个锁。如果抛出异常,那么当前执行代理不应该获得一个锁。

m.unlock ()

要求: 当前执行代理应锁定m。

效果: 释放当前执行代理持有的m上的锁。

抛出: 没有。

 

 

Lockable - Lockable type (mutex type)

可锁定类型(也称为互斥类型)是一种支持try_lock的基本可锁定类型。

标准库定义了以下可锁定类型:

值m属于可锁定类型(如果以下表达式格式符合语法规则):

m.lock ()

效果: 阻塞,直到当前执行代理可以获得一个锁。如果抛出异常,那么当前执行代理不应该获得一个锁。

 

m.unlock ()

要求: 当前执行代理应锁定m。

效果: 释放当前执行代理持有的m上的锁。

抛出: 没有。

 

m.try_lock ()

效果: 尝试不阻塞地获取当前执行代理的锁。如果抛出异常,那么当前执行代理不应该获得一个锁。

返回类型: bool,如果获得了锁,则返回:true,否则返回false。

 

TimedLockable - Timed lockable type

定时可锁定类型是支持try_lock_for和try_lock_until的可锁定类型。

标准库定义了以下TimedLockable类型:

timed_mutex.h ->  recursive_timed_mutex

 

值m属于TimedLockable类型(如果以下表达式格式符合语法规则):

m.lock ()

效果: 阻塞,直到当前执行代理可以获得一个锁。如果抛出异常,那么当前执行代理不应该获得一个锁。

 

m.unlock ()

要求: 当前执行代理应锁定m。

效果: 释放当前执行代理持有的m上的锁。

抛出: 没有。

 

m.try_lock ()

效果: 尝试不阻塞地获取当前执行代理的锁。如果抛出异常,那么当前执行代理不应该获得一个锁。

返回类型: bool,如果获得了锁,则返回:true,否则返回false。

 

m.try_lock_for (rel_time)

效果: 尝试在rel_time指定的相对超时内获取当前执行代理的锁。该函数不会在rel_time指定的超时时间内返回,除非它为当前执行代理获得了m上的锁。如果抛出异常,那么当前执行代理不应该获得一个锁。

返回类型:bool

如果获得了锁,则返回:true,否则返回false。

 

m.try_lock_until (abs_time)

效果:尝试在abs_time指定的绝对超时之前获取当前执行代理的锁。函数在abs_time指定的超时时间之前不会返回,除非它为当前执行代理获得了m的锁定。如果抛出异常,那么当前执行代理不应该获得一个锁。

返回类型:bool

如果获得了锁,则返回:true,否则返回false。

互斥类

class mutex;

互斥锁是一种可锁定的对象,它被设计用来在关键代码段需要独占访问时发出信号,以防止具有相同保护的其他线程并发执行和访问相同的内存位置。

 

互斥锁对象提供排他的所有权,不支持递归(即,线程不应该锁定它已经拥有的互斥锁)——请参阅recursive_mutex,另一个类有这个功能。

 

它是一个标准布局类。

成员类型:

native_handle_type由native_handle返回的类型(仅在库实现支持时定义)

 

成员函数:

构造函数:

default (1)

constexpr mutex() noexcept;

copy [deleted] (2)

mutex (const mutex&) = delete;

构造一个互斥对象。对象处于解锁状态。

不能复制/移动互斥对象(对于这种类型,复制构造函数和赋值操作符都被删除)。

互斥锁的构造本身并不是原子的:在构造时访问对象可能会引发数据竞争。

 

锁定互斥对象

void lock();

调用线程锁定互斥锁,必要时阻塞:

  • 如果互斥锁(mutex)当前没有被任何线程锁定,那么调用它的线程就会锁定它(从此时开始,直到它的成员unlock被调用,这个线程就拥有互斥锁)。
  • 如果互斥锁(mutex)当前被另一个线程锁定,则调用线程的执行将被阻塞,直到被另一个线程解锁(其他非锁定线程继续执行)。
  • 如果互斥锁(mutex)当前被调用该函数的同一线程锁定,它会产生死锁(行为未定义)。请参阅recursive_mutex了解允许来自同一个线程的多个锁的互斥锁类型。

 

在互斥对象上的所有锁定和解锁操作都遵循一个单一的总顺序,在同一个对象上的锁定操作和之前的解锁操作之间的所有可见效果都是同步的。

 

非成员函数锁允许同时锁定多个互斥对象,避免在多个线程以不同顺序锁定/解锁单个互斥对象时可能发生的死锁。

 

注意,不同并发锁的返回顺序是未指定的,而且不一定与它们被锁定的顺序相关(取决于系统和库实现)。

 

返回值:无返回值

 

互斥对象被修改为一个原子操作(不会导致数据竞争)。

 

异常:

基本保证:

  • 如果该成员函数抛出异常,互斥对象将保持有效状态。而且,发出抛出调用的线程永远不会获得锁。
  • 如果互斥锁已经被当前线程锁定,调用这个函数会导致死锁(未定义的行为):在某些库实现中,这会导致函数失败。

如果调用失败,抛出一个system_error异常:

根据库实现的不同,该成员函数还可能抛出异常以报告其他情况。

 

尝试锁定互斥对象

bool try_lock();

 

尝试锁定互斥对象,而不被阻塞:

  • 如果互斥锁(mutex)当前没有被任何线程锁定,那么调用它的线程就会锁定它(从此时开始,直到它的成员unlock被调用,这个线程就拥有互斥锁)。
  • 如果互斥锁(mutex)当前被另一个线程锁定,那么该函数将失败并返回false,不会阻塞(调用线程将继续执行)。
  • 如果互斥锁(mutex)当前被调用该函数的同一线程锁定,它会产生死锁(行为未定义)。请参阅recursive_mutex了解允许来自同一个线程的多个锁的互斥锁类型。

 

当没有其他线程锁定互斥锁时,这个函数可能会假地失败,但在这种情况下重复调用一定会成功。

 

在互斥对象上的所有锁定和解锁操作都遵循一个单一的总顺序,在同一个对象上的锁定操作和之前的解锁操作之间的所有可见效果都是同步的。

 

返回值:如果函数成功锁定了线程的互斥锁,则为true。否则错误。

 

互斥对象被作为原子操作访问/修改(不会导致数据竞争)。

 

异常:

如果互斥锁当前没有被调用线程锁定,这个函数永远不会抛出异常(no-throw保证)。

否则,它会导致未定义的行为。

 

解锁互斥

void unlock();

解锁互斥,释放对互斥的所有权。

如果其他线程正在阻塞试图锁定这个互斥锁(mutex),其中一个线程将获得该互斥锁的所有权并继续执行。

 

在互斥对象上的所有锁定和解锁操作都遵循一个单一的总顺序,在同一个对象上的锁定操作和之前的解锁操作之间的所有可见效果都是同步的。

 

如果互斥锁当前没有被调用线程锁定,它会导致未定义的行为。

 

互斥对象被修改为原子操作(不会导致数据竞争)。

 

异常:

如果互斥锁当前被调用线程锁定,这个函数永远不会抛出异常(no-throw保证)。

否则,它会导致未定义的行为。

 

获取本地处理

native_handle_type native_handle();

只有库实现支持时,这个成员函数才会出现在互斥类中。

如果存在,则返回一个用于访问与对象关联的特定于实现的信息的值。

 

递归互斥类

class recursive_mutex;

递归互斥锁是一个可锁定的对象,就像互斥锁一样,但是允许同一个线程获得对互斥锁对象的多级所有权。

这允许锁(或try-lock)已经锁定的线程的互斥对象,获取一个新的水平的互斥对象:拥有互斥对象会保持锁拥有调用线程,直到其成员解锁多少次就这种程度的所有权。

它保证是一个标准布局类。

 

template <class T> struct is_standard_layout;

标识T是否为标准布局类型的Trait类。

标准布局类型是一种具有简单线性数据结构和访问控制的类型,它可以很容易地用于与用其他编程语言(如C语言)编写的代码通信,无论是否符合cv要求。对于标量类型、标准布局类和任何此类类型的数组都是如此。

标准布局类是这样一个类(用class、struct或union定义):

  • 没有虚函数和虚基类。
  • 对其所有非静态数据成员具有相同的访问控制(私有、受保护、公共)。
  • 要么在大多数派生类中没有非静态数据成员,最多只有一个基类具有非静态数据成员,要么没有基类具有非静态数据成员。
  • 它的基类(如果有的话)本身也是一个标准布局类。而且,
  • 没有与其第一个非静态数据成员相同类型的基类。
  • 这个类从integral_constant继承为true_type或false类型,这取决于T是否是普通类型。

 

成员类型:

成员函数:

构造函数:

default (1)

constexpr recursive_mutex() noexcept;

copy [deleted] (2)

recursive_mutex (const recursive_mutex&) = delete;

构建recursive_mutex,构造recursive_mutex对象。

对象处于解锁状态。

recursive_mutex对象不能被复制/移动(对于这种类型,复制构造函数和赋值操作符都被删除)。

recursive_mutex的构造本身并不是原子的:在构造过程中访问对象可能会引发数据竞争。

 

Lock recursive mutex

void lock();

调用线程锁定recursive_mutex,必要时阻塞:

  • 如果recursive_mutex当前没有被任何线程锁定,调用它的线程就会锁定它(从此时开始,直到它的成员unlock被调用,线程就拥有这个互斥锁)。
  • 如果recursive_mutex当前被另一个线程锁定,则调用线程的执行将被阻塞,直到另一个线程完全解锁(其他非锁定线程继续执行)。
  • 如果recursive_mutex当前被调用该函数的同一线程锁定,那么该线程将获得对recursive_mutex的新的所有权级别。完全解锁recursive_mutex需要额外调用member unlock。

 

recursive_mutex上的所有锁定和解锁操作都遵循一个单一的总顺序,在同一个对象上,锁定操作和之前的解锁操作之间的所有可见效果都是同步的。

 

注意,不同并发锁的返回顺序是未指定的,而且不一定与它们被锁定的顺序相关(取决于系统和库实现)。

 

recursive_mutex对象被修改为一个原子操作(不会导致数据竞争)。

 

异常:

基本保证:

如果该成员函数抛出异常,recursive_mutex对象将保持有效状态。而且,发出抛出调用的线程永远不会获得锁。

如果调用失败,抛出一个system_error异常:

根据库实现的不同,该成员函数还可能抛出异常以报告其他情况。

 

尝试锁定互斥对象

bool try_lock() noexcept;

试图锁定recursive_mutex,而没有阻塞:

  • 如果recursive_mutex当前没有被任何线程锁定,那么调用它的线程就会锁定它(从此时开始,直到它的成员unlock被调用为止,线程拥有recursive_mutex)。
  • 如果recursive_mutex当前被另一个线程锁定,函数将失败并返回false,不会阻塞(调用线程继续执行)。
  • 如果recursive_mutex当前被调用该函数的同一线程锁定,那么该线程将获得对recursive_mutex的新的所有权级别。完全解锁recursive_mutex需要额外调用member unlock。

 

当没有其他线程锁定recursive_mutex时,这个函数可能会假地失败,但在这种情况下重复调用一定会成功。

 

recursive_mutex上的所有锁定和解锁操作都遵循一个单一的总顺序,在同一个对象上,锁定操作和之前的解锁操作之间的所有可见效果都是同步的。

 

返回值:如果函数成功锁定了线程的递归互斥锁,则为true。否则错误。

 

解锁互斥

void unlock();

解锁recursive_mutex,释放对它的一级所有权。

如果调用线程对recursive_mutex只有一个级别的所有权,那么它是完全解锁的:如果其他线程正在阻塞试图锁定这个recursive_mutex,其中一个线程将获得它的所有权并继续执行。

recursive_mutex上的所有锁定和解锁操作都遵循一个单一的总顺序,在同一个对象上,锁定操作和之前的解锁操作之间的所有可见效果都是同步的。

如果recursive_mutex当前没有被调用线程锁定,它会导致未定义的行为。

 

异常:

如果recursive_mutex当前被调用线程锁定,这个函数永远不会抛出异常(no-throw保证)。

否则,它会导致未定义的行为。

 

获取本地处理

native_handle_type native_handle();

只有库实现支持时,这个成员函数才会出现在recursive_mutex类中。

如果存在,则返回一个用于访问与对象关联的特定于实现的信息的值。

 

定时互斥类

class timed_mutex;

定时互斥锁是一种时间锁定对象,与常规的互斥锁一样,它被设计用来在关键代码段需要排他访问时发出信号,但是还支持定时尝试锁定请求。

因此,timed_mutex还有两个额外的成员:try_lock_for和try_lock_until。

它保证是一个标准布局类。

成员类型:

成员函数:

构造函数:

default (1)

constexpr timed_mutex() noexcept;

copy [deleted] (2)

timed_mutex (const timed_mutex&) = delete;

构建timed_mutex,构造一个timed_mutex对象。

对象处于解锁状态。

不能复制/移动timed_mutex对象(对于这种类型,复制构造函数和赋值操作符都被删除)。

 

timed_mutex的构造本身并不是原子的:在构造时访问对象可能会引发数据竞争。

 

锁互斥锁

void lock();

调用线程锁定timed_mutex,必要时阻塞(它的行为和互斥锁完全一样):

  • 如果timed_mutex当前没有被任何线程锁定,那么调用它的线程就会锁定它(从此时开始,直到它的成员unlock被调用为止,这个线程拥有timed_mutex)。
  • 如果timed_mutex当前被另一个线程锁定,则调用线程的执行将被阻塞,直到被另一个线程解锁(其他非锁定线程继续执行)。
  • 如果timed_mutex当前被调用该函数的同一线程锁定,它会产生死锁(行为未定义)。请参阅recursive_timed_mutex了解允许来自同一个线程的多个锁的定时互斥锁类型。

在timed_mutex上的所有加锁和解锁操作都遵循一个单一的总顺序,所有可见的效果都在同一个对象上的加锁操作和之前的解锁操作之间同步。

注意,不同并发锁的返回顺序是未指定的,而且不一定与它们被锁定的顺序相关(取决于系统和库实现)。

 

timed_mutex对象被修改为一个原子操作(不会导致数据竞争)。

 

异常:

基本保证:如果该成员函数抛出异常,则timed_mutex对象将保持有效状态。而且,发出抛出调用的线程永远不会获得锁。

如果timed_mutex已经被当前线程锁定,调用这个函数会导致死锁(未定义的行为):在某些库实现中,这会导致函数失败。

 

如果调用失败,抛出一个system_error异常:

根据库实现的不同,该成员函数还可能抛出异常以报告其他情况。

 

尝试锁定定时互斥量

bool try_lock();

尝试锁定timed_互斥锁,而不阻塞(它的行为和互斥锁完全一样):

  • 如果timed_mutex当前没有被任何线程锁定,那么调用它的线程就会锁定它(从此时开始,直到它的成员unlock被调用为止,这个线程拥有timed_mutex)。
  • 如果timed_mutex当前被另一个线程锁定,那么该函数将失败并返回false,不会阻塞(调用线程继续执行)。
  • 如果timed_mutex当前被调用该函数的同一线程锁定,它会产生死锁(行为未定义)。请参阅recursive_timed_mutex了解允许来自同一个线程的多个锁的定时互斥锁类型。

 

当没有其他线程锁定timed_mutex时,这个函数可能会假地失败,但在这种情况下重复调用一定会成功。

 

在timed_mutex上的所有加锁和解锁操作都遵循一个单一的总顺序,所有可见的效果都在同一个对象上的加锁操作和之前的解锁操作之间同步。

 

尝试锁定指定时间跨度

template <class Rep, class Period>

bool try_lock_for (const chrono::duration<Rep,Period>& rel_time);

试图锁定timed_mutex,最多阻塞rel_time:

  • 如果timed_mutex当前没有被任何线程锁定,那么调用它的线程就会锁定它(从此时开始,直到它的成员unlock被调用为止,这个线程拥有timed_mutex)。
  • 如果timed_mutex当前被另一个线程锁定,那么调用线程的执行就会被阻塞,直到解锁或rel_time过去,无论哪个先发生(与此同时,其他非锁定线程继续执行)。
  • 如果timed_mutex当前被调用该函数的同一线程锁定,它会产生死锁(行为未定义)。请参阅recursive_timed_mutex了解允许来自同一个线程的多个锁的定时互斥锁类型。

在timed_mutex上的所有加锁和解锁操作都遵循一个单一的总顺序,所有可见的效果都在同一个对象上的加锁操作和之前的解锁操作之间同步。

 

参数:

rel_time - 线程阻塞等待获得锁的最大时间跨度。

duration - 是一个表示特定相对时间的对象。

 

返回值:如果函数成功锁定了线程的timed_mutex,则为true。否则错误。

 

timed_mutex对象是作为原子操作访问/修改的(不会导致数据竞争)。

 

异常:

如果timed_mutex当前被调用线程锁定,它会导致未定义的行为。

否则,它提供与duration对象上的操作相同级别的保证(对于<chrono>中的类型实例化,这是一个no-throw保证)。

 

试着锁定到指定时间点

template <class Clock, class Duration>
bool try_lock_until (const chrono::time_point<Clock,Duration>& abs_time);

尝试锁定timed_mutex,最多阻塞到abs_time:

  • 如果timed_mutex当前没有被任何线程锁定,那么调用它的线程就会锁定它(从此时开始,直到它的成员unlock被调用为止,这个线程拥有timed_mutex)。
  • 如果timed_mutex当前被另一个线程锁定,则调用线程的执行将被阻塞,直到被解锁或直到abs_time,无论哪个先发生(与此同时,其他非锁定线程继续执行)。
  • 如果timed_mutex当前被调用该函数的同一线程锁定,它会产生死锁(行为未定义)。请参阅recursive_timed_mutex了解允许来自同一个线程的多个锁的定时互斥锁类型。

在timed_mutex上的所有加锁和解锁操作都遵循一个单一的总顺序,所有可见的效果都在同一个对象上的加锁操作和之前的解锁操作之间同步。

 

参数:

abs_time - 线程将停止阻塞,放弃锁定尝试的时间点。

time_point - 是一个表示特定绝对时间的对象。

返回值:如果函数成功锁定了线程的timed_mutex,则为true。否则错误。

 

timed_mutex对象是作为原子操作访问/修改的(不会导致数据竞争)。

 

如果timed_mutex当前被调用线程锁定,它会导致未定义的行为。

否则,它提供了与duration对象上的操作相同级别的保证(对于<chrono>中的时钟使用的类型,这是一个无抛出保证)。

 

定时的互斥锁进行解锁

void unlock();

解锁timed_互斥锁,释放对它的所有权(与互斥锁一样)。

如果其他线程在试图锁定同一个timed_mutex时阻塞,其中一个线程会获得它的所有权并继续执行。

 

在timed_mutex上的所有加锁和解锁操作都遵循一个单一的总顺序,所有可见的效果都在同一个对象上的加锁操作和之前的解锁操作之间同步。

 

如果调用线程当前没有锁定timed_mutex,则会导致未定义的行为。

 

timed_mutex对象被修改为一个原子操作(不会导致数据竞争)。

 

异常:

如果timed_mutex当前被调用线程锁定,这个函数永远不会抛出异常(no-throw保证)。

否则,它会导致未定义的行为。

 

获取本地处理

native_handle_type native_handle();

只有库实现支持时,这个成员函数才会出现在timed_mutex类中。

如果存在,则返回一个用于访问与对象关联的特定于实现的信息的值。

 

递归的互斥

class recursive_timed_mutex;

递归定时互斥锁将recursive_mutex和timed_mutex的特性组合到一个类中:它支持通过单个线程获取多个锁级别,也支持定时尝试锁定请求。

它保证是一个标准布局类。

成员类型:

成员函数:

构造函数:

default (1)

constexpr recursive_timed_mutex() noexcept;

copy [deleted] (2)

recursive_timed_mutex (const timed_mutex&) = delete;

构建recursive_timed_mutex,构造recursive_timed_mutex对象。

对象处于解锁状态。

recursive_timed_mutex对象不能被复制/移动(对于这种类型,复制构造函数和赋值操作符都被删除)。

 

锁定递归计时互斥锁

void lock();

调用线程锁定recursive_timed_mutex,必要时阻塞(它的行为与recursive_mutex完全相同):

  • 如果recursive_timed_mutex当前没有被任何线程锁定,那么调用它的线程就会锁定它(从此时开始,直到它的成员unlock被调用为止,线程拥有这个互斥锁)。
  • 如果recursive_timed_mutex当前被另一个线程锁定,则调用线程的执行将被阻塞,直到另一个线程完全解锁(其他非锁定线程继续执行)。
  • 如果recursive_timed_mutex当前被调用该函数的同一线程锁定,则该线程对recursive_timed_mutex获得了新的所有权级别。完全解锁recursive_timed_mutex需要额外调用成员解锁。

recursive_timed_mutex上的所有锁定和解锁操作都遵循一个单一的总顺序,在同一个对象上的锁定操作和之前的解锁操作之间同步所有可见效果。

注意,不同并发锁的返回顺序是未指定的,而且不一定与它们被锁定的顺序相关(取决于系统和库实现)。

 

 

异常:

基本保证:如果这个成员函数抛出异常,recursive_timed_mutex对象将保持有效状态。而且,发出抛出调用的线程永远不会获得锁。

如果调用失败,抛出一个system_error异常:

根据库实现的不同,该成员函数还可能抛出异常以报告其他情况。

 

尝试锁递归计时互斥锁

bool try_lock() noexcept;

尝试锁定recursive_timed_mutex,而不阻塞(它的行为与recursive_mutex完全相同):

  • 如果recursive_timed_mutex当前没有被任何线程锁定,则调用线程锁定它(从此时开始,直到调用它的成员unlock为止,线程拥有recursive_timed_mutex)。
  • 如果recursive_timed_mutex当前被另一个线程锁定,该函数将失败并返回false,不会阻塞(调用线程继续执行)。
  • 如果recursive_timed_mutex当前被调用该函数的同一线程锁定,则该线程对recursive_timed_mutex获得了新的所有权级别。完全解锁recursive_timed_mutex需要额外调用成员解锁。

当没有其他线程锁定recursive_timed_mutex时,这个函数可能会假地失败,但在这种情况下重复调用一定会成功。

recursive_timed_mutex上的所有锁定和解锁操作都遵循一个单一的总顺序,在同一个对象上的锁定操作和之前的解锁操作之间同步所有可见效果。

 

尝试锁定时间跨度

template <class Rep, class Period>
  bool try_lock_for (const chrono::duration<Rep,Period>& rel_time);

试图锁定recursive_timed_mutex,最多阻塞rel_time:

  • 如果recursive_timed_mutex当前没有被任何线程锁定,则调用线程锁定它(从此时开始,直到调用它的成员unlock为止,线程拥有recursive_timed_mutex)。
  • 如果recursive_timed_mutex当前被另一个线程锁定,则调用线程的执行将被阻塞,直到解锁或一旦rel_time经过,无论哪个先发生(与此同时,其他非锁定线程继续执行)。
  • 如果recursive_timed_mutex当前被调用该函数的同一线程锁定,则该线程对recursive_timed_mutex获得了新的所有权级别。完全解锁recursive_timed_mutex需要额外调用成员解锁。

recursive_timed_mutex上的所有锁定和解锁操作都遵循一个单一的总顺序,在同一个对象上的锁定操作和之前的解锁操作之间同步所有可见效果。

 

参数:

rel_time - 线程阻塞等待获得锁的最大时间跨度。

duration - 是一个表示特定相对时间的对象。

 

试着锁定到时间点

template <class Clock, class Duration>
  bool try_lock_until (const chrono::time_point<Clock,Duration>& abs_time);

试图锁定recursive_timed_mutex,最多阻塞到abs_time:

  • 如果recursive_timed_mutex当前没有被任何线程锁定,则调用线程锁定它(从此时开始,直到调用它的成员unlock为止,线程拥有recursive_timed_mutex)。
  • 如果recursive_timed_mutex当前被另一个线程锁定,则调用线程的执行将被阻塞,直到解锁或abs_time(无论哪个先发生)(与此同时,其他非锁定线程继续执行)。
  • 如果recursive_timed_mutex当前被调用该函数的同一线程锁定,则该线程对recursive_timed_mutex获得了新的所有权级别。完全解锁recursive_timed_mutex需要额外调用成员解锁。

recursive_timed_mutex上的所有锁定和解锁操作都遵循一个单一的总顺序,在同一个对象上的锁定操作和之前的解锁操作之间同步所有可见效果。

参数:

abs_time - 线程将停止阻塞,放弃锁定尝试的时间点。

time_point - 是一个表示特定绝对时间的对象。

 

定时的互斥锁进行解锁

void unlock();

解锁recursive_timed_互斥锁,释放对它的一级所有权(它的行为与recursive_mutex一样)。

如果调用线程对recursive_timed_mutex只有一个级别的所有权,那么它是完全解锁的:如果其他线程正在阻塞试图锁定同一个recursive_timed_mutex,那么其中一个线程将获得对它的所有权并继续执行。

recursive_timed_mutex上的所有锁定和解锁操作都遵循一个单一的总顺序,在同一个对象上的锁定操作和之前的解锁操作之间同步所有可见效果。

如果recursive_timed_mutex当前没有被调用线程锁定,它会导致未定义的行为。

 

获取本地处理

native_handle_type native_handle();

只有库实现支持时,这个成员函数才会出现在timed_mutex类中。

如果存在,则返回一个用于访问与对象关联的特定于实现的信息的值。

 

锁保护

 class lock_guard;

template <class Mutex> class lock_guard;

lock guard是一个通过保持互斥对象始终处于锁定状态来管理互斥对象的对象。

在构造时,互斥对象被调用线程锁定,在销毁时,互斥对象被解锁。它是最简单的锁,特别适合作为具有自动持续时间的对象,一直持续到其上下文结束。通过这种方式,它可以保证在抛出异常时正确地解锁互斥对象。

但是请注意,lock_guard对象不会以任何方式管理互斥对象的生命周期:互斥对象的持续时间至少应该延长到锁定它的lock_guard被销毁为止。

 

模板参数

互斥锁,mutex-like类型。

它应该是一种基本的可锁定类型,比如互斥锁(要求见BasicLockable)。

成员类型:

成员函数:

构造函数:

locking (1)

explicit lock_guard (mutex_type& m);

adopting (2)

lock_guard (mutex_type& m, adopt_lock_t tag);

copy [deleted](3)

lock_guard (const lock_guard&) = delete;

构造一个lock_guard对象,使m处于锁定状态。

(1)锁定初始化

对象管理m,并锁定它(通过调用m.lock())。

(2)采用初始化

这个对象管理m,它是一个互斥对象,当前被构造线程锁定。

(3)复制建设

删除(lock_guard对象不能复制/移动)。

对象保持m锁定,并维护对m的引用,该引用用于在销毁时解锁m(通过调用其成员unlock)。

 

参数:

m - 由lock_guard对象管理的互斥对象。

mutex_type 是lock_guard的模板参数(拥有的互斥对象的类型)。

tag -  这个tag参数仅用于选择特定的构造函数(这些类型的值没有状态)。

它是以下值之一:

adopt_lock_t是adopt_lock对象的类型。

 

参数m可以被访问或修改(作为一个原子操作,不会导致数据竞争)。

对象保留对m的引用,在销毁时对其进行修改。

 

异常:

对于锁定版本(1),如果m当前没有被调用线程锁定,构造函数永远不会抛出异常(no-throw保证)。

对于采用的版本(2),如果m当前被调用线程锁定,构造函数永远不会抛出异常(no-throw保证)。

否则,它会导致未定义的行为。

 

Destroy lock_guard(解锁互斥锁)

~lock_guard();

销毁lock_guard对象。

在此之前,析构函数调用它管理的互斥对象的unlock成员。

注意,这不会破坏托管互斥对象。

 

被管理的互斥对象被访问和修改(作为一个原子操作,不会导致数据竞争)。

 

Unique Lock

class unique_lock;

template <class Mutex> class unique_lock;

唯一锁是一个对象,它管理一个互斥对象,该互斥对象在锁定和解锁两种状态下拥有唯一的所有权。

在构造时(或通过移动赋值),对象获得一个互斥对象,对其进行锁定和解锁操作。

该对象支持两种状态:锁定状态和未锁定状态。

这个类保证了销毁时的解锁状态(即使没有显式调用)。因此,作为具有自动持续时间的对象,它特别有用,因为它保证在抛出异常时正确解锁互斥对象。

但是请注意,unique_lock对象不会以任何方式管理互斥对象的生命周期:互斥对象的持续时间至少应该延长到管理它的unique_lock被销毁为止。

 

模板参数:

互斥锁,mutex-like类型。

它应该是一种基本的可锁定类型,比如互斥锁(要求见BasicLockable)。

成员类型:

成员函数:

构造函数:

default (1)

unique_lock() noexcept;

locking (2)

explicit unique_lock (mutex_type& m);

try-locking (3)

unique_lock (mutex_type& m, try_to_lock_t tag);

deferred (4)

unique_lock (mutex_type& m, defer_lock_t tag) noexcept;

adopting (5)

unique_lock (mutex_type& m, adopt_lock_t tag);

locking for (6)

template <class Rep, class Period>
unique_lock (mutex_type& m, const chrono::duration<Rep,Period>& rel_time);

locking until (7)

template <class Clock, class Duration>
unique_lock (mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time);

copy [deleted] (8)

unique_lock (const unique_lock&) = delete;

move (9)

unique_lock (unique_lock&& x);

构建unique_lock

构造一个unique_lock:

(1)默认构造函数

该对象不管理互斥对象。

(2)锁定初始化(locking initialization)

这个对象管理m,并通过调用m.lock()来锁定它(阻塞,如果需要的话)。

(3) try-locking初始化(try-locking initialization)

该对象管理m,并试图通过调用m.try_lock()来锁定它(不阻塞)。

(4)延迟初始化(deferred initialization)

对象管理m而不锁定它。m必须是一个互斥对象,并且当前没有被构造线程锁定。

(5)采用初始化(adopting initialization)

这个对象管理m,它是一个互斥对象,目前由构造线程锁定(对象获得了锁的所有权)。

(6)锁定指定时间(locking for duration)

对象管理m,并试图通过调用m.try_lock_for(rel_time)在rel_time期间锁定它。

(7)锁定到指定时间点(locking until time point)

对象管理m,并试图在abs_time之前通过调用m.try_lock_until(abs_time)锁定它。

(8)拷贝构造函数[删除](copy construction [deleted])

unique_lock对象不能被复制(复制构造函数被删除)。

(9)移动move construction

对象获得由x管理的互斥锁,包括它的当前拥有状态。x - 保持与默认构造的状态相同(指没有互斥对象)。

 

对象通过保存对互斥对象的引用和是否拥有该互斥对象上的锁的信息来管理互斥对象的状态。

由(2)和(5)构造的对象总是拥有互斥对象的锁。拥有(1)和(4)的人从来不拥有锁。对于(3)、(6)和(7),如果锁定尝试成功,它们就拥有一个锁。

 

参数:

m - 由unique_lock对象管理的互斥对象。

mutex_type是unique_lock的模板参数(托管互斥对象的类型)。

tag - 这个tag参数仅用于选择特定的构造函数(这些类型的值没有状态)。

它是以下值之一:

try_to_lock_t、defer_lock_t和adopt_lock_t分别是对象try_to_lock、defer_lock和adopt_lock的类型。

 

rel_time - 线程阻塞等待获得锁的最大时间跨度。如果耗尽,则初始化对象时不拥有锁。

duration - 是一个表示特定相对时间的对象。

abs_time - 线程停止阻塞等待获得锁的时间点。如果到达,则初始化对象时不拥有锁。

time_point - 是一个表示特定绝对时间的对象。

x - 另一个unique_lock对象。

 

参数m可以被访问或修改(作为一个原子操作,不会导致数据竞争)。

对象保留对m的引用,可以通过它的操作访问或修改m。

move构造函数(9)修改了x。

 

异常:

如果互斥对象支持在m当前状态下应用的操作(如果有的话),构造函数永远不会抛出异常(no-throw保证)。

否则,它会导致未定义的行为。

 

析构函数:

~unique_lock();

摧毁unique_lock,销毁unique_lock对象。

如果对象当前拥有管理互斥对象上的锁,则在销毁对象之前调用其unlock成员。

注意,托管互斥对象本身不会被销毁。

 

锁定互斥对象

void lock();

调用托管互斥对象的成员锁。

对已经被其他线程锁定的互斥对象调用lock会导致当前线程阻塞(等待),直到它能够拥有对它的锁。

当函数返回时,对象拥有互斥锁。

如果对lock的调用失败,则抛出system_error异常。

 

修改了unique_lock对象。

被管理的互斥对象被访问和修改(作为一个原子操作,不会导致数据竞争)。

 

异常:

基本保证:如果这个成员函数抛出异常,unique_lock对象将保持有效状态。

如果调用失败,抛出一个system_error异常:

如果对托管互斥对象的锁定调用失败,以及库实现使用这种机制报告的任何其他条件,也会抛出异常。

 

尝试锁定互斥对象

bool try_lock();

调用管理互斥对象的成员try_lock,并使用返回值设置所属状态。

如果在调用之前所属状态已经为true,或者如果对象当前没有管理互斥对象,函数将抛出一个system_error异常。

返回值:如果函数锁定管理的互斥对象成功,则为“true”。否则错误。

 

访问/修改unique_lock对象。

被管理的互斥对象被作为原子操作访问/修改(不会导致数据竞争)。

 

异常:

基本保证:如果这个成员函数抛出异常,unique_lock对象将保持有效状态。

如果调用失败,抛出一个system_error异常:

如果对托管互斥对象的try_lock调用失败,以及库实现使用这种机制报告的任何其他条件,也会抛出异常。

 

template <class Rep, class Period>
  bool try_lock_for (const chrono::duration<Rep,Period>& rel_time);

 

template <class Clock, class Duration>
  bool try_lock_until (const chrono::time_point<Clock,Duration>& abs_time);

 

void unlock();

 

交换独立锁

void swap (unique_lock& x) noexcept;

与x交换内容,包括被管理的互斥对象和它们当前的拥有状态。

 

释放互斥锁

mutex_type* release() noexcept;

返回一个指向托管互斥对象的指针,释放对它的所有权。

调用之后,unique_lock对象将不再管理任何互斥对象(即,它将保持与if默认构造的相同状态)。

注意,这个函数不会锁定或解锁返回的互斥对象。

 

 

Observers

拥有锁

bool owns_lock() const noexcept;

返回对象是否拥有锁。

如果管理的互斥对象被unique_lock对象锁定(或采用),并且自那以后还没有被解锁或释放,则此值为真。

在所有其他情况下,它是假的。

这是unique_lock::operator bool的别名。

 

返回值:true表示该对象拥有管理互斥对象上的锁。否则错误。

 

explicit operator bool() const noexcept;

这是unique_lock::owns_lock的别名。

 

获得互斥锁

mutex_type* mutex() const noexcept;

返回一个指向托管互斥对象的指针。

注意unique_lock不会释放被管理的互斥对象的所有权:也就是说,如果它拥有一个互斥对象上的锁,它仍然会在某些时候(比如当它被销毁的时候)为其解锁。

要获取和获得托管互斥对象的所有权,请参见unique_lock::release。

 

template <class Mutex>
void swap (unique_lock<Mutex>& x, unique_lock<Mutex>& y) noexcept;

 

struct once_flag;

call_once的标志参数类型

该类型的对象用作call_once的实参。

在不同的线程中对call_once的不同调用使用同一个对象,如果并发调用的话,会导致单个执行。

它是一个不可复制、不可移动、默认可构造的类,在<mutex>中声明,并使用以下原型:

struct once_flag {
 
constexpr once_flag() noexcept;
  once_flag (
const once_flag&) = delete;
  once_flag&
operator= (const once_flag&) = delete;
};

 

类型adopt_lock

struct adopt_lock_t {};

这是一个用作adopt_lock类型的空类。

将adopt_lock传递给unique_lock或lock_guard的构造函数,使对象不锁定互斥对象,而是假设它已经被当前线程锁定。

 

类型defer_lock

struct defer_lock_t {};

这是一个空类,用作defer_lock的类型。

将defer_lock传递给unique_lock的构造函数,使其不会在构造互斥对象时自动锁定互斥对象,将对象初始化为不拥有锁。

 

类型try_to_lock

struct try_to_lock_t {};

这是一个空类,用作try_to_lock的类型。

将try_to_lock传递给unique_lock的构造函数,使得它可以通过调用其try_lock成员而不是lock来尝试锁定互斥对象。

 

条件变量

condition_variable.h

声明条件变量类型的头文件:

 

条件变量状态

enum class cv_status;

指示函数是否因超时而返回的类型。

这种类型的值由condition_variable和condition_variable_any的成员wait_for和wait_until返回。

定义为:

enum class cv_status { no_timeout, timeout };

条件变量

class condition_variable;

条件变量是能够阻塞调用线程的对象,直到被通知恢复。

当它的一个等待函数被调用时,它使用unique_lock(通过互斥锁)来锁定线程。该线程保持阻塞状态,直到被另一个线程唤醒,该线程调用同一个condition_variable对象上的通知函数。

condition_variable类型的对象总是使用unique_lock<mutex>来等待:对于任何类型的可锁定类型,请参阅condition_variable_any

 

成员函数:

构造函数:

default (1)

condition_variable();

copy [deleted] (2)

condition_variable (const condition_variable&) = delete;

构建condition_variable

构造一个condition_variable对象。

不能复制/移动condition_variable对象(对于这种类型,复制构造函数和赋值操作符都被删除)。

 

异常:

强保证:在抛出异常的情况下不会产生效果。

分配内存失败可以通过抛出bad_alloc来表示。

如果构造失败,抛出一个system_error异常:

根据库实现的不同,它可能在其他情况下抛出异常。

 

析构函数:

摧毁condition_variable,销毁condition_variable对象。

任何在此条件下阻塞的线程都应该在调用此析构函数之前得到通知。在这个析构函数被调用后,没有线程应该启动一个等待。

 

如果在这种情况下没有线程被阻塞,它就不会抛出异常(no-throw保证)。否则,将产生未定义的行为。

 

等到通知

unconditional (1)

void wait (unique_lock<mutex>& lck);

predicate (2)

template <class Predicate>
  void wait (unique_lock<mutex>& lck, Predicate pred);

当前线程(应该已经锁定了lck的互斥锁)的执行会被阻塞,直到收到通知。

 

在阻塞线程的那一刻,函数自动调用lock .unlock(),允许其他锁定的线程继续。

 

一旦得到通知(由其他线程显式地发出),该函数将解除阻塞并调用lck.lock(),使lck处于与函数被调用时相同的状态。然后函数返回(注意最后的互斥锁在返回之前可能会再次阻塞线程)。

通常,函数会被另一个线程中的notify_one或notify_all成员的调用唤醒。但某些实现可能会在不调用任何这些函数的情况下产生虚假的唤醒调用。因此,使用该功能的用户应确保满足其恢复的条件。

如果指定了pred(2),函数只有在pred返回false时才会阻塞,并且只有当它变为true时,通知才能解除线程阻塞(这对于检查虚假的唤醒调用特别有用)。这个版本(2)的行为就像实现了:

while (!pred()) wait(lck);

 

lck - 一个unique_lock对象,它的互斥对象目前被这个线程锁定。所有对该对象的wait成员函数的并发调用都应该使用相同的基础互斥对象(由lck.mutex()返回)。

pred - 一个可调用的对象或函数,不接受参数并返回可作为bool值计算的值。这个函数会被反复调用,直到它的值为true。

 

该函数执行三个原子操作:

  • lck初始解锁,同时进入等待状态。
  • 等待状态的解除阻塞。
  • 返回前锁定lck。

对象上的原子操作按照单一的总顺序排列,这个函数中的三个原子操作与上面的相对顺序相同。

 

异常:

如果任何参数的值对这个函数无效(比如lck的互斥对象没有被调用线程锁定),它会导致未定义的行为。

 

等待超时或直到收到通知

unconditional (1)

template <class Rep, class Period>
  cv_status wait_for (unique_lock<mutex>& lck,
                      const chrono::duration<Rep,Period>& rel_time);

predicate (2)

template <class Rep, class Period, class Predicate>
       bool wait_for (unique_lock<mutex>& lck,
                      const chrono::duration<Rep,Period>& rel_time, Predicate pred);

 

当前线程的执行(它应该已经锁定了lck的互斥锁)在rel_time期间被阻塞,或者直到被通知(如果后者先发生)。

在阻塞线程的那一刻,函数自动调用lock .unlock(),允许其他锁定的线程继续。

一旦得到通知或rel_time被传递,函数将解除阻塞并调用lck.lock(),使lck处于与函数被调用时相同的状态。然后函数返回(注意最后的互斥锁在返回之前可能会再次阻塞线程)。

通常,函数会被另一个线程中的notify_one或notify_all成员的调用唤醒。但某些实现可能会在不调用任何这些函数的情况下产生虚假的唤醒调用。因此,使用该功能的用户应确保满足其恢复的条件。

如果指定了pred(2),函数只有在pred返回false时才会阻塞,并且只有当它变为true时,通知才能解除线程阻塞(这对于检查虚假的唤醒调用特别有用)。它的行为就像这样实现:

lck - 一个unique_lock对象,它的互斥对象目前被这个线程锁定。所有对该对象的wait成员函数的并发调用都应该使用相同的基础互斥对象(由lck.mutex()返回)。

 

参数:

rel_time - 线程阻塞等待通知的最大时间跨度。

duration - 是一个表示特定相对时间的对象。

pred - 一个可调用的对象或函数,不接受参数并返回可作为bool值计算的值。这个函数会被反复调用,直到它的值为true。

返回值:

如果函数因rel_time已通过而返回,则The unconditional version (1)返回cv_status::timeout,否则返回cv_status::no_timeout。

The predicate version(2)返回pred(),而不管超时是否被触发(尽管只有在被触发时才为false)。

 

等待通知或时间点

unconditional (1)

template <class Clock, class Duration>
  cv_status wait_until (unique_lock<mutex>& lck,
                        const chrono::time_point<Clock,Duration>& abs_time);

predicate (2)

template <class Clock, class Duration, class Predicate>
       bool wait_until (unique_lock<mutex>& lck,
                        const chrono::time_point<Clock,Duration>& abs_time,
                        Predicate pred);

当前线程(应该已经锁定了lck的互斥锁)的执行会被阻塞,直到收到通知或abs_time,以最先发生的情况为限。

在阻塞线程的那一刻,函数自动调用lock .unlock(),允许其他锁定的线程继续。

一旦得到通知或当它是abs_time时,函数解除阻塞并调用lck.lock(),使lck处于与函数被调用时相同的状态。然后函数返回(注意最后的互斥锁在返回之前可能会再次阻塞线程)。

通常,函数会被另一个线程中的notify_one或notify_all成员的调用唤醒。但某些实现可能会在不调用任何这些函数的情况下产生虚假的唤醒调用。因此,使用该功能的用户应确保满足其恢复的条件。

如果指定了pred(2),函数只有在pred返回false时才会阻塞,并且只有当它变为true时,通知才能解除线程阻塞(这对于检查虚假的唤醒调用特别有用)。它的行为就像这样实现:

while (!pred())
 
if ( wait_until(lck,abs_time) == cv_status::timeout)
   
return pred();
return true;

 

参数:

lck - 一个unique_lock对象,它的互斥对象目前被这个线程锁定。所有对该对象的wait成员函数的并发调用都应该使用相同的基础互斥对象(由lck.mutex()返回)。

abs_time - 线程停止阻塞并允许函数返回的时间点。

time_point - 是一个表示特定绝对时间的对象。

pred - 一个可调用的对象或函数,不接受参数并返回可作为bool值计算的值。这个函数会被反复调用,直到它的值为true。

 

返回值:

如果函数因为达到了abs_time而返回,则The unconditional version(1)返回cv_status::timeout,否则返回cv_status::no_timeout。

The predicate version(2)返回pred(),而不管超时是否被触发(尽管只有在被触发时才为false)。

 

通知一个

void notify_one() noexcept;

解除当前等待此条件的线程之一的阻塞。

如果没有线程在等待,该函数将不执行任何操作。

如果有多个线程,则不指定选择哪个线程。

通知所有

void notify_all() noexcept;

解除当前等待此条件的所有线程的阻塞。

如果没有线程在等待,该函数将不执行任何操作。

 

在线程退出时通知所有线程

void notify_all_at_thread_exit (condition_variable& cond, unique_lock<mutex> lck);

当调用线程退出时,将通知所有等待cond的线程恢复执行。

该函数还获得了lck管理的互斥对象的锁的所有权,该互斥对象存储在函数内部,并在线程退出时解锁(就在通知所有线程之前),行为就像一旦所有具有线程存储时间的对象被销毁后调用:

lck.unlock();
cond.notify_all();

 

参数:

cond - 一个条件变量对象,在线程退出时通知所有线程。

lck - 一个unique_lock对象,它的互斥对象目前被这个线程锁定。该对象由函数获取(它必须是右值)。

 

cond上的所有等待线程(如果有的话)都应该使用与lck相同的底层互斥对象。

 

条件变量(任何锁)

class condition_variable_any;

与condition_variable相同,不同的是它的等待函数可以接受任何可锁定类型作为参数(condition_variable对象只能接受unique_lock<mutex>)。除此之外,它们是相同的。

 

例子:

condition_variable

condition_variable_any

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值