c++ 11: lock_guard/unique_lock详解

参考:C++11 并发指南三(std::mutex 详解) - Haippy - 博客园 (cnblogs.com)

C++11 并发指南三(Lock 详解) - Haippy - 博客园 (cnblogs.com)

 c++ 11: lock_guard/unique_lock详解_c++ lock_guard-优快云博客

C++14 17共享超时互斥锁 shared_timed_mutex / 共享锁 shared_mutex-优快云博客

adopt_lock 和 defer_lock 的区别-优快云博客

 并发支持库 - cppreference.com

线程支持库 - C++中文 - API参考文档 (apiref.com)

 cplusplus.com/reference/mutex/

待整理:

C++11中的多线程同步:互斥锁、信号量、条件变量、异步操作、原子操作_sem_t c++11-优快云博客

概述


头文件介绍 
Mutex 系列类(六种)

  • std::mutex,最基本的 Mutex 类,提供基本互斥设施(类)。 
  • std::recursive_mutex,递归 Mutex 类,提供互斥设施,提供能被同一线程递归锁定的互斥设施(类)。 
  • std::timed_mutex,定时 Mutex 类,实现有时限锁定(类)。 
  • std::recursive_timed_mutex,定时递归 Mutex 类,提供能被同一线程递归锁定的互斥设施,并实现有时限锁定(类)。 
  • shared_mutex (C++17) ,提供共享互斥设施(类)。

  • shared_timed_mutex (C++14) , 提供共享互斥设施并实现有时限锁定


Lock 类(通用互斥管理)

在头文件<mutex>定义

  • std::lock_guard,与 Mutex RAII 相关,方便线程对互斥量上锁。实现严格基于作用域的互斥体所有权包装器(类模板) 。
  • std::unique_lock,与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。实现可移动的互斥体所有权包装器(类模板)。
  • std::scoped_lock (C++17),用于多个互斥体的免死锁 RAII 封装器(类模板)
  • std::shared_lock (C++14),实现可移动的共享互斥体所有权封装器(类模板)


其他类型

  • std::once_flag (C++11),  确保 call_once 只调用函数一次的帮助对象(类) 
  • defer_lock_t (C++11), 用于指定锁定策略的标签类型(类)。
  • try_to_lock_t (C++11), 用于指定锁定策略的标签类型(类)。
  • adopt_lock_t (C++11), 用于指定锁定策略的标签类型(类)。

常量

  • defer_lock (C++11),用于指定锁定策略的标签常量(常量)。
  • try_to_lock (C++11),用于指定锁定策略的标签常量(常量)。
  • adopt_lock (C++11),用于指定锁定策略的标签常量(常量)。

单次调用

  • once_flag (C++11),  确保 call_once 只调用函数一次的帮助对象(类)。
  • call_once (C++11),仅调用函数一次,即使从多线程调用

函数

  • std::try_lock,尝试同时对多个互斥量上锁。 
  • std::lock,可以同时对多个互斥量上锁。 
  • std::call_once,如果多个线程需要同时调用某个函数,call_once 可以保证多个线程对该函数只调用一次。 

std::Mutex各种锁介绍


std::mutex 介绍

下面以 std::mutex 为例介绍 C++11 中的互斥量用法。

std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性——即不支持递归地对 std::mutex 对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁。

 mutex 类是能用于保护共享数据免受从多个线程同时访问的同步原语。

mutex 提供排他性非递归所有权语义:

  • 调用方线程从它成功调用 lock 或 try_lock 开始,到它调用 unlock 为止占有 mutex 。
  • 线程占有 mutex 时,所有其他线程若试图要求 mutex 的所有权,则将阻塞(对于 lock 的调用)或收到 false 返回值(对于 try_lock ).
  • 调用方线程在调用 lock 或 try_lock 前必须不占有 mutex 。

若 mutex 在仍为任何线程所占有时即被销毁,或在占有 mutex 时线程终止,则行为未定义。 mutex 类满足互斥体 (Mutex) 标准布局类型 (StandardLayoutType) 的全部要求。

std::mutex 既不可复制亦不可移动。

std::mutex的成员类型

成员类型定义
native_handle_type(可选)实现定义

std::mutex 的成员函数

(构造函数)构造互斥(公开成员函数)
(析构函数)销毁互斥(公开成员函数)

operator=

[被删除]

不可复制赋值(公开成员函数)
锁定
lock锁定互斥,若互斥不可用则阻塞(公开成员函数)
try_lock尝试锁定互斥,若互斥不可用则返回(公开成员函数)
unlock解锁互斥(公开成员函数)

原生句柄

native_handle返回底层实现定义的原生句柄(公开成员函数)

注意

通常不直接使用 std::mutex : std::unique_lock 、 std::lock_guard 或 std::scoped_lock (C++17 起)以更加异常安全的方式管理锁定。

  • 构造函数(constructor)std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。 
  • lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况: (1). 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。(2). 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。 
  • unlock(), 解锁,释放对互斥量的所有权。 
  • try_lock(),尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。线程调用该函数也会出现下面 3 种情况,(1). 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。(2). 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉。(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
  • native_handle:返回底层实现定义的原生句柄对象。仅当库实现支持此成员函数时,该成员函数才存在于类互斥锁中。如果存在,它将返回一个值,该值用于访问与对象关联的特定于实现的信息。

下面给出一个与 std::mutex 的小例子(参考

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex

volatile int counter(0); // non-atomic counter
std::mutex mtx;           // locks access to counter

void attempt_10k_increases() {
    for (int i=0; i<10000; ++i) {
        if (mtx.try_lock()) {   // only increase if currently not locked:
            ++counter;
            mtx.unlock();
        }
    }
}

int main (int argc, const char* argv[]) {
    std::thread threads[10];
    for (int i=0; i<10; ++i)
        threads[i] = std::thread(attempt_10k_increases);

    for (auto& th : threads) th.join();
    std::cout << counter << " successful increases of the counter.\n";

    return 0;
}

std::recursive_mutex 介绍

std::recursive_mutex 与 std::mutex 一样,也是一种可以被上锁的对象,但是和 std::mutex 不同的是,std::recursive_mutex 允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,std::recursive_mutex 释放互斥量时需要调用与该锁层次深度相同次数的 unlock(),可理解为 lock() 次数和 unlock() 次数相同,除此之外,std::recursive_mutex 的特性和 std::mutex 大致相同。

recursive_mutex 类是同步原语,能用于保护共享数据免受从个多线程同时访问。

recursive_mutex 提供排他性递归所有权语义:

  • 调用方线程在从它成功调用 lock 或 try_lock 开始的时期里占有 recursive_mutex 。此时期间,线程可以进行对 lock 或 try_lock 的附加调用。所有权的时期在线程调用 unlock 匹配次数时结束。
  • 线程占有 recursive_mutex 时,若其他所有线程试图要求 recursive_mutex 的所有权,则它们将阻塞(对于调用 lock )或收到 false 返回值(对于调用 try_lock )。
  • 可锁定 recursive_mutex 次数的最大值是未指定的,但抵达该数后,对 lock 的调用将抛出 std::system_error 而对 try_lock 的调用将返回 false 。

若 recursive_mutex 在仍为某线程占有时被销毁,则程序行为未定义。 recursive_mutex 类满足互斥体 (Mutex) 标准布局类型 (StandardLayoutType) 的所有要求。

成员类型

成员类型定义
native_handle_type(可选)实现定义

成员函数

(构造函数)

构造互斥
(公开成员函数)

(析构函数)

销毁互斥
(公开成员函数)

operator=

[被删除]

不可复制赋值
(公开成员函数)
锁定

lock

锁定互斥,若互斥不可用则阻塞
(公开成员函数)

try_lock

尝试锁定互斥,若互斥不可用则返回
(公开成员函数)

unlock

解锁互斥
(公开成员函数)
原生句柄

native_handle

返回底层实现定义的原生句柄
(公开成员函数)

示例

recursive_mutex 的使用场景之一是保护类中的共享状态,而类的成员函数可能相互调用

#include <iostream>
#include <thread>
#include <mutex>
 
class X {
    std::recursive_mutex m;
    std::string shared;
  public:
    void fun1() {
      std::lock_guard<std::recursive_mutex> lk(m);
      shared = "fun1";
      std::cout << "in fun1, shared variable is now " << shared << '\n';
    }
    void fun2() {
      std::lock_guard<std::recursive_mutex> lk(m);
      shared = "fun2";
      std::cout << "in fun2, shared variable is now " << shared << '\n';
      fun1(); // 递归锁在此处变得有用
      std::cout << "back in fun2, shared variable is " << shared << '\n';
    };
};
 
int main() 
{
    X x;
    std::thread t1(&X::fun1, &x);
    std::thread t2(&X::fun2, &x);
    t1.join();
    t2.join();
}

可能的输出:

in fun1, shared variable is now fun1
in fun2, shared variable is now fun2
in fun1, shared variable is now fun1
back in fun2, shared variable is fun1

std::timed_mutex 介绍

std::timed_mutex 比 std::mutex 多了两个成员函数,try_lock_for(),try_lock_until()。

try_lock_for 函数接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与 std::mutex 的 try_lock() 不同,try_lock 如果被调用时没有获得锁则直接返回 false),如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。

try_lock_until 函数则接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。 

timed_mutex 类是能用于保护数据免受多个线程同时访问的同步原语。

以类似 mutex 的行为, timed_mutex 提供排他性非递归所有权语义。另外, timed_mutex 提供通过 try_lock_for() 和 try_lock_until() 方法试图带时限地要求 timed_mutex 所有权的能力。

timed_mutex 类满足定时互斥体 (TimedMutex) 标准布局类型 (StandardLayoutType) 的所有要求。

成员类型

成员类型定义
native_handle_type(可选)实现定义

成员函数

(构造函数)

构造互斥
(公开成员函数)

(析构函数)

销毁互斥
(公开成员函数)

operator=

[被删除]

不可复制赋值
(公开成员函数)
锁定

lock

锁定互斥,若互斥不可用则阻塞
(公开成员函数)

try_lock

尝试锁定互斥,若互斥不可用则返回
(公开成员函数)

try_lock_for

尝试锁定互斥,若互斥在指定的时限时期中不可用则返回
(公开成员函数)

try_lock_until

尝试锁定互斥,若直至抵达指定时间点互斥不可用则返回
(公开成员函数)

unlock

解锁互斥
(公开成员函数)
原生句柄

native_handle

返回底层实现定义的原生句柄
(公开成员函数)

下面的小例子说明了 std::time_mutex 的用法(参考)。

#include <iostream>       // std::cout
#include <chrono>         // std::chrono::milliseconds
#include <thread>         // std::thread
#include <mutex>          // std::timed_mutex

std::timed_mutex mtx;

void fireworks() {
  // waiting to get a lock: each thread prints "-" every 200ms:
  while (!mtx.try_lock_for(std::chrono::milliseconds(200))) {
    std::cout << "-";
  }
  // got a lock! - wait for 1s, then this thread prints "*"
  std::this_thread::sleep_for(std::chrono::milliseconds(1000));
  std::cout << "*\n";
  mtx.unlock();
}

int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(fireworks);

  for (auto& th : threads) th.join();

  return 0;
}


