C++1X 线程间通信

Mutex锁

  1. std::lock_guard 的原型是一个模板类,定义如下:template<class Mutex> class lock_guard;lock_guard 通常用来管理一个 std::mutex 类型的对象,通过定义一个 lock_guard 一个对象来管理 std::mutex 的上锁和解锁。在 lock_guard 初始化的时候进行上锁,然后在 lock_guard 析构的时候进行解锁。这样避免了我们对 std::mutex 的上锁和解锁的管理。注意,lock_guard 并不管理 std::mutex 对象的声明周期,也就是说在使用 lock_guard 的过程中,如果 std::mutex 的对象被释放了,那么在 lock_guard 析构的时候进行解锁就会出现空指针错误之类。

  2. lock_guard不够灵活,lock_guard只能保证在析构的时候执行解锁操作,lock_guard本身并没有提供加锁和解锁的接口,但是有些时候会有这种需求。修改方法是使用unique_lock。它提供了lock()unlock()接口,能记录现在处于上锁还是没上锁状态,在析构的时候,会根据当前状态来决定是否要进行解锁(lock_guard就一定会解锁)。

  3. 在无需加锁的操作时,可以先临时释放锁,然后需要继续保护的时候,可以继续上锁,这样就无需重复的实例化lock_guard对象,还能减少锁的区域。同样,可以使用std::defer_lock设置初始化的时候不进行默认的上锁操作。它内部需要维护锁的状态,所以效率要比lock_guard低一点,在lock_guard能解决问题的时候,就是用lock_guard,反之,使用unique_lock

  4. unique_lock 对象以独占所有权的方式( unique owership)管理 mutex 对象的上锁和解锁操作,所谓独占所有权,就是没有其他的 unique_lock 对象同时拥有某个 mutex 对象的所有权。unique_locklock_guard都不能复制,lock_guard不能移动,但是unique_lock可以!

  5. unique_lock()的第二个参数示例:std::adopt_lock表示第一个参数的mutex对象已经被lock了,try_to_lock():尝试mutex的lock去锁定这个mutex,但是如果没有锁定成功,也会立即返回,并不会阻塞。defer_lock:不能先lock,不然会报异常。初始化一个没有加锁的mutex。然后要lock,但是可以不用手动解锁。unique_lock模板类中的release方法返回它所管理的mutex对象指针,并释放所有权;也就是说,这个unique_lock和mutex不再有关系。严格区分unlock()与release()的区别,不要混淆。如果原来mutex对像处于加锁状态,你有责任接管过来并负责解锁。(release返回的是原始mutex的指针)。

  6. shared_lock是read lock。被锁后仍允许其他线程执行同样被shared_lock的代码。这是一般做读操作时的需要。共享互斥 (SharedMutex) 类型的对象 m 支持另一所有权模式:共享。多个线程(或更通用地,执行代理)能同时以共享模式占有此互斥,但若有线程以排他性模式占有,则无线程可获得共享所有权,而若有线程以共享模式占有,则无线程可获得排他性所有权。若多于实现定义数量(不少于 10000 )的线程数保有一个共享锁,则另一对获得共享锁的尝试会阻塞直至共享所有者的数量跌到该阈值以下。

  7. 保证call_once调度的函数只被执行一次。某些场景下,我们需要代码只被执行一次,比如单例类的初始化,考虑到多线程安全,需要进行加锁控制。C++11中提供的call_once可以很好的满足这种需求,使用又非常简单。 第一个参数是std::once_flag的对象(once_flag是不允许修改的,其拷贝构造函数和operator=函数都声明为delete),第二个参数可调用实体,即要求只执行一次的代码,后面可变参数是其参数列表。 call_once保证函数fn只被执行一次,如果有多个线程同时执行函数fn调用,则只有一个活动线程(active call)会执行函数,其他的线程在这个线程执行返回之前会处于”passive execution”(被动执行状态)——不会直接返回,直到活动线程对fn调用结束才返回。对于所有调用函数fn的并发线程,数据可见性都是同步的(一致的)。 如果活动线程在执行fn时抛出异常,则会从处于”passive execution”状态的线程中挑一个线程成为活动线程继续执行fn,依此类推。一旦活动线程返回,所有”passive execution”状态的线程也返回,不会成为活动线程。(实际上once_flag相当于一个锁,使用它的线程都会在上面等待,只有一个线程允许执行。如果该线程抛出异常,那么从等待中的线程中选择一个,重复上面的流程)。 在单例模式中建立对象,最好使用call_once或者进行加锁

    #include<mutex>
    template <class Fn, class... Args>
    void call_once (once_flag& flag, Fn&& fn, Args&&...args); 
    

    条件变量

    1. condition_variable 类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable

      有意修改变量的线程必须

      1. 获得 std::mutex (典型地通过 [std::lock_guard]
      2. 在保有锁时进行修改
      3. std::condition_variable 上执行 notify_onenotify_all (不需要为通知保有锁)
    2. 任何有意在 std::condition_variable 上等待的线程必须

      1. 获得 std::unique_lock<[std::mutex]>,在与用于保护共享变量者相同的互斥上
      2. 执行 waitwait_forwait_until ,等待操作自动释放互斥,并悬挂线程的执行。
      3. condition_variable 被通知时,时限消失或虚假唤醒发生,线程被唤醒,且自动重获得互斥。之后线程应检查条件,若唤醒是虚假的,则继续等待。
    3. std::condition_variable 只可与 std::unique_lock<[std::mutex]一同使用;此限制在一些平台上允许最大效率。 std::condition_variable_any 提供可与任何基础可锁 (BasicLockable) 对象,例如 std::shared_lock 一同使用的条件变量。

      condition_variable 容许 waitwait_forwait_untilnotify_onenotify_all 成员函数的同时调用。

    4. std::condition_variable 提供了两种 wait() 函数。当前线程调用 wait() 后将被阻塞(此时当前线程应该获得了锁(mutex),不妨设获得锁 lck),直到另外某个线程调用 notify_* 唤醒了当前线程。

      在线程被阻塞时,该函数会自动调用 lck.unlock() 释放锁,使得其他被阻塞在锁竞争上的线程得以继续执行。另外,一旦当前线程获得通知(notified,通常是另外某个线程调用 notify_* 唤醒了当前线程),wait() 函数也是自动调用 lck.lock(),使得 lck 的状态和 wait 函数被调用时相同。

      在第二种情况下(即设置了 Predicate),只有当 pred 条件为 false 时调用 wait() 才会阻塞当前线程,并且在收到其他线程的通知后只有当 pred 为 true 时才会被解除阻塞。因此第二种情况类似以下代码:

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

    5. std::condition_variable::wait() 类似,不过 wait_for 可以指定一个时间段,在当前线程收到通知或者指定的时间 rel_time 超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了其他线程的通知,wait_for 返回,剩下的处理步骤和 wait() 类似。

      另外,wait_for 的重载版本(predicte(2))的最后一个参数 pred 表示 wait_for 的预测条件,只有当 pred 条件为 false 时调用 wait() 才会阻塞当前线程,并且在收到其他线程的通知后只有当 pred 为 true 时才会被解除阻塞

    6. std::condition_variable::wait_for 类似,但是 wait_until 可以指定一个时间点,在当前线程收到通知或者指定的时间点 abs_time 超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了其他线程的通知,wait_until 返回,剩下的处理步骤和 wait_until() 类似。

      另外,wait_until 的重载版本(predicte(2))的最后一个参数 pred 表示 wait_until 的预测条件,只有当 pred 条件为 false 时调用 wait() 才会阻塞当前线程,并且在收到其他线程的通知后只有当 pred 为 true 时才会被解除阻塞

    7. std::condition_variable::notify_one() 介绍

      唤醒某个等待(wait)线程。如果当前没有等待线程,则该函数什么也不做,如果同时存在多个等待线程,则唤醒某个线程是不确定的(unspecified)。

    8. std::condition_variable::notify_all() 介绍

      唤醒所有的等待(wait)线程。如果当前没有等待线程,则该函数什么也不做。

    9. std::condition_variable_any std::condition_variable 类似,只不过 std::condition_variable_any 的 wait 函数可以接受任何 lockable 参数,而 std::condition_variable 只能接受 std::unique_lock<std::mutex> 类型的参数,除此以外,和 std::condition_variable 几乎完全一样。

    10. std::cv_status 枚举类型介绍

    cv_status::no_timeout wait_for 或者 wait_until 没有超时,即在规定的时间段内线程收到了通知。
    cv_status::timeout wait_for 或者 wait_until 超时。
    1. std::notify_all_at_thread_exit当调用该函数的线程退出时,所有在 cond 条件变量上等待的线程都会收到通知。