搜索
您的当前位置:首页正文

c++11 多线程(2)mutex 总结

来源:知库网

2、

mutex 头文件中主要包含 Mutexes、lock 和相关的类型(Other types)和公共函数。

    • (constructor) 不允许拷贝;互斥对象不能被复制/移动,初始状态为未锁定。
      default (1) constexpr mutex() noexcept;
      copy [deleted] (2) mutex (const mutex&) = delete;

      1. 如果互斥对象当前没有被任何线程锁定,则调用线程锁定它(从这一点开始,直到它的成员解锁被调用,线程拥有互斥对象)。
      2. 如果互斥锁当前被另一个线程锁定,则调用线程的执行将被阻塞,直到其他线程解锁(其他非锁定线程继续执行它们)。
      3. 如果互斥锁当前被相同的线程所锁定,调用此函数,则会产生死锁(未定义的行为)。
      // mutex::lock/unlock
      #include <iostream>       // std::cout
      #include <thread>         // std::thread
      #include <mutex>          // std::mutex
      std::mutex mtx;           // mutex for critical section
      void print_thread_id (int id) {
        // critical section (exclusive access to std::cout signaled by locking mtx):
        mtx.lock();
        std::cout << "thread #" << id << '\n';
        mtx.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;
      }
      

    // mutex::try_lock example

    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 ()
    {
    std::thread threads[10];
    // spawn 10 threads:
    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;
    

    }
    ```


    • 传入时间段,在时间范围内未获得所就阻塞住线程,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时,则返回 false。

    • 同上面的解释,只是传入参数为一个未来的一个时间点。
    // timed_mutex::try_lock_for example
    #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(2))) {
        std::cout << "-";
      }
      // got a lock! - wait for 1s, then this thread prints "*"
      std::this_thread::sleep_for(std::chrono::milliseconds(100));
      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;
    }
    
    // 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;
    
    // gets time_point for next midnight:
    std::chrono::time_point<std::chrono::system_clock> midnight() {
      using std::chrono::system_clock;
      std::time_t tt = system_clock::to_time_t (system_clock::now());
      struct std::tm * ptm = std::localtime(&tt);
      ++ptm->tm_mday; ptm->tm_hour=0; ptm->tm_min=0; ptm->tm_sec=0;
      return system_clock::from_time_t (mktime(ptm));
    }
    
    void carriage() {
      if (cinderella.try_lock_until(midnight())) {
        std::cout << "ride back home on carriage\n";
        cinderella.unlock();
      }
      else
        std::cout << "carriage reverts to pumpkin\n";
    }
    
    void ball() {
      cinderella.lock();
      std::cout << "at the ball...\n";
      cinderella.unlock();
    }
    
    int main ()
    {
      std::thread th1 (ball);
      std::thread th2 (carriage);
      th1.join();
      th2.join();
      return 0;
    }
    

  • 定义: template <class Mutex> class lock_guard;
    锁保护是一个通过将互斥对象保持锁定来管理互斥对象的对象。
    在构造上,互斥对象被调用线程锁定,并且在销毁时,互斥锁被解锁。它是最简单的锁,作为具有自动持续时间的对象特别有用,直到它的上下文结束。这样,它保证在抛出异常时,可以正确地锁定互斥对象。
    注意,lock_guard对象不以任何方式管理互斥对象的生命周期:互斥对象的持续时间至少要延长到锁定它的lock_guard的销毁为止。

模板参数 Mutex 代表互斥量类型,例如 std::mutex 类型,它应该是一个基本的 BasicLockable 类型,标准库中定义几种基本的 BasicLockable 类型,分别 std::mutex, std::recursive_mutex, std::timed_mutexstd::recursive_timed_mutex 以及 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)。

  // constructing lock_guard with adopt_lock
  #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;
  }

  • 定义: template <class Mutex> class unique_lock;
    unique_lock与 lock_guard 类相似,但它提供了更好的上锁和解锁控制。unique_lock 对象以独占所有权的方式(官方叫做unique ownership)管理 mutex 对象的上锁和解锁操作;独占所有权,就是没有其他的 unique_lock 对象同时拥有某个 mutex 对象的所有权。
    unique_lock对象在析构的时候一定保证互斥量为解锁状态;因此它作为具有自动持续时间的对象特别有用,因为它保证在抛出异常时,互斥对象被正确地解锁。不过,注意unique_lock对象并不以任何方式管理互斥对象的生命周期:互斥对象的持续时间将延长至少直到unique_lock管理它的毁灭。
  1. 构造函数(constructor):
    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);
    下面我们来分别介绍以上各个构造函数:
    (1) 默认构造函数
    unique_lock 对象不管理任何 Mutex 对象m。
    (2) locking 初始化
    unique_lock 对象管理 Mutex 对象 m,并调用 m.lock() 对 Mutex 对象进行上锁,如果其他 unique_lock 对象已经管理了m,该线程将会被阻塞。
    (3) try-locking 初始化
    unique_lock 对象管理 Mutex 对象 m,并调用 m.try_lock() 对 Mutex 对象进行上锁,但如果上锁不成功,不会阻塞当前线程。
    (4) deferred 初始化
    unique_lock 对象管理 Mutex 对象 m并不锁住m。 m 是一个没有被当前线程锁住的 Mutex 对象。
    (5) adopting 初始化
    unique_lock 对象管理 Mutex 对象 m, m 应该是一个已经被当前线程锁住的 Mutex 对象。(当前unique_lock 对象拥有对锁(lock)的所有权)。
    (6) locking 一段时间(duration)
    新创建的 unique_lock 对象管理 Mutex 对象 m,通过调用 m.try_lock_for(rel_time) 来锁住 Mutex 对象一段时间(rel_time)。
    (7) locking 直到某个时间点(time point)
    新创建的 unique_lock 对象管理 Mutex 对象m,通过调用 m.try_lock_until(abs_time) 来在某个时间点(abs_time)之前锁住 Mutex 对象。
    (8) 拷贝构造 [被禁用]
    unique_lock 对象不能被拷贝构造。
    (9) 移动(move)构造
    新创建的 unique_lock 对象获得了由 x 所管理的 Mutex 对象的所有权(包括当前 Mutex 的状态)。调用 move 构造之后, x 对象如同通过默认构造函数所创建的,就不再管理任何 Mutex 对象了。
  // unique_lock constructor example
  #include <iostream>       // std::cout
  #include <thread>         // std::thread
  #include <mutex>          // std::mutex, std::lock, std::unique_lock
                                        // std::adopt_lock, std::defer_lock
  std::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;
  }
  1. 其他成员函数
// 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;
}
// unique_lock::try_lock example
#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();
  std::cout << "\n";
  return 0;
}

(3) std::unique_lock::try_lock_for

template <class Rep, class Period>
bool try_lock_for (const chrono::duration<Rep,Period>& rel_time);
// unique_lock::try_lock_for example
#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;
}

(4)std::unique_lock::try_lock_until
同上面描述,传入一个时间点而不是时间段。示例:同上改动,不再述。

// 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;
}

(6) std::unique_lock::operator=

move (1)    unique_lock& operator= (unique_lock&& x) noexcept;
copy [deleted] (2) unique_lock& operator= (const unique_lock&) = delete;
// unique_lock::operator= example
#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;
}

(7) std::unique_lock::swap
与x交换内容,包括托管的互斥对象和它们当前拥有的状态。

// unique_lock::release example
#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;
}
// unique_lock::operator= example
#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();
  std::cout << "\n";
  return 0;
}

(10) std::unique_lock::operator bool
同上面。

// unique_lock::mutex example
#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;
}
  • struct once_flag {
    constexpr once_flag() noexcept;
    once_flag (const once_flag&) = delete;
    once_flag& operator= (const once_flag&) = delete;
    };

  • constexpr adopt_lock_t adopt_lock {}; 
    

定义adopt_lock 值用作对unique_lock或lock_guard的构造函数的可能参数。
使用adopt_lock构造unique_lock对象不锁定构建中的互斥对象,只是假设它已经被当前线程锁定了;这个值是一个没有状态的编译时常量,只是用来消除构造函数签名之间的歧义。


  • std::try_lock是一个模版函数:
    template <class Mutex1, class Mutex2, class... Mutexes>
    int try_lock (Mutex1& a, Mutex2& b, Mutexes&... cde);
    使用try_lock成员函数(非阻塞)式的锁定对象a,b...等。函数为每个参数调用try_lock成员函数(首先是a,然后是b,最后是cde中的其他函数),直到所有调用都是成功的,或者只要调用失败(返回false或抛出异常)。如果函数结束是因为调用失败,则对所有调用try_lock成功的对象调用解锁,函数将返回锁定失败的对象的参数序号。没有对参数列表中的其余对象执行其他调用。
  // std::lock example
  #include <iostream>       // std::cout
  #include <thread>         // std::thread
  #include <mutex>          // std::mutex, std::try_lock

  std::mutex foo,bar;

  void task_a () {
    foo.lock();
    std::cout << "task a\n";
    bar.lock();
    // ...
    foo.unlock();
    bar.unlock();
  }

  void task_b () {
    int x = try_lock(bar,foo);
    if (x==-1) {
      std::cout << "task b\n";
      // ...
      bar.unlock();
      foo.unlock();
    }
    else {
      std::cout << "[task b failed: mutex " << (x?"foo":"bar") << " locked]\n";
    }
  }

  int main ()
  {
    std::thread th1 (task_a);
    std::thread th2 (task_b);

    th1.join();
    th2.join();
  
    return 0;
  }

  • std::lock同样为一个模版函数
    template <class Mutex1, class Mutex2, class... Mutexes>
    void lock (Mutex1& a, Mutex2& b, Mutexes&... cde);
    锁定所有的参数互斥对象,阻塞当前调用线程;该函数使用一个未指定的调用序列来锁定对象,该序列调用其成员锁、try_lock和解锁,以确保所有参数都被锁定在返回(不产生任何死锁)。
    如果函数不能锁定所有对象(例如,因为其中一个内部调用抛出异常),则函数首先解锁所有成功锁定的对象(如果有的话)。
    :
  // std::lock example
  #include <iostream>       // std::cout
  #include <thread>         // std::thread
  #include <mutex>          // std::mutex, std::lock

  std::mutex foo,bar;

  void task_a () {
    // foo.lock(); bar.lock(); // replaced by:
    std::lock (foo,bar);
    std::cout << "task a\n";
    foo.unlock();
    bar.unlock();
  }
  
  void task_b () {
    // bar.lock(); foo.lock(); // replaced by:
    std::lock (bar,foo);
    std::cout << "task b\n";
    bar.unlock();
    foo.unlock();
  }

  int main ()
  {
    std::thread th1 (task_a);
    std::thread th2 (task_b);

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

    return 0;
  }

  • std::call_once 公有模版函数
    template <class Fn, class... Args>
    void call_once (once_flag& flag, Fn&& fn, Args&&... args);
    call_once调用将args 作为fn的参数调用fn,除非另一个线程已经(或正在执行)使用相同的flag调用执行call_once。如果已经有一个线程使用相同flag调用call_once,会使得当前变为被动执行,所谓被动执行不执行fn也不返回直到恢复执行后返回。这这个时间点上所有的并发调用这个函数相同的flag都是同步的。
    注意,一旦一个活跃调用返回了,所有当前被动执行和未来可能的调用call_once相同相同的flag也还不会成为积极执行。
  // call_once example
  #include <iostream>       // std::cout
  #include <thread>         // std::thread, std::this_thread::sleep_for
  #include <chrono>         // std::chrono::milliseconds
  #include <mutex>          // std::call_once, std::once_flag

  int winner;
  void set_winner (int x) { winner = x; }
  std::once_flag winner_flag;

  void wait_1000ms (int id) {
    // count to 1000, waiting 1ms between increments:
    for (int i=0; i<1000; ++i)
      std::this_thread::sleep_for(std::chrono::milliseconds(1));
    // claim to be the winner (only the first such call is executed):
    std::call_once (winner_flag,set_winner,id);
  }

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

    std::cout << "waiting for the first among 10 threads to count 1000   ms...\n";

    for (auto& th : threads) th.join();
    std::cout << "winner thread: " << winner << '\n';
  
    return 0;
  }
Top