std::recursive_timed_mutex 介绍

和 std:recursive_mutex 与 std::mutex 的关系一样,std::recursive_timed_mutex 的特性也可以从 std::timed_mutex 推导出来

recursive_timed_mutex 是同步原语,能用于保护共享数据免受从多个线程同时访问。

以类似 std::recursive_mutex 的方式, recursive_timed_mutex 提供排他性递归所有权语义。另外, recursive_timed_mutex 通过 try_lock_for 与 try_lock_until 方法,提供带时限地试图要求 recursive_timed_mutex 所有权的能力。

recursive_timed_mutex 类满足定时互斥体 (TimedMutex) 标准布局类型 (StandardLayoutType) 的所有要求。

成员类型

成员类型定义
native_handle_type(可选)实现定义

成员函数

(构造函数)

构造互斥
(公开成员函数)

(析构函数)

销毁互斥
(公开成员函数)

operator=

[被删除]

不可复制赋值
(公开成员函数)
锁定

lock

锁定互斥,若互斥不可用则阻塞
(公开成员函数)

try_lock

尝试锁定互斥,若互斥不可用则返回
(公开成员函数)

try_lock_for

尝试锁定互斥,若互斥在指定的时限时期中不可用则返回
(公开成员函数)

try_lock_until

尝试锁定互斥,若直至抵达指定时间点互斥不可用则返回
(公开成员函数)

unlock

解锁互斥
(公开成员函数)
原生句柄

native_handle

返回底层实现定义的原生句柄
(公开成员函数)

std::shared_mutex介绍 
(C++17)

定义于头文件 <shared_mutex>

class shared_mutex;

(C++17 起)

shared_mutex 类是一个同步原语,可用于保护共享数据不被多个线程同时访问。与便于独占访问的其他互斥类型不同,shared_mutex 拥有二个访问级别:

  • 共享 - 多个线程能共享同一互斥的所有权。
  • 独占性 - 仅一个线程能占有互斥。

若一个线程已获取独占性锁(通过 lock 、 try_lock ),则无其他线程能获取该锁(包括共享的)。

仅当任何线程均未获取独占性锁时,共享锁能被多个线程获取(通过 lock_shared 、 try_lock_shared )。

在一个线程内,同一时刻只能获取一个锁(共享独占性)。

共享互斥体在能由任何数量的线程同时读共享数据,但一个线程只能在无其他线程同时读写时写同一数据时特别有用。

shared_mutex 类满足共享互斥体 (SharedMutex) 标准布局类型 (StandardLayoutType) 的所有要求。

成员类型

成员类型定义
native_handle_type(可选)实现定义

成员函数

(构造函数)

构造互斥
(公开成员函数)

(析构函数)

销毁互斥
(公开成员函数)

operator=

[被删除]

不可复制赋值
(公开成员函数)
排他性锁定

lock

锁定互斥,若互斥不可用则阻塞
(公开成员函数)

try_lock

尝试锁定互斥,若互斥不可用则返回
(公开成员函数)

unlock

解锁互斥
(公开成员函数)
共享锁定

lock_shared

为共享所有权锁定互斥,若互斥不可用则阻塞
(公开成员函数)

try_lock_shared

尝试为共享所有权锁定互斥,若互斥不可用则返回
(公开成员函数)

unlock_shared

解锁互斥(共享所有权)
(公开成员函数)
原生句柄

native_handle

返回底层实现定义的原生句柄
(公开成员函数)

示例

#include <iostream>
#include <mutex>  // 对于 std::unique_lock
#include <shared_mutex>
#include <thread>
 
class ThreadSafeCounter {
 public:
  ThreadSafeCounter() = default;
 
  // 多个线程/读者能同时读计数器的值。
  unsigned int get() const {
    std::shared_lock<std::shared_mutex> lock(mutex_);
    return value_;
  }
 
  // 只有一个线程/写者能增加/写线程的值。
  void increment() {
    std::unique_lock<std::shared_mutex> lock(mutex_);
    value_++;
  }
 
  // 只有一个线程/写者能重置/写线程的值。
  void reset() {
    std::unique_lock<std::shared_mutex> lock(mutex_);
    value_ = 0;
  }
 
 private:
  mutable std::shared_mutex mutex_;
  unsigned int value_ = 0;
};
 
int main() {
  ThreadSafeCounter counter;
 
  auto increment_and_print = [&counter]() {
    for (int i = 0; i < 3; i++) {
      counter.increment();
      std::cout << std::this_thread::get_id() << ' ' << counter.get() << '\n';
 
      // 注意:写入 std::cout 实际上也要由另一互斥同步。省略它以保持示例简洁。
    }
  };
 
  std::thread thread1(increment_and_print);
  std::thread thread2(increment_and_print);
 
  thread1.join();
  thread2.join();
}
 
// 解释:下列输出在单核机器上生成。 thread1 开始时,它首次进入循环并调用 increment() ,
// 随后调用 get() 。然而,在它能打印返回值到 std::cout 前,调度器将 thread1 置于休眠
// 并唤醒 thread2 ,它显然有足够时间一次运行全部三个循环迭代。再回到 thread1 ,它仍在首个
// 循环迭代中,它最终打印其局部的计数器副本的值,即 1 到 std::cout ,再运行剩下二个循环。
// 多核机器上,没有线程被置于休眠,且输出更可能为递增顺序。

 std::shared_timed_mutex介绍

(C++14)

定义于头文件 <shared_mutex>

class shared_timed_mutex;

(C++14 起)

shared_timed_mutex 类是能用于保护数据免受多个线程同时访问的同步原语。与其他促进排他性访问的互斥类型相反,拥有二个层次的访问:

  • 共享 - 多个线程能共享同一互斥的所有权。
  • 排他性 - 仅一个线程能占有互斥。

共享互斥通常用于多个读线程能同时访问同一资源而不导致数据竞争,但只有一个写线程能访问的情形。

以类似 timed_mutex 的行为, shared_timed_mutex 提供通过 try_lock_for() 、 try_lock_until() 、 try_lock_shared_for() 、 try_lock_shared_until() 方法,试图带时限地要求 shared_timed_mutex 所有权的能力。

shared_timed_mutex 类满足共享定时互斥体 (SharedTimedMutex) 标准布局类型 (StandardLayoutType) 的所有要求。

成员函数

(构造函数)

构造互斥
(公开成员函数)

(析构函数)

销毁互斥
(公开成员函数)

operator=

[被删除]

不可复制赋值
(公开成员函数)
排他性锁定

lock

锁定互斥,若互斥不可用则阻塞
(公开成员函数)

try_lock

尝试锁定互斥,若互斥不可用则返回
(公开成员函数)

try_lock_for

尝试锁定互斥,若互斥在指定的时限时期中不可用则返回
(公开成员函数)

try_lock_until

尝试锁定互斥,若直至抵达指定时间点互斥不可用则返回
(公开成员函数)

unlock

解锁互斥
(公开成员函数)
共享锁定

lock_shared

为共享所有权锁定互斥,若互斥不可用则阻塞
(公开成员函数)

try_lock_shared

