C++并发:在线程间共享数据

打印 上一主题 下一主题

主题 946|帖子 946|积分 2838

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1 线程间共享数据的标题


1.1 条件竞争

条件竞争:在并发编程中:操纵由两个或多个线程负责,它们抢先让线程执行各自的操纵,而结果取决于它们执行的相对序次,这样的环境就是条件竞争。
诱发恶性条件竞争的典型场景是,要完成一项操纵,却需要改动两份或多份不同的数据,而它们只能用单独的指令改动,当其中的一份数据完成改动时,别的线程有可能不期而访。而且由于这样的场景出现的时间窗口小,因此一般很难复现场景定位。

1.2 防止恶性条件竞争

有如下方法:
1 接纳掩护步伐包装数据结构,确保中间状态只对执行改动的线程可见。
2 修改设计,由一连串不可拆分的改动完成数据变更,每个改动都维持不变量不被破坏。这通常称为无锁编程,难以精确编写。如果从事这一层面的开辟,就要探究内存模型的细节,以及区分每个线程可以大概看到什么数据集。
3 修改数据结构来看成事件处理。

2 用互斥掩护共享数据

访问一个数据结构前,先锁住与数据干系的互斥,访问竣过后再解锁互斥。C++线程库包管了,一旦有线程锁住了某个互斥,若其他线程试图再给他加锁,需要等待。
互斥也可能带来某些标题,好比死锁,对数据的过掩护和欠掩护。
2.1 std::mutex

C++中使用std::mutex的实例来构造互斥。
可以通过成员函数lock()对其加锁,unlock()进行解锁。但是并不保举直接调用成员函数,原因是这样需要记住在函数以外的每条代码路径都要调用unlock(),包罗异常退出的路径。
取而代之,C++便准库提供了模板std::lock_guard<>,针对互斥类融合实现了RAII:在构造时加锁,在析构时解锁,从而包管互斥总被精确解锁。
  1. #include <list>
  2. #include <mutex>
  3. #include <algorithm>
  4. std::list<int> some_list;
  5. std::mutex some_mutex;
  6. void add_to_list(int new_value) {
  7.     std::lock_guard<std::mutex> guard(some_mutex);
  8.     some_list.push_back(new_value);
  9. }
  10. bool list_contains(int value_to_find) {
  11.     std::lock_guard<std::mutex> guard(some_mutex);
  12.     return std::find(some_list.begin(), some_list.end(), value_to_find) != some_list.end();
  13. }
复制代码
C++17支持了模板参数推导,使得上述实现可以写成如下样式。而且引入了std::scoped_lock,他是增强版的lock_guard
  1. std::lock_guard guard(some_mutex);
  2. std::scoped_guard guard(some_mutex);
复制代码

2.2 指针和引用冲破互斥掩护


如果成员函数返回指针或引用,指向受掩护的数据,那么即便成员函数全部按良好、有序的方式锁定互斥,仍然会无济于事。
只要存在任何能访问该指针和引用的代码,它就可以访问受掩护的共享数据,而无需锁定互斥。因此,利用互斥掩护共享数据,需要谨慎设计步伐接口,从而包管互斥已先行锁定,再对受掩护的共享数据进行访问。

2.3 组织和编排代码以掩护共享数据


我们除了要防止成员函数向调用者传出指针或者引用,还要注意成员函数内部调用的别的函数,也不要向这些函数传递指针或者引用。
  1. #include <mutex>
  2. #include <string>
  3. class some_data {
  4.     int a;
  5.     std::string b;
  6. public:
  7.     void do_something();
  8. };
  9. class data_wrapper {
  10. private:
  11.     some_data data;
  12.     std::mutex m;
  13. public:
  14.     template<typename Function>
  15.     void process_data(Function func) {
  16.         std::lock_guard<std::mutex> l(m);
  17.         func(data);
  18.     }
  19. };
  20. some_data* unprotected;
  21. void malicious_function(some_data& protected_data) {
  22.     unprotected=&protected_data;
  23. }
  24. data_wrapper x;
  25. void foo() {
  26.     x.process_data(malicious_function);
  27.     unprotected->do_something();
  28. }
