c++ mutex庫(kù)

參考cplusplus
參考cppreference

1.mutex

  • 用于保護(hù)臨界區(qū)(critical section)代碼的訪問。

1.1 mutex

  • 特定mutex上所有的lock和unlock順序要一致
  • 非成員lock函數(shù)允許一次lock多個(gè)mutex,可以避免多線程mutex的lock/unlock順序不同造成的死鎖。
// 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;
}
  • try_lock
    嘗試加鎖,但不阻塞。如果是同一個(gè)線程重復(fù)加鎖,也會(huì)死鎖。

1.2 timed_mutex

  • 跟mutex一致,只不過增加了try_lock_for和try_lock_util
  • try_lock_for
    嘗試加鎖,最多阻塞rel_time這么長(zhǎng)的時(shí)間。
template <class Rep, class Period>
  bool try_lock_for (const chrono::duration<Rep,Period>& rel_time);
// 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(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;
}
結(jié)果如下:
------------------------------------*
----------------------------------------*
-----------------------------------*
------------------------------*
-------------------------*
--------------------*
---------------*
----------*
-----*
*
  • try_lock_until
    嘗試加鎖,阻塞最多到abs_time。
template <class Clock, class Duration>
  bool try_lock_until (const chrono::time_point<Clock,Duration>& abs_time);

1.3 recursive_mutex

  • 同一個(gè)線程可以重復(fù)加鎖

1.4 recursive_timed_mutex

  • 支持try_lock_for和try_lock_until
  • 同一個(gè)線程可以重復(fù)加鎖

1.5 shared_mutex (頭文件<shared_mutex>)

  • 本質(zhì)是讀寫鎖
    可以同時(shí)被多個(gè)讀者擁有,但是只能被一個(gè)寫者擁有的鎖。
  • 獨(dú)占式
    lock try_lock unlock
  • 共享式
    lock_shared try_lock_shared unlock_shared
#include <iostream>
#include <mutex>  // For std::unique_lock
#include <shared_mutex>
#include <thread>
 
class ThreadSafeCounter {
 public:
  ThreadSafeCounter() = default;
 
  // Multiple threads/readers can read the counter's value at the same time.
  unsigned int get() const {
    std::shared_lock<std::shared_mutex> lock(mutex_);
    return value_;
  }
 
  // Only one thread/writer can increment/write the counter's value.
  void increment() {
    std::unique_lock<std::shared_mutex> lock(mutex_);
    value_++;
  }
 
  // Only one thread/writer can reset/write the counter's 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';
 
      // Note: Writing to std::cout actually needs to be synchronized as well
      // by another std::mutex. This has been omitted to keep the example small.
    }
  };
 
  std::thread thread1(increment_and_print);
  std::thread thread2(increment_and_print);
 
  thread1.join();
  thread2.join();
}

1.6 shared_timed_mutex

  • 提供時(shí)間設(shè)置

2.lock——Generic mutex management

  • 管理mutex

2.1 lock_guard

  • 構(gòu)造函數(shù)
    創(chuàng)建的對(duì)象管理m,并且調(diào)用m.lock()鎖住mutex。
//locking (1)   
explicit lock_guard (mutex_type& m);
//adopting (2)  
lock_guard (mutex_type& m, adopt_lock_t tag);
  • C++11的標(biāo)準(zhǔn)庫(kù)中提供了std::lock_guard類模板做mutex的RAII。
    采用”資源分配時(shí)初始化”(RAII——Resource Acquisition Is Initialization)方法來加鎖、解鎖,這避免了在臨界區(qū)中因?yàn)閽伋霎惓;騬eturn等操作導(dǎo)致沒有解鎖就退出的問題。
  • std::lock_guard類的構(gòu)造函數(shù)禁用拷貝構(gòu)造,且禁用移動(dòng)構(gòu)造。std::lock_guard類除了構(gòu)造函數(shù)和析構(gòu)函數(shù)外沒有其它成員函數(shù)。
  • 在std::lock_guard對(duì)象構(gòu)造時(shí),傳入的mutex對(duì)象(即它所管理的mutex對(duì)象)會(huì)被當(dāng)前線程鎖住。在lock_guard對(duì)象被析構(gòu)時(shí),它所管理的mutex對(duì)象會(huì)自動(dòng)解鎖,不需要程序員手動(dòng)調(diào)用lock和unlock對(duì)mutex進(jìn)行上鎖和解鎖操作。lock_guard對(duì)象并不負(fù)責(zé)管理mutex對(duì)象的生命周期,lock_guard對(duì)象只是簡(jiǎn)化了mutex對(duì)象的上鎖和解鎖操作,方便線程對(duì)互斥量上鎖,即在某個(gè)lock_guard對(duì)象的生命周期內(nèi),它所管理的鎖對(duì)象會(huì)一直保持上鎖狀態(tài);而lock_guard的生命周期結(jié)束之后,它所管理的鎖對(duì)象會(huì)被解鎖。程序員可以非常方便地使用lock_guard,而不用擔(dān)心異常安全問題。