尝试为共享所有权锁定互斥,若互斥不可用则返回
(公开成员函数)

try_lock_shared_for

尝试为共享所有权锁定互斥,若互斥在指定的时限时期中不可用则返回
(公开成员函数)

try_lock_shared_until

尝试为共享所有权锁定互斥,若直至抵达指定时间点互斥不可用则返回
(公开成员函数)

unlock_shared

解锁互斥(共享所有权)
(公开成员函数)

示例

复制赋值运算符,属于保有能处置多个读者,但只有一个写者的资源

#include <mutex>
#include <shared_mutex>
 
class R
{
    mutable std::shared_timed_mutex mut;
    /* 数据 */
public:
    R& operator=(const R& other)
    {
        // 要求排他性所有权以写入 *this
        std::unique_lock<std::shared_timed_mutex> lhs(mut, std::defer_lock);
        // 要求共享所有权以读取 other
        std::shared_lock<std::shared_timed_mutex> rhs(other.mut, std::defer_lock);
        std::lock(lhs, rhs);
        /* 赋值数据 */
        return *this;
    }
};
 
int main() {
    R r;
}

shared_mutex和shared_timed_mutex总结

共享锁,也叫读写锁,主要应用与读多写少的场景

比如,在多线程环境下,多个线程操作同一个文件,其中读文件的操作比写文件的操作更加频繁,那么在进行读操作时,不需要互斥,线程间可以共享这些数据,随意的读取。但是一旦有写操作,那么一定要进行互斥操作,否则读取到的数据可能存在不一致。

C++14 共享超时互斥锁 shared_timed_mutex


读线程 调用 lock_shared()获取共享锁,写线程 调用 lock() 获取互斥锁。

  • 当调用lock()的时候,如果有线程获取了共享锁,那么写线程会等待,直到所有线程将数据读取完成释放共享锁,再去锁定资源,进行修改;
  • 当调用lock_shared()时,如果有写线程获取了互斥锁,那么需要等待
  • 当调用lock_shared()时,如果有读线程获取共享锁,也会直接返回,获取成功。

代码示例:

std::shared_timed_mutex stmtx;

void ReadThread()
{
    while (true)
    {
        stmtx.lock_shared();
        std::cout << "thread id = " << std::this_thread::get_id() << " lock shared  read" << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        stmtx.unlock_shared();
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
}

void WriteThread()
{
    while (true)
    {
        stmtx.lock();
        std::cout << "thread id = " << std::this_thread::get_id() << " lock  write" << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
        stmtx.unlock();
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
}


int main()
{
    for(int i = 0; i < 3; ++i)
    {
        std::thread t(ReadThread);
        t.detach();
    }

    for(int i = 0; i < 2; ++i)
    {
        std::thread t(WriteThread);
        t.detach();
    }

    getchar();
    return 0;
}

执行结果:

thread id = 139663449360128 lock  write
thread id = 139663457752832 lock shared  read
thread id = 139663474538240 lock shared  read
thread id = 139663466145536 lock shared  read
thread id = 139663440967424 lock  write
thread id = 139663474538240 lock shared  read
thread id = 139663466145536 lock shared  read
thread id = 139663457752832 lock shared  read
thread id = 139663449360128 lock  write
thread id = 139663457752832 lock shared  read
thread id = 139663466145536 lock shared  read
thread id = 139663474538240 lock shared  read
thread id = 139663440967424 lock  write
thread id = 139663466145536 lock shared  read
thread id = 139663457752832 lock shared  read
thread id = 139663474538240 lock shared  read
thread id = 139663449360128 lock  write
thread id = 139663457752832 lock shared  read
thread id = 139663466145536 lock shared  read
thread id = 139663474538240 lock shared  read
thread id = 139663440967424 lock  write
thread id = 139663457752832 lock shared  read
thread id = 139663466145536 lock shared  read
thread id = 139663474538240 lock shared  read
 

C++17 共享互斥 shared_mutex

shared_mutex与shared_timed_mutex基本一致,shared_timed_mutex较shared_mutex多了一些与时间相关的接口,如:

try_lock_for(...);
try_lock_shared_for(...);
try_lock_shared_until(...);
try_lock_until(...);

用法和共享超时互斥锁一致,如:

std::shared_mutex smtx;
smtx.lock_shared();
smtx.unlock_shared();
smtx.lock();
smtx.unlock();

std::Lock锁管理类介绍

C++11 标准为我们提供了两种基本的锁管理类型,分别如下:

  • std::lock_guard,与 Mutex RAII 相关,方便线程对互斥量上锁。
  • std::unique_lock,与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。

另外C++14增加了:

  •         std::shared_lock, (C++14) ,实现可移动的共享互斥体所有权封装器

C++17增加了:

  •         std::scoped_lock,(C++17),用于多个互斥体的免死锁 RAII 封装器

 几个与锁类型相关的 Tag 类

另外还提供了几个与锁类型相关的 Tag 类,分别如下:

  • std::adopt_lock_t,一个空的标记类,定义如下:

struct adopt_lock_t {};

该类型的常量对象adopt_lock(adopt_lock 是一个常量对象,定义如下:

constexpr adopt_lock_t adopt_lock {};,// constexpr 是 C++11 中的新关键字)

通常作为参数传入给 unique_lock 或 lock_guard 的构造函数。

  • std::defer_lock_t,一个空的标记类,定义如下: 

struct defer_lock_t {};

该类型的常量对象 defer_lockdefer_lock 是一个常量对象,定义如下:


constexpr defer_lock_t defer_lock {};,// constexpr 是 C++11 中的新关键字)

通常作为参数传入给 unique_lock 或 lock_guard 的构造函数。

  • std::try_to_lock_t,一个空的标记类,定义如下:
struct try_to_lock_t {};

该类型的常量对象 try_to_locktry_to_lock 是一个常量对象,定义如下:


constexpr try_to_lock_t try_to_lock {};,// constexpr 是 C++11 中的新关键字)

通常作为参数传入给 unique_lock 或 lock_guard 的构造函数。后面我们会详细介绍以上三种 Tag 类型在配合 lock_gurad 与 unique_lock 使用时的区别。

std::lock_guard 介绍


std::lock_gurad 是 C++11 中定义的模板类。定义如下:

template <class Mutex> 
class lock_guard;

类 lock_guard 是互斥体包装器,为在作用域块期间占有互斥提供便利 RAII 风格机制。

创建 lock_guard 对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard 对象的作用域时,销毁 lock_guard 并释放互斥。

lock_guard 类不可复制。

模板形参

Mutex-要锁定的互斥。类型必须满足基本可锁定 (BasicLockable) 要求
成员类型定义
mutex_typeMutex

成员函数

(构造函数)

构造 lock_guard ,可选地锁定给定的互斥
(公开成员函数)

(析构函数)

析构 lock_guard 对象,解锁底层互斥
(公开成员函数)

operator=

[被删除]

不可复制赋值
(公开成员函数)

 示例

#include <thread>
#include <mutex>
#include <iostream>
 
int g_i = 0;
std::mutex g_i_mutex;  // 保护 g_i
 
void safe_increment()
{
    std::lock_guard<std::mutex> lock(g_i_mutex);
    ++g_i;
 
    std::cout << std::this_thread::get_id() << ": " << g_i << '\n';
 
    // g_i_mutex 在锁离开作用域时自动释放
}
 
int main()
{
    std::cout << "main: " << g_i << '\n';
 
    std::thread t1(safe_increment);
    std::thread t2(safe_increment);
 
    t1.join();
    t2.join();
 
    std::cout << "main: " << g_i << '\n';
}


       lock_guard 对象通常用于管理某个锁(Lock)对象,因此与 Mutex RAII 相关,方便线程对互斥量上锁,即在某个 lock_guard 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 lock_guard 的生命周期结束之后,它所管理的锁对象会被解锁(注:类似 shared_ptr 等智能指针管理动态分配的内存资源 )。 
       模板参数 Mutex 代表互斥量类型,例如 std::mutex 类型,它应该是一个基本的 BasicLockable 类型,标准库中定义几种基本的 BasicLockable 类型,分别 std::mutex, std::recursive_mutex, std::timed_mutex,std::recursive_timed_mutex 以及 std::unique_lock(本文后续会介绍 std::unique_lock)。(注:BasicLockable 类型的对象只需满足两种操作,lock 和 unlock,另外还有 Lockable 类型,在 BasicLockable 类型的基础上新增了 try_lock 操作,因此一个满足 Lockable 的对象应支持三种操作:lock,unlock 和 try_lock;最后还有一种 TimedLockable 对象,在 Lockable 类型的基础上又新增了 try_lock_for 和 try_lock_until 两种操作,因此一个满足 TimedLockable 的对象应支持五种操作:lock, unlock, try_lock, try_lock_for, try_lock_until)。 
在 lock_guard 对象构造时,传入的 Mutex 对象(即它所管理的 Mutex 对象)会被当前线程锁住。在lock_guard 对象被析构时,它所管理的 Mutex 对象会自动解锁,由于不需要程序员手动调用 lock 和 unlock 对 Mutex 进行上锁和解锁操作,因此这也是最简单安全的上锁和解锁方式,尤其是在程序抛出异常后先前已被上锁的 Mutex 对象可以正确进行解锁操作,极大地简化了程序员编写与 Mutex 相关的异常处理代码。 
        值得注意的是,lock_guard 对象并不负责管理 Mutex 对象的生命周期,lock_guard 对象只是简化了 Mutex 对象的上锁和解锁操作,方便线程对互斥量上锁,即在某个 lock_guard 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 lock_guard 的生命周期结束之后,它所管理的锁对象会被解锁。

std::lock_guard 构造函数
lock_guard 构造函数如下表所示:

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;

 获得给定互斥 m 的所有权。

  • locking 初始化 :lock_guard 对象管理 Mutex 对象 m,并在构造时对 m 进行上锁(调用 m.lock());等效地调用 m.lock()。若 m 不是递归互斥,且当前线程已占有 m 则行为未定义。
  • adopting初始化 :lock_guard 对象管理 Mutex 对象 m,与 locking 初始化(1) 不同的是, Mutex 对象 m 已被当前线程锁住;获得互斥 m 的所有权而不试图锁定它。若当前线程不占有 m 则行为未定义。
  • 拷贝构造 :lock_guard 对象的拷贝构造和移动构造(move construction)均被禁用,因此 lock_guard 对象不可被拷贝构造或移动构造;复制构造函数被删除。 

我们来看一个简单的例子(参考):

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::lock_guard, std::adopt_lock
std::mutex mtx;           // mutex for critical section
void print_thread_id (int id) {
  mtx.lock();
  std::lock_guard<std::mutex> lck(mtx, std::adopt_lock);
  std::cout << "thread #" << id << '\n';
}
int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(print_thread_id,i+1);
 
  for (auto& th : threads) th.join();
 
  return 0;
}

 mtx.lock();
  std::lock_guard<std::mutex> lck(
mtx, std::adopt_lock);
     在 print_thread_id 中,我们首先对 mtx 进行上锁操作(mtx.lock();),然后用 mtx 对象构造一个 lock_guard 对象(std::lock_guard lck(mtx, std::adopt_lock);),注意此时 Tag 参数为 std::adopt_lock,表明当前线程已经获得了锁,此后 mtx 对象的解锁操作交由 lock_guard 对象 lck 来管理,在 lck 的生命周期结束之后,mtx 对象会自动解锁。 
lock_guard 最大的特点就是安全易于使用,请看下面例子(参考),在异常抛出的时候通过 lock_guard 对象管理的 Mutex 可以得到正确地解锁。

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::lock_guard
#include <stdexcept>      // std::logic_error
std::mutex mtx;
void print_even (int x) {
  if (x%2==0) std::cout << x << " is even\n";
  else throw (std::logic_error("not even"));
}
void print_thread_id (int id) {
  try {
    // using a local lock_guard to lock mtx guarantees unlocking on destruction / exception:
    std::lock_guard<std::mutex> lck (mtx);
    print_even(id);
  }
  catch (std::logic_error&) {
    std::cout << "[exception caught]\n";
  }
}
int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(print_thread_id,i+1);
 
  for (auto& th : threads) th.join();
 
  return 0;
}

// using a local lock_guard to lock mtx guarantees unlocking on destruction / exception:
    std::lock_guard<std::mutex> lck (mtx);

std::unique_lock 介绍

定义于头文件 <mutex>

template< class Mutex >
class unique_lock;

(C++11 起)

类 unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。

类 unique_lock 可移动,但不可复制——它满足可移动构造 (MoveConstructible) 可移动赋值 (MoveAssignable) 但不满足可复制构造 (CopyConstructible) 可复制赋值 (CopyAssignable) 

类 unique_lock 满足基本可锁定 (BasicLockable) 要求。若 Mutex 满足可锁定 (Lockable) 要求,则 unique_lock 亦满足可锁定 (Lockable) 要求(例如:能用于 std::lock ) ;若 Mutex 满足可定时锁定 (TimedLockable) 要求,则 unique_lock 亦满足可定时锁定 (TimedLockable) 要求。

模板形参

Mutex-要锁定的互斥类型。类型必须满足基本可锁定 (BasicLockable) 要求

成员类型

类型定义
mutex_typeMutex

成员函数

(构造函数)

构造 unique_lock ,可选地锁定提供的互斥
(公开成员函数)

(析构函数)

若占有关联互斥,则解锁之
(公开成员函数)

operator=

若占有则解锁互斥,并取得另一者的所有权
(公开成员函数)
锁定

lock

锁定关联互斥
(公开成员函数)

try_lock

尝试锁定关联互斥,若互斥不可用则返回
(公开成员函数)

try_lock_for

试图锁定关联的可定时锁定 (TimedLockable) 互斥,若互斥在给定时长中不可用则返回
(公开成员函数)

try_lock_until

尝试锁定关联可定时锁定 (TimedLockable) 互斥,若抵达指定时间点互斥仍不可用则返回
(公开成员函数)

unlock

解锁关联互斥
(公开成员函数)
修改器

swap

与另一 std::unique_lock 交换状态
(公开成员函数)

release

将关联互斥解关联而不解锁它
(公开成员函数)
观察器

mutex

返回指向关联互斥的指针
(公开成员函数)

owns_lock

测试锁是否占有其关联互斥
(公开成员函数)

operator bool

测试锁是否占有其关联互斥
(公开成员函数)

非成员函数

std::swap(std::unique_lock)

(C++11)

std::swap 对 unique_lock 的特化
(函数模板)

示例

#include <mutex>
#include <thread>
#include <chrono>
 
struct Box {
    explicit Box(int num) : num_things{num} {}
 
    int num_things;
    std::mutex m;
};
 
void transfer(Box &from, Box &to, int num)
{
    // 仍未实际取锁
    std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
    std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);
 
    // 锁两个 unique_lock 而不死锁
    std::lock(lock1, lock2);
 
    from.num_things -= num;
    to.num_things += num;
 
    // 'from.m' 与 'to.m' 互斥解锁于 'unique_lock' 析构函数
}
 