复制代码
好比上述代码,malicious_function方法将被互斥锁掩护的data_wrapper中的some_data的引用赋值给外面的unprotected,导致互斥掩护被冲破,在外面可直接通过unprotected进行操纵。
2.4 发现接口固有的条件竞争


  1. #include <deque>
  2. template<typename T, typename Container=std::deque<T>>
  3. class stack {
  4. public:
  5.     explicit stack(const Container&);
  6.     explicit stack(Container&& = Container());
  7.     template <class Alloc> explicit stack(const Alloc&);
  8.     template <class Alloc> stack(const Container&, const Alloc&);
  9.     template <class Alloc> stack(Container&, const Alloc&);
  10.     template <class Alloc> stack(stack&&, const Alloc&);
  11.     bool empty() const;
  12.     size_t size() const;
  13.     T& top();
  14.     T const& top() const;
  15.     void push(T const&);
  16.     void push(T&&);
  17.     void pop();
  18.     void swap(stack&&);
  19.     template <class... Args> void emplace(Args&&... args);
  20. };
复制代码
上述实现会导致条件竞争,也就是empty和size的结果不可信,因为在函数返回后,其他线程不再受限,可能立刻会有新元素入栈或者出栈。
线程1线程2
if(!s.empty())
if(!s.empty())
    int const value=s.top();
    int const value=s.top();
    s.pop();
    do_something(value);    s.pop();
    do_something(value);
这样,当一个栈只有一个元素的时候,第二个pop的线程会导致未界说行为。
而且,当我们复制vector时,如果vector中的元素数量巨大,可能导致因为资源不足造成的内存分配失败。pop函数的界说是,返回栈顶元素的值,而且将其从栈顶移除。因此,只有在栈被改动之后,弹出的元素才返回给调用者,然而在向调用者复制数据的过程中,有可能抛出异常。万一弹出的元素已经从栈上移除,但是复制不乐成,就会造成数据丢失。

2.4.1 消除竞争


2.4.1.1 传入引用

  1. std::vector<int> result;
  2. some_stack.pop(result);
复制代码
长处:pop的元素在外部容器白村了生命周期
缺点:如果要调用pop,还要先闯将一个别的容器。

2.4.1.2 提供不抛出异常的拷贝构造函数,或不抛出异常的移动构造函数

这样虽然安全,但是结果并不理想。栈容器的用途会受限。

2.4.1.3 返回指针,指向弹出元素

长处:指针可以自由的复制,不会抛出异常。
缺点:指向的对象仍然在内存中,需要额外的内存管理,可以使用shared_ptr。

2.4.1.4 联合1,2或者1,3


2.4.1.5 线程安全的栈容器


  1. #include <exception>
  2. #include <memory>
  3. #include <mutex>
  4. #include <stack>
  5. struct empty_stack: std::exception {
  6.     const char* what() const throw();
  7. };
  8. template<typename T>
  9. class threadsafe_stack {
  10. private:
  11.     std::stack<T> data;
  12.     mutable std::mutex m;
  13. public:
  14.     threadsafe_stack() {}
  15.     threadsafe_stack(const threadsafe_stack& other) {
  16.         std::lock_guard<std::mutex> lock(other.m);
  17.         data=other.data;
  18.     }
  19.     threadsafe_stack operator=(const threadsafe_stack&) = delete;
  20.     void push(T new_value) {
  21.         std::lock_guard<std::mutex> lock(m);
  22.         data.push(std::move(new_value));
  23.     }
  24.     std::shared_ptr<T> pop() {
  25.         std::lock_guard<std::mutex> lock(m);
  26.         if (data.empty()) throw empty_stack();
  27.         std::shared_ptr<T> const res(std::make_shared<T>(data.top()));
  28.         data.pop();
  29.         return res;
  30.     }
  31.     void pop(T& value) {
  32.         std::lock_guard<std::mutex> lock(m);
  33.         if (data.empty()) throw empty_stack();
  34.         value = data.pop();
  35.         data.pop();
  36.     }
  37.     bool empty() const {
  38.         std::lock_guard<std::mutex> lock(m);
  39.         return data.empty();
  40.     }
  41. };
复制代码

2.5 死锁:标题息争决方法


防范死锁的发起通常是,始终按照相同的序次对两个互斥加锁。
C++尺度提供了std::lock函数,使得可以同时锁住多个互斥。
  1. #include <mutex>
  2. class some_big_object;
  3. void swap(some_big_object& lhs, some_big_object& rhs);
  4. class X {
  5. private:
  6.     some_big_object some_detail;
  7.     std::mutex m;
  8. public:
  9.     X(some_big_object const& sd) : some_detail(sd){}
  10.     friend void swap(X& lhs, X& rhs);
  11.     {
  12.         if (&lhs == & rhs)
  13.             return;
  14.         std::lock(lhs.m, rhs.m);
  15.         std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);
  16.         std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);
  17.         swap(lhs.some_detail, rhs.some_detail);
  18.     }
  19. };