// lock_guard example
#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;
}

2.2 scoped_lock

  • scoped_lock加鎖多個(gè)mutexes時(shí),不會(huì)出現(xiàn)死鎖,并且是RAII.
    這個(gè)跟std::lock有什么區(qū)別?
  • 只有構(gòu)造函數(shù)和析構(gòu)函數(shù)
  • 構(gòu)造函數(shù)如下
explicit scoped_lock( MutexTypes&... m );
scoped_lock( std::adopt_lock_t, MutexTypes&... m );
#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 &)
{
    // simulate a time-consuming messaging operation
    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;
    }
 
    {
        // use std::scoped_lock to acquire two locks without worrying about 
        // other calls to assign_lunch_partner deadlocking us
        // and it also provides a convenient RAII-style mechanism
 
        std::scoped_lock lock(e1.m, e2.m);
 
        // Equivalent code 1 (using std::lock and 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);
 
        // Equivalent code 2 (if unique_locks are needed, e.g. for condition variables)
        // 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");
 
    // assign in parallel threads because mailing users about lunch assignments
    // takes a long time
    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';
}

2.3 unique_lock

  • std::unique_lock對(duì)象以獨(dú)占所有權(quán)的方式(unique owership)管理mutex對(duì)象的上鎖和解鎖操作,即在unique_lock對(duì)象的聲明周期內(nèi),它所管理的鎖對(duì)象會(huì)一直保持上鎖狀態(tài);而unique_lock的生命周期結(jié)束之后,它所管理的鎖對(duì)象會(huì)被解鎖。unique_lock具有l(wèi)ock_guard的所有功能,而且更為靈活。雖然二者的對(duì)象都不能復(fù)制,但是unique_lock可以移動(dòng)(movable),因此用unique_lock管理互斥對(duì)象,可以作為函數(shù)的返回值,也可以放到STL的容器中。
  • std::unique_lock還支持同時(shí)鎖定多個(gè)mutex,這避免了多道加鎖時(shí)的資源”死鎖”問題。在使用std::condition_variable時(shí)需要使用std::unique_lock而不應(yīng)該使用std::lock_guard。
  • 其lock try_lock 等操作都是調(diào)用mutex對(duì)象的相應(yīng)操作
  • 構(gòu)造函數(shù)如下:
//default (1)
unique_lock() noexcept;

//locking (2)   
//調(diào)用 m.lock()   
explicit unique_lock (mutex_type& m);

//try-locking (3)   
//調(diào)用 m.try_lock()   
unique_lock (mutex_type& m, try_to_lock_t tag);

//deferred (4)  
// m 當(dāng)前應(yīng)該沒有被鎖
unique_lock (mutex_type& m, defer_lock_t tag) noexcept;

//adopting (5)  
// m 當(dāng)前應(yīng)該是被鎖的
unique_lock (mutex_type& m, adopt_lock_t tag);

//locking for (6)   
// 調(diào)用 m.try_lock_for(rel_time)

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

//locking until (7) 
// 調(diào)用 m.try_lock_until(abs_time)
template <class Clock, class Duration>
unique_lock (mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time);

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

//move (9)  
unique_lock (unique_lock&& x);
// unique_lock 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;
}

2.4 shared_lock

  • shared_lock就是讀鎖,被鎖后仍允許其他線程執(zhí)行同樣被shared_lock的代碼。這是一般做讀操作時(shí)的需要。
  • unique_lock就是寫鎖。被鎖后不允許其他線程執(zhí)行被shared_lock或unique_lock的代碼。在寫操作時(shí),一般用這個(gè),可以同時(shí)限制unique_lock的寫和share_lock的讀。
  • lock try_lock try_lock_for try_lock_until調(diào)用的都是shared版本:如mutex()->lock_shared()
std::shared_lock::mutex如下:
mutex_type* mutex() const noexcept;
  • 構(gòu)造函數(shù)如下
shared_lock() noexcept;

shared_lock( shared_lock&& other ) noexcept;

//調(diào)用 m.lock_shared()
explicit shared_lock( mutex_type& m );

//調(diào)用 m.lock_shared()
shared_lock( mutex_type& m, std::defer_lock_t t ) noexcept;

//調(diào)用 m.try_lock_shared()
shared_lock( mutex_type& m, std::try_to_lock_t t );

shared_lock( mutex_type& m, std::adopt_lock_t t );

//調(diào)用 m.try_lock_shared_until(timeout_duration)
template< class Rep, class Period >
shared_lock( mutex_type& m, 
             const std::chrono::duration<Rep,Period>& timeout_duration );