int main()
{
    Box acc1(100);
    Box acc2(50);
 
    std::thread t1(transfer, std::ref(acc1), std::ref(acc2), 10);
    std::thread t2(transfer, std::ref(acc2), std::ref(acc1), 5);
 
    t1.join();
    t2.join();
}

std::unique_lock 构造函数

std::unique_lock 的构造函数的数目相对来说比 std::lock_guard 多,其中一方面也是因为 
std::unique_lock 更加灵活,从而在构造 std::unique_lock 
对象时可以接受额外的参数。总地来说,std::unique_lock 构造函数如下:

default (1) unique_lock() noexcept;

新创建的 unique_lock 对象不管理任何 Mutex 对象。 构造无关联互斥的 unique_lock 。

locking (2) explicit unique_lock(mutex_type& m);

新创建的 unique_lock 对象管理 Mutex 对象 m,并尝试调用 m.lock() 对 Mutex 对象进行上锁,如果此时另外某个 unique_lock 对象已经管理了该 Mutex 对象 m,则当前线程将会被阻塞。

 构造以 m 为关联互斥的 unique_lock 。 通过调用 m.lock() 锁定关联互斥。若当前线程已占有互斥则行为未定义,除非互斥是递归的。

try-locking (3) unique_lock(mutex_type& m, try_to_lock_t tag);

新创建的 unique_lock 对象管理 Mutex 对象 m,并尝试调用 m.try_lock() 对 Mutex 对象进行上锁,但如果上锁不成功,并不会阻塞当前线程。

构造以 m 为关联互斥的 unique_lock 。 通过调用 m.try_lock() 尝试锁定关联互斥而不阻塞。若当前线程已占有互斥则行为未定义,除非互斥是递归的。

deferred (4)    unique_lock(mutex_type& m, defer_lock_t tag) noexcept;

新创建的 unique_lock 对象管理 Mutex 对象 m,但是在初始化的时候并不锁住 Mutex 对象。 m 应该是一个没有当前线程锁住的 Mutex 对象。

构造以 m 为关联互斥的 unique_lock 。不锁定关联互斥。

adopting (5)    unique_lock(mutex_type& m, adopt_lock_t tag);

新创建的 unique_lock 对象管理 Mutex 对象 m, m 应该是一个已经被当前线程锁住的 Mutex 对象。(并且当前新创建的 unique_lock 对象拥有对锁(Lock)的所有权)。

构造以 m 为关联互斥的 unique_lock 。 假定调用方线程已占有 m 。

locking for (6) template <class Rep, class Period>
unique_lock(mutex_type& m, const chrono::duration<Rep,Period>& rel_time /*timeout_duration*/);

新创建的 unique_lock 对象管理 Mutex 对象 m,并试图通过调用 m.try_lock_for(rel_time) 来锁住 Mutex 对象一段时间(rel_time)。

构造以 m 为关联互斥的 unique_lock 。通过调用 m.try_lock_for(timeout_duration) 尝试锁定关联互斥。阻塞直至经过指定的 timeout_duration 或获得锁,之先到来者。可能阻塞长于 timeout_duration 。

locking until (7)   template <class Clock, class Duration>
unique_lock(mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time /*timeout_time*/);