复制代码
std::adopt_lock对象指明确互斥已被锁住,即互斥上有锁存在。std::lock_guard实例据此接收锁的归属权,不会在构造函数内试图另行加锁。
无论是正常返回还是异常退出,std::lock_guard都包管了互斥全都精确解锁。
别的,lock()对lhs.m或rhs.m进行加锁,这一函数调用可能导致抛出异常。
C++17还提供了全新的特性std::scoped_lock<>。它和std::lock_guard<>完全等价。只不外前者是可变参数模板,接收各种互斥型别作为模板参数列表,还能以多个互斥对象作为构造函数的参数列表。
  1. void swap(X& lhs, X& rhs)
  2. {
  3.     if (&lhs==&rhs)
  4.         return;
  5.     std::scoped_lock guard(lhs.m, rhs.m);
  6.     swap(lhs.some_detail, rhs.some_detail);
  7. }
复制代码
使用新特性实现如上,而且上述代码还是用了类模板参数推导(C++17)。使用std::scoped_lock将lock和lock_guard合并为一句,降低堕落概率。

2.6 防范死锁的补充准则


即使没有牵涉锁,也会发生死锁征象。假定有两个线程,各自关联了std::thread实例,若同时在对方的std::thread实例上调用join,那么就能制造出死锁征象。
防范死锁的最终准则:只要另一个线程有可能正在等待当前线程,那么当前线程不要反过来等待他。

2.6.1 避免嵌套锁

假如已经持有锁,就不要试图获取第二个锁。这样包管每个线程最多只持有一个锁,仅锁的使用自己不可能导致锁。
但是还存在其他可能引起死锁的场景(好比多个线程彼此等待),操纵多个互斥锁很可能是最常见的死锁诱因。如果真的需要获取多个锁,应使用lock函数,单独的调用动作一次获取全部锁来避免死锁。

2.6.2 一旦持锁,就须避免调用由用户提供的步伐接口

若步伐接口由用户自行实现,则我们无从得知它到底会做什么,可能会试图获取锁。这样便可能违反避免嵌套锁的准则,可能发生死锁。
不外偶然候这个环境难以避免,因此在需要调用用户提供的步伐接口时,要服从2.6.3准则。

2.6.3 依从固定序次获取锁

如果多个锁是绝对须要的,却无法通过std::lock()在一步操纵内全部获取,我们只能退而求其次,在每个线程内部依从固定序次获取这些锁。
也可以同时给这些互斥加锁。
或者对于双向链表来说,规定遍历的方向,让线程总是必须先锁住A,再锁住B,也可以防范死锁。

2.6.4 按层级加锁

锁的层级划分就是按照特定的方式规定加锁序次,在运行期据此查验加锁操纵是否遵从预设规则。若某个线程已对低层级互斥加锁,则不准它再对高层级互斥加锁。不外这种模式C++尺度库尚未提供支持,自行实现如下:
  1. #include <limits.h>
  2. #include <mutex>
  3. class hierarchical_mutex {
  4.     std::mutex internal_mutex;
  5.     unsigned long const hierarchy_value;
  6.     unsigned long previous_hierarchy_value;
  7.     static thread_local unsigned long this_thread_hierarchy_value;
  8.     void check_for_hierarchy_violation() {
  9.         if (this_thread_hierarchy_value <= hierarchy_value) {
  10.             throw std::logic_error("mutex hierarchy violated");
  11.         }
  12.     }
  13.     void update_hierarchy_value() {
  14.         previous_hierarchy_value = this_thread_hierarchy_value;
  15.         this_thread_hierarchy_value = hierarchy_value;
  16.     }
  17. public:
  18.     explicit hierarchical_mutex(unsigned long value) :
  19.         hierarchy_value(value),
  20.         previous_hierarchy_value(0) {}
  21.     void lock() {
  22.         check_for_hierarchy_violation();
  23.         internal_mutex.lock();
  24.         update_hierarchy_value();
  25.     }
  26.     void unlock() {
  27.         if (this_thread_hierarchy_value!=hierarchy_value) {
  28.             throw std::logic_error("mutex hierarchy violated");
  29.         }
  30.         this_thread_hierarchy_value = previous_hierarchy_value;
  31.         internal_mutex.unlock();
  32.     }
  33.     bool try_lock() {
  34.         check_for_hierarchy_violation();
  35.         if(!internal_mutex.try_lock()) {
  36.             return false;
  37.         }
  38.         update_hierarchy_value();
  39.         return true;
  40.     }
  41. };
  42. thread_local unsigned long hierarchical_mutex::this_thread_hierarchy_value(ULONG_MAX);
  43. hierarchical_mutex high_level_mutex(10000);
  44. hierarchical_mutex low_level_mutex(5000);
  45. hierarchical_mutex other_mutex(6000);
  46. int do_low_level_stuff();
  47. int low_level_func() {
  48.     std::lock_guard<hierarchical_mutex> lk(low_level_mutex);
  49.     return do_low_level_stuff();
  50. }
  51. int high_level_stuff(int some_param);
  52. int high_level_func() {
  53.     std::lock_guard<hierarchical_mutex> lk(low_level_mutex);
  54.     high_level_stuff(low_level_func());
  55. }
  56. void thread_a() {
  57.     high_level_func();
  58. }
  59. int do_other_stuff();
  60. void other_stuff() {
  61.     high_level_func();
  62.     do_other_stuff();
  63. }
  64. void thread_b() {
  65.     std::lock_guard<hierarchical_mutex> lk(other_mutex);
  66.     other_stuff();
  67. }