// 調(diào)用 m.try_lock_shared_for(timeout_time)
template< class Clock, class Duration >
shared_lock( mutex_type& m, 
             const std::chrono::time_point<Clock,Duration>& timeout_time );
#include <shared_mutex>
#include <iostream>
#include <thread>
#include <chrono>
 
std::shared_timed_mutex m;
int i = 10;
 
void read()
{
   // both the threads get access to the integer i
   std::shared_lock<std::shared_timed_mutex> slk(m);
   std::cout << "read i as " << i << "...\n"; // this is not synchronized
   std::this_thread::sleep_for(std::chrono::milliseconds(10));
   std::cout << "woke up...\n";
}
 
int main()
{
   std::thread r1(read);
   std::thread r2(read);
 
   r1.join();
   r2.join();
   return 0;
}

2.5 defer_lock_t / try_to_lock_t /adopt_lock_t

  • 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指定鎖定策略的空結(jié)構(gòu)體標(biāo)簽類型。
// 不用獲取mutex的所有權(quán)
struct defer_lock_t { explicit defer_lock_t() = default; };

// 嘗試獲取mutex的所有權(quán),不阻塞
struct try_to_lock_t { explicit try_to_lock_t() = default; };

// 假設(shè)調(diào)用線程已經(jīng)獲得mutex的所有權(quán)
struct adopt_lock_t { explicit adopt_lock_t() = default; };

2.6 defer_lock / try_to_lock / adopt_lock

  • std::defer_lock 、 std::try_to_lock和 std::adopt_lock分別是空結(jié)構(gòu)體標(biāo)簽類型 std::defer_lock_t 、 std::try_to_lock_t和 std::adopt_lock_t的實(shí)例。
    它們用于為 std::lock_guard 、 std::unique_lock及 std::shared_lock指定鎖定策略。
inline constexpr std::defer_lock_t defer_lock {};
inline constexpr std::try_to_lock_t try_to_lock {};
inline constexpr std::adopt_lock_t adopt_lock {};
#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)
{
    // lock both mutexes without deadlock
    std::lock(from.m, to.m);
    // make sure both already-locked mutexes are unlocked at the end of scope
    std::lock_guard<std::mutex> lock1(from.m, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(to.m, std::adopt_lock);
 
// equivalent approach:
//    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();
}

3.Generic locking algorithms

  • 同時(shí)對(duì)多個(gè)mutex進(jìn)行加鎖

3.1 try_lock

  • 嘗試對(duì)所有mutex進(jìn)行加鎖(非阻塞)
  • 如果全部加鎖成功,返回-1
    如果有一個(gè)加鎖失敗,則將加鎖成功的mutex解鎖,并返回加鎖失敗的那個(gè)鎖的序號(hào)(從0開始)
template <class Mutex1, class Mutex2, class... Mutexes>
  int try_lock (Mutex1& a, Mutex2& b, Mutexes&... cde);
// 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;
}

3.2 lock

  • 鎖住所有的mutex,可能會(huì)阻塞
    調(diào)用mutex對(duì)象的lock try_lock
    如果不能鎖住所有mutex(因?yàn)槠渲杏幸粋€(gè)拋出異常),則先unlock加鎖成功的mutex。
template <class Mutex1, class Mutex2, class... Mutexes>
  void lock (Mutex1& a, Mutex2& b, Mutexes&... cde);
  • 原來多個(gè)鎖會(huì)造成死鎖,換成lock后可正常運(yùn)行
// 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;
}

4.Call once

  • 直接阻止指定函數(shù)的并發(fā)執(zhí)行。

4.1 once_flag

  • 作為call_one的參數(shù)
    Using the same object on different calls to call_once in different threads causes a single execution if called concurrently.
struct once_flag {
  constexpr once_flag() noexcept;
  once_flag (const once_flag&) = delete;
  once_flag& operator= (const once_flag&) = delete;
};

4.2 call_once

  • 相同的flag只有一個(gè)函數(shù)正在執(zhí)行,fn使用args進(jìn)行調(diào)用。
  • 所有相同的flag的調(diào)用只有一個(gè)是活動(dòng)的執(zhí)行(active execution),其他是passive executions。被動(dòng)執(zhí)行等到活動(dòng)的執(zhí)行返回后才返回,所有調(diào)用的可見副作用同步到這些函數(shù)。
    一旦一個(gè)ative execution返回,當(dāng)前和以后有相同標(biāo)識(shí)的call_once都會(huì)直接返回。
  • 如果當(dāng)前的活動(dòng)執(zhí)行拋出異常后結(jié)束,從passive里面選擇一個(gè)作為新的活動(dòng)執(zhí)行實(shí)體。
template <class Fn, class... Args>
  void call_once (once_flag& flag, Fn&& fn, Args&&... args);
// 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;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容