新创建的 unique_lock 对象管理 Mutex 对象m,并试图通过调用 m.try_lock_until(abs_time) 来在某个时间点(abs_time)之前锁住 Mutex 对象。

构造以 m 为关联互斥的 unique_lock 。通过调用 m.try_lock_until(timeout_time) 尝试锁定关联互斥。阻塞直至抵达指定的 timeout_time 或获得锁,之先到来者。可能阻塞长于抵达 timeout_time 。

copy [deleted] (8)  unique_lock(const unique_lock&) = delete;
unique_lock 

对象不能被拷贝构造。

move (9)    unique_lock(unique_lock&& x);

 新创建的 unique_lock 对象获得了由 x 所管理的 Mutex 对象的所有权(包括当前 Mutex 的状态)。调用 move 构造之后, x 对象如同通过默认构造函数所创建的,就不再管理任何 Mutex 对象了。

移动构造函数。以 other 的内容初始化 unique_lock 。令 other 无关联互斥。

简单示例:
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock

std::mutex mtx;           // mutex for critical section

void print_block (int n, char c) {
    // critical section (exclusive access to std::cout signaled by lifetime of lck):
    std::unique_lock<std::mutex> lck (mtx);
    for (int i=0; i<n; ++i) {
        std::cout << c;
    }
    std::cout << '\n';
}

int main ()
{
    std::thread th1 (print_block,50,'*');
    std::thread th2 (print_block,50,'$');

    th1.join();
    th2.join();

    return 0;
}

可能的输出(行的顺序可能会有所不同,但字符绝不会混合):

**************************************************
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

综上所述,由 (2) 和 (5) 创建的 unique_lock 对象通常拥有 Mutex 对象的锁。而通过 (1) 和 (4) 创建的则不会拥有锁。通过 (3),(6) 和 (7) 创建的 unique_lock 对象,则在 lock 成功时获得锁。 


关于unique_lock 的构造函数,请看下面例子(参考):

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::lock, std::unique_lock

std::mutex foo,bar;       // std::adopt_lock, std::defer_lockstd::mutex foo,bar;

void task_a () 
{
  std::lock (foo,bar);         // simultaneous lock (prevents deadlock)
  std::unique_lock<std::mutex> lck1 (foo,std::adopt_lock);
  std::unique_lock<std::mutex> lck2 (bar,std::adopt_lock);
  std::cout << "task a\n";
  // (unlocked automatically on destruction of lck1 and lck2)
}

void task_b () 
{
  // foo.lock(); bar.lock(); // replaced by:
  std::unique_lock<std::mutex> lck1, lck2;
  lck1 = std::unique_lock<std::mutex>(bar,std::defer_lock);
  lck2 = std::unique_lock<std::mutex>(foo,std::defer_lock);
  std::lock (lck1,lck2);       // simultaneous lock (prevents deadlock)
  std::cout << "task b\n";
  // (unlocked automatically on destruction of lck1 and lck2)
}
 
int main ()
{
  std::thread th1 (task_a);
  std::thread th2 (task_b);
 
  th1.join();
  th2.join();
 
  return 0;
}

std::unique_lock 移动(move assign)赋值操作

std::unique_lock 支持移动赋值(move assignment),但是普通的赋值被禁用了,

move (1)
unique_lock& operator= (unique_lock&& x) noexcept;
copy [deleted] (2)
unique_lock& operator= (const unique_lock&) = delete;
move (1)    unique_lock& operator= (unique_lock&& x) noexcept;
copy [deleted] (2)  unique_lock& operator= (const unique_lock&) = delete;

移动赋值(move assignment)之后,由 x 所管理的 Mutex 对象及其状态将会被新的 std::unique_lock 对象取代。

如果被赋值的对象之前已经获得了它所管理的 Mutex 对象的锁,则在移动赋值(move assignment)之前会调用 unlock 函数释放它所占有的锁。调用移动赋值(move assignment)之后, x 对象如同通过默认构造函数所创建的,也就不再管理任何 Mutex 对象了。请看下面例子(参考):

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock
std::mutex mtx;           // mutex for critical section

void print_fifty (char c) {
  std::unique_lock<std::mutex> lck;         // default-constructed
  lck = std::unique_lock<std::mutex>(mtx);  // move-assigned
  for (int i=0; i<50; ++i) { std::cout << c; }
     std::cout << '\n';
}

int main ()
{
  std::thread th1 (print_fifty,'*');
  std::thread th2 (print_fifty,'$');
 
  th1.join();
  th2.join();
 
  return 0;
}

std::unique_lock 主要成员函数

本节我们来看看 std::unique_lock 的主要成员函数。由于 std::unique_lock 比 std::lock_guard 操作灵活,因此它提供了更多成员函数。具体分类如下:

  1. 上锁/解锁操作:lock,try_lock,try_lock_for,try_lock_until 和unlock
  2. 修改操作:移动赋值(move assignment)(前面已经介绍过了),交换(swap)(与另一个 std::unique_lock 对象交换它们所管理的 Mutex 对象的所有权),释放(release)(返回指向它所管理的 Mutex 对象的指针,并释放所有权)
  3. 获取属性操作:owns_lock(返回当前 std::unique_lock 对象是否获得了锁)、operator bool()(与 owns_lock 功能相同,返回当前 std::unique_lock 对象是否获得了锁)、mutex(返回当前 std::unique_lock 对象所管理的 Mutex 对象的指针)。 

各个成员函数的用法:

std::unique_lock::lock

 
上锁操作,调用它所管理的 Mutex 对象的 lock 函数。如果在调用 Mutex 对象的 lock 函数时该 Mutex 对象已被另一线程锁住,则当前线程会被阻塞,直到它获得了锁。 
该函数返回时,当前的 unique_lock 对象便拥有了它所管理的 Mutex 对象的锁。如果上锁操作失败,则抛出 system_error 异常。 
// unique_lock::lock/unlock 

请看下面例子(参考):

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock, std::defer_lock
std::mutex mtx;           // mutex for critical section
void print_thread_id (int id) {
  std::unique_lock<std::mutex> lck (mtx,std::defer_lock);
  // critical section (exclusive access to std::cout signaled by locking lck):
  lck.lock();
  std::cout << "thread #" << id << '\n';
  lck.unlock();
}
int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(print_thread_id,i+1);
 
  for (auto& th : threads) th.join();
 
  return 0;
}
std::unique_lock::try_lock

上锁操作,调用它所管理的 Mutex 对象的 try_lock 函数,如果上锁成功,则返回 true,否则返回 false。 

请看下面例子(参考):

#include <iostream>       // std::cout
#include <vector>         // std::vector
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock, std::defer_lock
std::mutex mtx;           // mutex for critical section

void print_star () {
  std::unique_lock<std::mutex> lck(mtx,std::defer_lock);
  // print '*' if successfully locked, 'x' otherwise: 
  if (lck.try_lock())
    std::cout << '*';
  else                    
    std::cout << 'x';
}

int main ()
{
  std::vector<std::thread> threads;
  for (int i=0; i<500; ++i)
    threads.emplace_back(print_star);
 
  for (auto& x: threads) x.join();
 
  return 0;
}
std::unique_lock::try_lock_for

上锁操作,调用它所管理的 Mutex 对象的 try_lock_for 函数,如果上锁成功,则返回 true,否则返回 false。 

请看下面例子(参考):

#include <iostream>       // std::cout
#include <chrono>         // std::chrono::milliseconds
#include <thread>         // std::thread
#include <mutex>          // std::timed_mutex, std::unique_lock, std::defer_lock
std::timed_mutex mtx;

void fireworks () {
  std::unique_lock<std::timed_mutex> lck(mtx,std::defer_lock);
  // waiting to get a lock: each thread prints "-" every 200ms:
  while (!lck.try_lock_for(std::chrono::milliseconds(200))) {
    std::cout << "-";
  }
  // got a lock! - wait for 1s, then this thread prints "*"
  std::this_thread::sleep_for(std::chrono::milliseconds(1000));
  std::cout << "*\n";
}

int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(fireworks);
 
  for (auto& th : threads) th.join();
 
  return 0;
}
std::unique_lock::try_lock_until

上锁操作,调用它所管理的 Mutex 对象的 try_lock_for 函数,如果上锁成功,则返回 true,否则返回 false。 

请看下面例子(参考):

// timed_mutex::try_lock_until example
#include <iostream>       // std::cout
#include <chrono>         // std::chrono::system_clock
#include <thread>         // std::thread
#include <mutex>          // std::timed_mutex
#include <ctime>          // std::time_t, std::tm, std::localtime, std::mktime
 
std::timed_mutex cinderella;
 
void carriage() {
    std::unique_lock<std::timed_mutex> lck(cinderella, std::defer_lock);
    if (lck.try_lock_until(chrono::system_clock::now()+chrono::seconds(10))) {
        std::cout << "ride back home on carriage\n";
        lck.unlock();
    }
    else
        std::cout << "carriage reverts to pumpkin\n";
}
 
void ball() {
    std::unique_lock<std::timed_mutex> lck(cinderella, std::defer_lock);
    lck.lock();
    std::cout << "at the ball...\n";
 
}
 