复制代码

2.6.5 将准则推广到锁操纵之外

如果要等待线程,那就值得针对线程规定层级,使得每个线程仅等待层级更低的线程。
有一种简单的方法可以实现这种机制:让同一个函数启动全部线程,且汇合工作也由之负责。

2.7 std::unique_lock<>

它与std::lock_guard<>一样,也是一个依据互斥作为参数的类模板,而且使用RAII手法管理锁。
std::unique_lock<>放宽了不变量的建立条件,因此更灵活一些。std::unique_lock对象不一定始终占有与之关联的互斥。
其构造函数接收两个参数:互斥锁和lock实例。
可以传入std::adopt_lock实例,借此指明std::unique_lock对象管理互斥上的锁。
也可以传入std::defer_lock实例,从而使互斥再完成构造时处于无锁状态,等以后有需要时才在std::unique_lock对象(不是互斥对象)上调用lock()而获取锁,或者把std::unique_lock对象交给std::lock()函数加锁。
  1. #include <mutex>
  2. class some_big_object;
  3. void swap(some_big_object& lhs, some_big_object& rhs);
  4. class X {
  5. private:
  6.     some_big_object some_detail;
  7.     std::mutex m;
  8. public:
  9.     X(some_big_object const& sd) : some_detail(sd) {};
  10.     friend void swap(X& lhs, X& rhs) {
  11.         if (&lhs == &rhs) {
  12.             return;
  13.         }
  14.         std::unique_lock<std::mutex> lock_a(lhs.m, std::defer_lock);
  15.         std::unique_lock<std::mutex> lock_b(rhs.m, std::defer_lock);
  16.         std::lock(lock_a, lock_b);
  17.         swap(lhs.some_detail, rhs.some_detail);
  18.     }
  19. };
复制代码

因为std::unique_lock类具有成员lock(),try_lock(),unlock(),所以它的实例可以传给lock()函数。std::unique_lock底层与目标互斥关联,此互斥也有这三个同名函数,因此上述函数调用转由它们执行。
std::unique_lock实例还有一个内部标志,随着函数的执行而更新,表明关联的互斥目前是否正被该类的实例占据。这个标志可以通过owns_lock()查询。
不外,最好还是用C++17提供的变参模板类std::scoped_lock,除非必须用std::unique_lock类进行某些操纵,如转移锁的归属权。

2.8 在不同作用域之间转移互斥归属权


转移有一种用途:答应函数锁定互斥,然后把互斥的归属权转移给函数调用者,好让他在同一个锁的掩护下执行其他操纵。
std::unique_lock实例可以在被烧毁前解锁,这意味着,在执行流程的任意分支上,若某个锁没须要继续持有,则可解锁。这对应用步伐的性能来说很告急。

2.9 按恰当的粒度加锁


持锁期间应避免任何耗时的操纵,如:I/O操纵加锁将毫无须要地壅闭其他线程(文件操纵通常比内存慢几百上千倍)
这种环境可以用std::unique_lock处理:假设代码不再需要访问共享数据,那就调用unlock()解锁,若需要重新访问,就调用lock()加锁。

3 掩护共享数据的其他工具


3.1 仅在初始化过程中掩护共享数据


  1. #include <memory>
  2. #include <mutex>
  3. std::shared_ptr<some_resource> resource_ptr;
  4. std::mutex resource_mutex;
  5. void foo() {
  6.     std::unique_lock<std::mutex> lk(resource_mutex);
  7.     if (!resource_ptr) {
  8.         resource_ptr.reset(new some_resource);
  9.     }
  10.     lk.unlock();
  11.     resource_ptr->do_something();
  12. }
复制代码
上述代码迫使多个线程循序运行,标题较大。为此,其中一个备受诟病的改进就是双重查验锁定模式。
3.1.1 双重查验锁定模式


  1. void undefined_behaviour_with_double_checked_locking() {
  2.     if (!resource_ptr) {
  3.         std::lock_guard<std::mutex> lk(resource_mutex);
  4.         if (!resource_ptr) {
  5.             resource_ptr.reset(new some_resource);
  6.         }
  7.     }
  8.     resource_ptr->do_something();
  9. }
复制代码
这种模式的思绪如下:
在无锁的条件下读取指针,如果为空,获取锁。
为了避免在一个线程进入第一个判定后,其他线程已经获取锁而且已经为resource_ptr赋值,因此在获取锁和赋值之间加入第二个判定,包管不会重复初始化,让resource_ptr被赋值两次。
但是这种思绪可能导致恶性竞争:
在第一个判定,一个线程想要读取resource_ptr的值的时候,另一个线程可能已经获取锁而且正在为resource_ptr执行写操纵。但是这个时候可能new some_resource还未生效被无视,导致前一个线程虽然能在第一个判定时了解到resource_ptr不为空,但是他后面又会走到resource_ptr->do_something(),这个时候使用了一个中间态的数据执行do_somthing()。就产生了读写操纵的不同步。也就是数据竞争,是条件竞争的一种。

3.1.2 std:nce_flag和std::call_once函数

用来专门处理上述数据竞争的环境:
令所有线程共同调用std::call_once()函数,确保在该调用返回时,指针初始化由其中某一线程安全且唯一地完成(通过合适的同步机制)。
须要的同步数据由std:nce_flag实例存储,每个std:nce_flag实例对应一次不同的初始化。
  1. #include <memory>
  2. #include <mutex>
  3. std::shared_ptr<some_resource> resource_ptr;
  4. std::once_flag resource_flag;
  5. void init_resource() {
  6.     resource_ptr.reset(new some_resource);
  7. }
  8. void foo() {
  9.     std::call_once(resource_flag, init_resource);
  10.     resource_ptr->do_something();
  11. }
复制代码
上述代码中的call_once函数包罗两个对象,需要初始化数据(some_resource)和once_flag对象,两者的作用域都完备涵盖了它们所属的名字空间。
3.1.2.2 对于类的数据成员的初始化


  1. #include <mutex>
  2. class X {
  3. private:
  4.     connection_info connection_details;
  5.     connection_handle connection;
  6.     std::once_flag connection_init_flag;
  7.     void open_connection() {
  8.         connection = connection_manager.open(connection_details);
  9.     }
  10. public:
  11.     X(connection_info const& connection_details_) :
  12.         connection_details(connection_details_) {}
  13.     void send_data(data_packet const& data) {
  14.         std::call_once(connection_init_flag, &X::open_connection, this);
  15.         connection.send_data(data);
  16.     }
  17.     data_packet receive_data() {
  18.         std::call_once(connection_init_flag, &X::open_connection, this);
  19.         return connection.receive_data();
  20.     }
  21. };
复制代码
上述代码中的初始化在send或者receive中进行,由于这个时候传入call_once的是类成员,因此需要传入this指针作为附加参数。
std:nce_flag不可复制也不可移动,这点与std::mutex类似。

3.1.2.3 使用静态变量取代成员变量进行初始化


  1. class my_class;
  2. my_class& get_my_class_instance()
  3. {
  4.     static my_class instance;
  5.     return instance;
  6. }
复制代码
把局部变量声明成静态数据,在C++11之后,规定静态数据初始化只会在某一线程单独发生,不会出现多个线程都认为自己应当为其赋值的环境,在初始化完成前,其他线程不会越过静态数据的声明而运行。某些类的代码只需要用到唯逐一个全局实例,这种环境下可以用静态成员取代std::call_once。来让多个线程可以安全地调用上述方法。