int main()
{
    std::thread th1(ball);
    std::thread th2(carriage);
 
    th1.join();
    th2.join();
 
    return 0;
}
std::unique_lock::unlock

解锁操作,调用它所管理的 Mutex 对象的 unlock 函数。

 请看下面例子(参考):

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock, std::defer_lock
std::mutex mtx;           // mutex for critical section
void print_thread_id (int id) {
  std::unique_lock<std::mutex> lck (mtx,std::defer_lock);
  // critical section (exclusive access to std::cout signaled by locking lck):
  lck.lock();
  std::cout << "thread #" << id << '\n';
  lck.unlock();
}
int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(print_thread_id,i+1);
 
  for (auto& th : threads) th.join();
 
  return 0;
}
std::unique_lock::release

返回指向它所管理的 Mutex 对象的指针,并释放所有权。

 请看下面例子(参考):

#include <iostream>       // std::cout
#include <vector>         // std::vector
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock

std::mutex mtx;
int count = 0;

void print_count_and_unlock (std::mutex* p_mtx) {
  std::cout << "count: " << count << '\n';
  p_mtx->unlock();
}

void task() {
  std::unique_lock<std::mutex> lck(mtx);
  ++count;
  print_count_and_unlock(lck.release());
}

int main ()
{
  std::vector<std::thread> threads;
  for (int i=0; i<10; ++i)
    threads.emplace_back(task);
 
  for (auto& x: threads) x.join();
 
  return 0;
}
std::unique_lock::owns_lock

返回当前 std::unique_lock 对象是否获得了锁。 

请看下面例子(参考):

#include <iostream>       // std::cout
#include <vector>         // std::vector
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock, std::try_to_lock
std::mutex mtx;           // mutex for critical section

void print_star () {
  std::unique_lock<std::mutex> lck(mtx,std::try_to_lock);
  // print '*' if successfully locked, 'x' otherwise: 
  if (lck.owns_lock())
    std::cout << '*';
  else                    
    std::cout << 'x';
}

int main ()
{
  std::vector<std::thread> threads;
  for (int i=0; i<500; ++i)
    threads.emplace_back(print_star);
 
  for (auto& x: threads) x.join();
 
  return 0;
}
std::unique_lock::operator bool()

与 owns_lock 功能相同,返回当前 std::unique_lock 对象是否获得了锁。

 请看下面例子(参考):

#include <iostream>       // std::cout
#include <vector>         // std::vector
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock, std::try_to_lock
std::mutex mtx;           // mutex for critical section
void print_star () {
  std::unique_lock<std::mutex> lck(mtx,std::try_to_lock);
  // print '*' if successfully locked, 'x' otherwise: 
  if (lck)
    std::cout << '*';
  else                    
    std::cout << 'x';
}
int main ()
{
  std::vector<std::thread> threads;
  for (int i=0; i<500; ++i)
    threads.emplace_back(print_star);
 
  for (auto& x: threads) x.join();
 
  return 0;
}
std::unique_lock::mutex

返回当前 std::unique_lock 对象所管理的 Mutex 对象的指针。 

请看下面例子(参考):

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock, std::defer_lock

class MyMutex : public std::mutex {
  int _id;
public:
  MyMutex (int id) : _id(id) {}
  int id() {return _id;}
};
 
MyMutex mtx (101);

void print_ids (int id) 
{
  std::unique_lock<MyMutex> lck (mtx);
  std::cout << "thread #" << id << " locked mutex " << lck.mutex()->id() << '\n';
}

int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(print_ids,i+1);
 
  for (auto& th : threads) th.join();
 
  return 0;

std::lock_guard和std:: std::unique_lock总结

参考资料Zeekr
《c++ 11并发指南》

############################################

std::unique_lock 与std::lock_guard都能实现自动加锁与解锁功能,但是std::unique_lock要比std::lock_guard更灵活,但是更灵活的代价是占用空间相对更大一点且相对更慢一点。
通过实现一个线程安全的队列来说明两者之间的差别。

template <typename T>
class ThreadSafeQueue{
public:
         void Insert(T value);
         void Popup(T &value);
         bool Empety();
 
private:
       mutable std::mutex mut_;
       std::queue<T> que_;
       std::condition_variable cond_;
};
 
 
template <typename T>
void ThreadSafeQueue::Insert(T value){
    std::lock_guard<std::mutex> lk(mut_);
    que_.push_back(value);
    cond_.notify_one();
}
 
 
template <typename T>
void ThreadSafeQueue::Popup(T &value){
    std::unique_lock<std::mutex> lk(mut_);
    cond_.wait(lk, [this]{return !que_.empety();});
    value = que_.front();
    que_.pop();
}
 
 
template <typename T>
bool ThreadSafeQueue::Empty() const{
        std::lock_guard<std::mutex> lk(mut_);
        return que_.empty();
}

     上面代码只实现了关键的几个函数,并使用了C++11新引入的condition_variable条件变量。从Popup与Inert两个函数看std::unique_lock相对std::lock_guard更灵活的地方在于在等待中的线程如果在等待期间需要解锁mutex,并在之后重新将其锁定。而std::lock_guard却不具备这样的功能。
上面代码中

cond_.wait(lk, [this]{return !Empety();});

可能会比较难以理解,

[this]{return !Empety();}

 是C++11新引入的功能,lambda表达式,是一种匿名函数。方括号内表示捕获变量。当lambda表达式返回true时(即queue不为空),wait函数会锁定mutex。当lambda表达式返回false时,wait函数会解锁mutex同时会将当前线程置于阻塞或等待状态。
还存在另一种读写锁,但是并没有引入C++11,但是boost库提供了对应的实现。读写锁主要适合在于共享数据更新频率较低,但是读取共享数据频率较高的场合。

std::shared_lock(C++14)介绍

实现可移动的共享互斥体所有权封装器(类模板)

在标头 <shared_mutex> 定义

template< class Mutex >
class shared_lock;

(C++14 起)

类 shared_lock 是通用共享互斥所有权包装器,允许延迟锁定、定时锁定和锁所有权的转移。锁定 shared_lock ,会以共享模式锁定关联的共享互斥( std::unique_lock 可用于以排他性模式锁定)。

shared_lock 类可移动,但不可复制——它满足可移动构造 (MoveConstructible) 可移动赋值 (MoveAssignable) 的要求,但不满足可复制构造 (CopyConstructible) 可复制赋值 (CopyAssignable) 

shared_lock 符合可锁定 (Lockable) 要求。若 Mutex 符合可共享定时锁定 (SharedTimedLockable) 要求,则 shared_lock 亦符合 可定时锁定 (TimedLockable) 要求。

为以共享所有权模式等待于共享互斥,可使用 std::condition_variable_any ( std::condition_variable 要求 std::unique_lock 故而只能以唯一所有权模式等待)。

模板形参

Mutex-要锁定的共享互斥类型。类型必须符合可共享锁定 (SharedLockable) 要求。

成员类型

类型定义
mutex_typeMutex

成员函数

(构造函数)

构造 shared_lock ,可选地锁定提供的互斥
(公开成员函数)

(析构函数)

解锁关联的互斥
(公开成员函数)

operator=

若占有则解锁互斥,然后获得对方的所有权
(公开成员函数)
共享锁定

lock

锁定关联的互斥
(公开成员函数)

try_lock

尝试锁定关联的互斥
(公开成员函数)

try_lock_for

尝试锁定关联的互斥,以指定时长
(公开成员函数)

try_lock_until

尝试锁定关联的互斥,直至指定的时间点
(公开成员函数)

unlock

解锁关联的互斥
(公开成员函数)
修改器

swap

与另一 shared_lock 交换数据成员
(公开成员函数)

release

解除关联 mutex 而不解锁
(公开成员函数)
观察器

mutex

返回指向关联的互斥的指针
(公开成员函数)

owns_lock

测试锁是否占有其关联的互斥
(公开成员函数)

operator bool

测试锁是否占有其关联的互斥
(公开成员函数)

非成员函数

std::swap(std::shared_lock)

(C++14)

std::swap 对 shared_lock 的特化
(函数模板)

详解(待补充)

std::scoped_lock(C++17)介绍

用于多个互斥体的免死锁 RAII 封装器(类模板) 

在标头 <mutex> 定义

template< class... MutexTypes >
class scoped_lock;

(C++17 起)

类 scoped_lock 是提供便利 RAII 风格机制的互斥包装器,它在作用域块的存在期间占有一或多个互斥。

创建 scoped_lock 对象时,它试图取得给定互斥的所有权。控制离开创建 scoped_lock 对象的作用域时,析构 scoped_lock 并释放互斥。若给出数个互斥,则使用免死锁算法,如同以 std::lock 。

scoped_lock 类不可复制。

模板形参

MutexTypes-要锁定的互斥类型。类型必须满足可锁定 (Lockable) 要求,除非 sizeof...(MutexTypes)==1 ,该情况下唯一的类型必须满足可基本锁定 (BasicLockable)

成员类型

成员类型定义
mutex_type (若 sizeof...(MutexTypes)==1)Mutex , MutexTypes... 中的单独类型

成员函数

(构造函数)