3.2 掩护很少更新的数据结构:std::shared_mutex和std::shared_timed_mutex


C++17尺度库提供了两种新的互斥:std::shared_mutex和std::shared_timed_mutex。
C++14尺度库只有std::shared_timed_mutex。
C++11尺度库都没有。可以使用Boost库。
std::shared_mutex相较于std::shared_timed_mutex,后者支持更多的操纵,前者在某些平台上可能会到来额外性能收益。

利用std::shared_mutex实施同步操纵:
更新操纵:使用std::lock_guard<std::shared_mutex>或者std::unique_lock<std::shared_mutex>锁定取代std::mutex.
共享锁:std::shared_lock<std::shared_mutex>,实现共享访问。C++14引入,工作原理是RAII过程。假设它被某些线程持有,要等线程全部开释共享锁,其他线程才能访问排他锁。如果任一线程持有排他锁,其他线程无法获取共享锁以及排他锁,直至排他锁被开释。

  1. #include <map>
  2. #include <string>
  3. #include <mutex>
  4. #include <shared_mutex>
  5. class dnc_entry;
  6. class dns_cache {
  7.     std::map<std::string, dns_entry> entries;
  8.     mutable std::shared_mutex entry_mutex;
  9. public:
  10.     dnc_entry find_entry(std::string const& domain) const {
  11.         std::shared_lock<std::shared_mutex> lk(entry_mutex);
  12.         std::map<std::string, dns_entry>::const_iterator const it = entries.find(domain);
  13.         return (it == entries.end()) ? dns_entry() : it->second;
  14.     }
  15.     void update_or_add_entry(std::string const& domain, dns_entry const& dns_details) {
  16.         std::lock_guard<std::shared_mutex> lk(entry_mutex);
  17.         entries[domain] = dns_details;
  18.     }
  19. };
复制代码

3.3 递归加锁std::recursive_mutex

某些场景需要让线程在同一互斥上多次加锁,而无需解锁。为此提供了std::recursive_mutex。
其允许同一线程对某互斥多次加锁,必须先开释全部锁,才能让另一个线程获取锁。
也是通过std::lock_guard<std::recursive_mutex>或者std::unique_lock<std::recursive_mutex>

递归互斥常常用于这样的环境:
每个公有函数都需要先锁住互斥,才进行操纵,但是当共有函数调用共有函数时,使用std::mutex就会有标题,因此这个时候使用递归互斥。但是一般不发起这样做,因为这可能意味着设计有标题。

4 小结


4.1 几种互斥锁

std::mutex
std::shared_mutex假设它被某些线程持有,要等线程全部开释共享锁,其他线程才能访问排他锁。如果任一线程持有排他锁,其他线程无法获取共享锁以及排他锁,直至排他锁被开释,性能更优
std::shared_timed_mutex假设它被某些线程持有,要等线程全部开释共享锁,其他线程才能访问排他锁。如果任一线程持有排他锁,其他线程无法获取共享锁以及排他锁,直至排他锁被开释,支持操纵更多
std::recursive_mutex其允许同一线程对某互斥多次加锁,必须先开释全部锁,才能让另一个线程获取锁

4.2 几种加锁实例


std::lock_guard依据互斥作为参数的类模板,而且使用RAII手法管理锁
std::scoped_lock<>它和std::lock_guard<>完全等价。只不外前者是可变参数模板,接收各种互斥型别作为模板参数列表,同时锁住多个
std::unique_lock<> 它与std::lock_guard<>一样,除了std::unique_lock对象不一定始终占有与之关联的互斥

4.3 其他

std::adopt_lock传入std::adopt_lock实例,指明std::unique_lock对象管理互斥上的锁。
std::defer_lock 传入std::defer_lock实例,从而使互斥再完成构造时处于无锁状态,等以后有需要时才在std::unique_lock对象(不是互斥对象)上调用lock()而获取锁,或者把std::unique_lock对象交给std::lock()函数加锁。
std::call_once()令所有线程共同调用std::call_once()函数,确保在该调用返回时,指针初始化由其中某一线程安全且唯一地完成(通过合适的同步机制)。
std:nce_flag须要的同步数据由std:nce_flag实例存储,每个std:nce_flag实例对应一次不同的初始化。





免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

小小小幸运

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表