构造 scoped_lock ,可选地锁定给定的互斥
(公开成员函数)

(析构函数)

析构 scoped_lock 对象,解锁底层互斥
(公开成员函数)

operator=

[弃置]

不可复制
(公开成员函数)

示例(参考)

 以下示例用 std::scoped_lock 锁定互斥对而不死锁,且为 RAII 风格。

#include <mutex>
#include <thread>
#include <iostream>
#include <vector>
#include <functional>
#include <chrono>
#include <string>
 
struct Employee {
    Employee(std::string id) : id(id) {}
    std::string id;
    std::vector<std::string> lunch_partners;
    std::mutex m;
    std::string output() const
    {
        std::string ret = "Employee " + id + " has lunch partners: ";
        for( const auto& partner : lunch_partners )
            ret += partner + " ";
        return ret;
    }
};
 
void send_mail(Employee &, Employee &)
{
    // 模拟耗时的发信操作
    std::this_thread::sleep_for(std::chrono::seconds(1));
}
 
void assign_lunch_partner(Employee &e1, Employee &e2)
{
    static std::mutex io_mutex;
    {
        std::lock_guard<std::mutex> lk(io_mutex);
        std::cout << e1.id << " and " << e2.id << " are waiting for locks" << std::endl;
    }
 
    {
        // 用 std::scoped_lock 取得二个锁,而无需担心
        // 其他对 assign_lunch_partner 的调用死锁我们
        // 而且它亦提供便利的 RAII 风格机制
 
        std::scoped_lock lock(e1.m, e2.m);
 
        // 等价代码 1 (用 std::lock 和 std::lock_guard )
        // std::lock(e1.m, e2.m);
        // std::lock_guard<std::mutex> lk1(e1.m, std::adopt_lock);
        // std::lock_guard<std::mutex> lk2(e2.m, std::adopt_lock);
 
        // 等价代码 2 (若需要 unique_lock ,例如对于条件变量)
        // std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
        // std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
        // std::lock(lk1, lk2);
        {
            std::lock_guard<std::mutex> lk(io_mutex);
            std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;
        }
        e1.lunch_partners.push_back(e2.id);
        e2.lunch_partners.push_back(e1.id);
    }
 
    send_mail(e1, e2);
    send_mail(e2, e1);
}
 
int main()
{
    Employee alice("alice"), bob("bob"), christina("christina"), dave("dave");
 
    // 在并行线程中指派,因为就午餐指派发邮件消耗很长时间
    std::vector<std::thread> threads;
    threads.emplace_back(assign_lunch_partner, std::ref(alice), std::ref(bob));
    threads.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(bob));
    threads.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(alice));
    threads.emplace_back(assign_lunch_partner, std::ref(dave), std::ref(bob));
 
    for (auto &thread : threads) thread.join();
    std::cout << alice.output() << '\n'  << bob.output() << '\n'
              << christina.output() << '\n' << dave.output() << '\n';
}

可能的输出:

alice and bob are waiting for locks
alice and bob got locks
christina and bob are waiting for locks
christina and alice are waiting for locks
dave and bob are waiting for locks
dave and bob got locks
christina and alice got locks
christina and bob got locks
Employee alice has lunch partners: bob christina 
Employee bob has lunch partners: alice dave christina 
Employee christina has lunch partners: alice bob 
Employee dave has lunch partners: bob

详解(待补充)

tag类小结:

std::defer_lock_t, std::try_to_lock_t, std::adopt_lock_t

在标头 <mutex> 定义

struct defer_lock_t { explicit defer_lock_t() = default; };

struct try_to_lock_t { explicit try_to_lock_t() = default; };

struct adopt_lock_t { explicit adopt_lock_t() = default; };
(C++11 起)

std::defer_lock_t 、 std::try_to_lock_t 和 std::adopt_lock_t 是用于为 std::lock_guard 、 std::scoped_lock 、 std::unique_lock 和 std::shared_lock 指定锁定策略的空类标签类型。

类型效果
defer_lock_t不获得互斥的所有权
try_to_lock_t尝试获得互斥的所有权而不阻塞
adopt_lock_t假设调用方线程已拥有互斥的所有权

 示例(参考):

#include <mutex>
#include <thread>
 
struct bank_account {
    explicit bank_account(int balance) : balance(balance) {}
    int balance;
    std::mutex m;
};
 
void transfer(bank_account &from, bank_account &to, int amount)
{
    // 锁定两个互斥而不死锁
    std::lock(from.m, to.m);
    // 保证二个已锁定互斥在作用域结尾解锁
    std::lock_guard<std::mutex> lock1(from.m, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(to.m, std::adopt_lock);
 
// 等价方法:
//    std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
//    std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);
//    std::lock(lock1, lock2);
 
    from.balance -= amount;
    to.balance += amount;
}
 
int main()
{
    bank_account my_account(100);
    bank_account your_account(50);
 
    std::thread t1(transfer, std::ref(my_account), std::ref(your_account), 10);
    std::thread t2(transfer, std::ref(your_account), std::ref(my_account), 5);
 
    t1.join();
    t2.join();
}

std::defer_lock, std::try_to_lock, std::adopt_lock

定义于头文件 <mutex>

constexpr std::defer_lock_t defer_lock {};

(C++11 起)
(C++17 前)

inline constexpr std::defer_lock_t defer_lock {};

(C++17 起)

constexpr std::try_to_lock_t try_to_lock {};

(C++11 起)
(C++17 前)

inline constexpr std::try_to_lock_t try_to_lock {};

(C++17 起)

constexpr std::adopt_lock_t adopt_lock {};

(C++11 起)
(C++17 前)

inline constexpr std::adopt_lock_t adopt_lock {};

(C++17 起)

std::defer_lock 、 std::try_to_lock 和 std::adopt_lock 分别是空结构体标签类型 std::defer_lock_t 、 std::try_to_lock_t 和 std::adopt_lock_t 的实例。

它们用于为 std::lock_guard 、 std::unique_lock 及 std::shared_lock 指定锁定策略。

类型效果
defer_lock_t不获得互斥的所有权
try_to_lock_t尝试获得互斥的所有权而不阻塞
adopt_lock_t假设调用方线程已拥有互斥的所有权

示例(参考)

#include <mutex>
#include <thread>
 
struct bank_account {
    explicit bank_account(int balance) : balance(balance) {}
    int balance;
    std::mutex m;
};
 
void transfer(bank_account &from, bank_account &to, int amount)
{
    // 锁定两个互斥而不死锁
    std::lock(from.m, to.m);
    // 保证二个已锁定互斥在作用域结尾解锁
    std::lock_guard<std::mutex> lock1(from.m, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(to.m, std::adopt_lock);
 
// 等价方法:
//    std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
//    std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);
//    std::lock(lock1, lock2);
 
    from.balance -= amount;
    to.balance += amount;
}
 
int main()
{
    bank_account my_account(100);
    bank_account your_account(50);
 
    std::thread t1(transfer, std::ref(my_account), std::ref(your_account), 10);
    std::thread t2(transfer, std::ref(your_account), std::ref(my_account), 5);
 
    t1.join();
    t2.join();
}

通用锁定算法介绍

try_lock

(C++11)

试图通过重复调用 try_lock 获得互斥体的所有权
(函数模板)

lock

(C++11)

锁定指定的互斥体,若任何一个不可用则阻塞
(函数模板)

std::try_lock介绍

定义于头文件 <mutex>

template< class Lockable1, class Lockable2, class... LockableN>
int try_lock( Lockable1& lock1, Lockable2& lock2, LockableN&... lockn);

(C++11 起)

尝试锁定每个给定的可锁定 (Lockable) 对象 lock1 、 lock2 、 ... 、 lockn ,通过以从头开始的顺序调用 try_lock 。

若调用 try_lock 失败,则不再进一步调用 try_lock ,并对任何已锁对象调用 unlock ,返回锁定失败对象的 0 底下标。

若调用 try_lock 抛出异常,则在重抛前对任何已锁对象调用 unlock 。

参数

lock1, lock2, ... , lockn-要锁定的可锁定 (Lockable) 对象

返回值

成功时为 -1 ,否则为锁定失败对象的 0 底下标值。

示例(参考)

下列示例用 std::try_lock 周期地记录并重置运行于分离线程的计数器。

#include <mutex>
#include <vector>
#include <thread>
#include <iostream>
#include <functional>
#include <chrono>
 
int main()
{
    int foo_count = 0;
    std::mutex foo_count_mutex;
    int bar_count = 0;
    std::mutex bar_count_mutex;
    int overall_count = 0;
    bool done = false;
    std::mutex done_mutex;
 
    auto increment = [](int &counter, std::mutex &m,  const char *desc) {
        for (int i = 0; i < 10; ++i) {
            std::unique_lock<std::mutex> lock(m);
            ++counter;
            std::cout << desc << ": " << counter << '\n';
            lock.unlock();
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    };
 
    std::thread increment_foo(increment, std::ref(foo_count), 
        std::ref(foo_count_mutex), "foo");
    std::thread increment_bar(increment, std::ref(bar_count), 
        std::ref(bar_count_mutex), "bar");
 
    std::thread update_overall([&]() {
        done_mutex.lock();
        while (!done) {
            done_mutex.unlock();
            int result = std::try_lock(foo_count_mutex, bar_count_mutex);
            if (result == -1) {
                overall_count += foo_count + bar_count;
                foo_count = 0;
                bar_count = 0;
                std::cout << "overall: " << overall_count << '\n';
                foo_count_mutex.unlock();
                bar_count_mutex.unlock();
            }
            std::this_thread::sleep_for(std::chrono::seconds(2));
            done_mutex.lock();
        }
        done_mutex.unlock();
    });
 
    increment_foo.join();
    increment_bar.join();
    done_mutex.lock();
    done = true;
    done_mutex.unlock();
    update_overall.join();
 
    std::cout << "Done processing\n"
              << "foo: " << foo_count << '\n'
              << "bar: " << bar_count << '\n'
              << "overall: " << overall_count << '\n';
}

可能的输出:

bar: 1
foo: 1
foo: 2
bar: 2
foo: 3
overall: 5
bar: 1
foo: 1
bar: 2
foo: 2
bar: 3
overall: 10
bar: 1
foo: 1
bar: 2
foo: 2
overall: 14
bar: 1
foo: 1
bar: 2
overall: 17
foo: 1
bar: 1
foo: 2
overall: 20
Done processing
foo: 0
bar: 0
overall: 20

std::lock介绍

定义于头文件 <mutex>

template< class Lockable1, class Lockable2, class... LockableN >
void lock( Lockable1& lock1, Lockable2& lock2, LockableN&... lockn );

(C++11 起)

锁定给定的可锁定 (Lockable) 对象 lock1 、 lock2 、 ... 、 lockn ,用免死锁算法避免死锁。

以对 lock 、 try_lock 和 unlock 的未指定系列调用锁定对象。若调用 lock 或 unlock 导致异常,则在重抛前对任何已锁的对象调用 unlock 。

参数

lock1, lock2, ... , lockn-要锁定的可锁定 (Lockable) 对象

返回值

(无)

注意

Boost 提供此函数的一个版本,它接收以一对迭代器定义的可锁定 (Lockable) 对象序列。

std::scoped_lock 提供此函数的 RAII 包装,通常它比裸调用 std::lock 更好。

示例(参考)

下列示例用 std::lock 锁定互斥对,而不死锁。

#include <mutex>
#include <thread>
#include <iostream>
#include <vector>
#include <functional>
#include <chrono>
#include <string>
 
struct Employee {
    Employee(std::string id) : id(id) {}
    std::string id;
    std::vector<std::string> lunch_partners;
    std::mutex m;
    std::string output() const
    {
        std::string ret = "Employee " + id + " has lunch partners: ";
        for( const auto& partner : lunch_partners )
            ret += partner + " ";
        return ret;
    }
};
 
void send_mail(Employee &, Employee &)
{
    // 模拟耗时的发信操作
    std::this_thread::sleep_for(std::chrono::seconds(1));
}
 
void assign_lunch_partner(Employee &e1, Employee &e2)
{
    static std::mutex io_mutex;
    {
        std::lock_guard<std::mutex> lk(io_mutex);
        std::cout << e1.id << " and " << e2.id << " are waiting for locks" << std::endl;
    }
 
    // 用 std::lock 获得二个锁,而不担心对 assign_lunch_partner 的其他调用会死锁我们
    {
        std::lock(e1.m, e2.m);
        std::lock_guard<std::mutex> lk1(e1.m, std::adopt_lock);
        std::lock_guard<std::mutex> lk2(e2.m, std::adopt_lock);
// 等价代码(若需要 unique_locks ,例如对于条件变量)
//        std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
//        std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
//        std::lock(lk1, lk2);
// C++17 中可用的较优解法
//        std::scoped_lock lk(e1.m, e2.m);
        {
            std::lock_guard<std::mutex> lk(io_mutex);
            std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;
        }
        e1.lunch_partners.push_back(e2.id);
        e2.lunch_partners.push_back(e1.id);
    }
    send_mail(e1, e2);
    send_mail(e2, e1);
}
 
int main()
{
    Employee alice("alice"), bob("bob"), christina("christina"), dave("dave");
 
    // 在平行线程指派,因为发邮件给用户告知午餐指派,会消耗长时间
    std::vector<std::thread> threads;
    threads.emplace_back(assign_lunch_partner, std::ref(alice), std::ref(bob));
    threads.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(bob));
    threads.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(alice));
    threads.emplace_back(assign_lunch_partner, std::ref(dave), std::ref(bob));
 
    for (auto &thread : threads) thread.join();
    std::cout << alice.output() << '\n'  << bob.output() << '\n'
              << christina.output() << '\n' << dave.output() << '\n';
}

可能的输出: 

alice and bob are waiting for locks
alice and bob got locks
christina and bob are waiting for locks
christina and bob got locks
christina and alice are waiting for locks
christina and alice got locks
dave and bob are waiting for locks
dave and bob got locks
Employee alice has lunch partners: bob christina 
Employee bob has lunch partners: alice christina dave 
Employee christina has lunch partners: bob alice 
Employee dave has lunch partners: bob

 单次调用 介绍

once_flag

(C++11)

确保 call_once 只调用函数一次的帮助对象
(类)

call_once

(C++11)

仅调用函数一次,即使从多个线程调用
(函数模板)

std::once_flag介绍

定义于头文件 <mutex>

class once_flag;

(C++11 起)

类 std::once_flag 是 std::call_once 的辅助类。

传递给多个 std::call_once 调用的 std::once_flag 对象允许那些调用彼此协调,从而只令调用之一实际运行完成。

std::once_flag 既不可复制亦不可移动。

成员函数

std::once_flag::once_flag

constexpr once_flag() noexcept;

构造 once_flag 对象。设置内部状态为指示尚未调用函数。

参数

(无)

std::call_once介绍

定义于头文件 <mutex>

template< class Callable, class... Args >
void call_once( std::once_flag& flag, Callable&& f, Args&&... args );

(C++11 起)


准确执行一次可调用 (Callable) 对象 f ,即使同时从多个线程调用。

细节为:

  • 若在调用 call_once 的时刻, flag 指示已经调用了 f ,则 call_once 立即返回(称这种对 call_once 的调用为消极)。
  • 否则, call_once 以参数 std::forward<Args>(args)... 调用 std::forward<Callable>(f) (如同用 std::invoke )。不同于 std::thread 构造函数或 std::async ,不移动或复制参数,因为不需要转移它们到另一执行线程(称这种对 call_once 的调用为积极)。
    • 若该调用抛异常,则传播异常给 call_once 的调用方,并且不翻转 flag ,以令其他调用将得到尝试(称这种对 call_once 的调用为异常)。
    • 若该调用正常返回(称这种对 call_once 的调用为返回),则翻转 flag ,并保证以同一 flag 对 call_once 的其他调用为消极。

同一 flag 上的所有积极调用组成单独全序,它们由零或多个异常调用后随一个返回调用组成。该顺序中,每个积极调用的结尾同步于下个积极调用。

从返回调用的返回同步于同一 flag 上的所有消极调用:这表示保证所有对 call_once 的同时调用都观察到积极调用所做的任何副效应,而无需额外同步。

参数
flag    -    对象,对于它只有一个函数得到执行
     f    -    要调用的可调用 (Callable) 对象
args...    -    传递给函数的参数
返回值(无)

异常

  • 若任何条件阻止对 call_once 的调用按规定执行,则抛出 std::system_error
  • 任何 f 所抛的异常

注解
若对 call_once 的同时调用传递不同的 f ,则调用哪个 f 是未指定的。被选择函数运行于与传递它的 call_once 的调用相同的线程。

即使在从多个线程调用时,也保证函数局域静态对象的初始化仅出现一次,这可能比使用 std::call_once 的等价代码更为高效。

此函数的 POSIX 类似物是pthread_once 。

示例(参考)

#include <iostream>
#include <thread>
#include <mutex>
 
std::once_flag flag1, flag2;
 
void simple_do_once()
{
    std::call_once(flag1, [](){ std::cout << "Simple example: called once\n"; });
}
 
void may_throw_function(bool do_throw)
{
  if (do_throw) {
    std::cout << "throw: call_once will retry\n"; // 这会出现多于一次
    throw std::exception();
  }
  std::cout << "Didn't throw, call_once will not attempt again\n"; // 保证一次
}
 
void do_once(bool do_throw)
{
  try {
    std::call_once(flag2, may_throw_function, do_throw);
  }
  catch (...) {
  }
}
 
int main()
{
    std::thread st1(simple_do_once);
    std::thread st2(simple_do_once);
    std::thread st3(simple_do_once);
    std::thread st4(simple_do_once);
    st1.join();
    st2.join();
    st3.join();
    st4.join();
 
    std::thread t1(do_once, true);
    std::thread t2(do_once, true);
    std::thread t3(do_once, false);
    std::thread t4(do_once, true);
    t1.join();
    t2.join();
    t3.join();
    t4.join();
}

可能的输出:

Simple example: called once
throw: call_once will retry
throw: call_once will retry
Didn't throw, call_once will not attempt again

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值