C++11多線程之std::unique_lock

奇怪,幾年前寫的,不知道爲何最近一年始終放在草稿箱了。拿出來曬曬。如有錯漏之處,再修正吧。

原文如下:
http://en.cppreference.com/w/cpp/thread/unique_lock
http://en.cppreference.com/w/cpp/thread/unique_lock/unique_lock
http://en.cppreference.com/w/cpp/thread/unique_lock/~unique_lock
http://en.cppreference.com/w/cpp/thread/unique_lock/operator%3D
http://en.cppreference.com/w/cpp/thread/unique_lock/lock
http://en.cppreference.com/w/cpp/thread/unique_lock/try_lock
http://en.cppreference.com/w/cpp/thread/unique_lock/try_lock_for
http://en.cppreference.com/w/cpp/thread/unique_lock/try_lock_until
http://en.cppreference.com/w/cpp/thread/unique_lock/unlock
http://en.cppreference.com/w/cpp/thread/unique_lock/mutex
http://en.cppreference.com/w/cpp/thread/unique_lock/owns_lock
http://en.cppreference.com/w/cpp/thread/unique_lock/operator_bool

#std::unique_lock

定義在頭文件<mutex> 中。

template <class Mutex>      (since C++11)
class unique_lock;

類 unique_lock 是一個一般性質的 mutex 屬主的封裝,提供延遲鎖定(deferred locking),限時嘗試(time-constrained attempts),遞歸鎖定(recursive locking), 鎖主的轉換, 以及對條件變量的使用。

類 unique_lock 是 movable 的,但不是 copyable 的 – 它滿足 MoveConstructible 和 MoveAssignable, 但不滿足 CopyConstructible 和 CopyAssignable.

類 unique_lock 滿足 BasicLockable 的要求。如果 Mutex 滿足 Lockable 的要求,那麼 unique_lock 也滿足 Lockable 的要求(比如,可以被用於 std::lock);如果 Mutex 滿足 TimedLockable 的要求,unique_lock 也滿足 TimedLockable 的要求。

模板參數

Mutex - 用於鎖定的 mutex 的類型。該類型必須滿足 BasicLockable 的要求。

構造函數

(1) unique_lock();  
(2) unique_lock( unique_lock&& other );  
(3) explicit unique_lock( mutex_type& m );
(4) unique_lock( mutex_type& m, std::defer_lock_t t );
(5) unique_lock( mutex_type& m, std::try_to_lock_t t );
(6) unique_lock( mutex_type& m, std::adopt_lock_t t );
(7) template< class Rep, class Period >
    unique_lock(mutex_type& m, 
                const std::chrono::duration<Rep,Period>& timeout_duration);
(8) template< class Clock, class Duration >
    unique_lock(mutex_type& m, 
                const std::chrono::time_point<Clock,Duration>& timeout_time);

(1) unique_lock();
構造一個沒有關聯 mutex 的 unique_lock

(2) unique_lock( unique_lock&& other );
Move構造函數,使用 other 的內容來構造 unique_lock. 使得other變成沒有mutex關聯的unique_lock.

(3) - (8) 構造一個以 m 爲關聯的mutex的unique_lock, 另外:

(3) explicit unique_lock( mutex_type& m );
通過調用 m.lock() 來鎖定相關聯的 mutex. 如果當前線程已經擁有了mutex,且不是遞歸的mutex,那麼行爲未定義。

(4) unique_lock( mutex_type& m, std::defer_lock_t t );
不鎖定相關的mutex.

(5) unique_lock( mutex_type& m, std::try_to_lock_t t );
通過調用 m.try_lock() 來嘗試鎖定相關的mutex而不會阻塞。如果當前線程已經擁有mutex且不是遞歸mutex,則行爲未定義。

(6) unique_lock( mutex_type& m, std::adopt_lock_t t );
假設線程已經擁有m.

(7)

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

通過調用 m.try_lock_for(timeout_duration) 試圖鎖定相關聯的 mutex. 一直阻塞直到超時或鎖定成功。也可能阻塞得比time_duration的時間更長一些。

(8)

template< class Clock, class Duration >  
    unique_lock(mutex_type& m, const std::chrono::time_point<Clock,Duration>& timeout_time);  

通過調用 m.try_lock_until(timeout_time) 來試圖鎖定相關聯的 mutex. 一直阻塞直到指定的時間點到達或者鎖定成功。可能會在指定的時間到達後仍阻塞一會兒。

示例程序:

#include <cassert>
#include <iostream> // std::cout
#include <thread>
#include <vector>
#include <mutex>
 
class Number;
std::ostream& operator<<(std::ostream& stream, const Number& number);
 
class Number {
 public:
  Number() : v_(1) {}
 
  // thread-safe update of 'a' and 'b'
  static void update(Number& a, Number& b, bool order) {
    // do not lock 'mutex_' of 'a' and 'b' sequentially,
    // two sequential lock may lead to deadlock,
    // that's why 'std::lock' exists (see below)
    GuardLock lock_a(a.mutex_, std::defer_lock);
    GuardLock lock_b(b.mutex_, std::defer_lock);
 
    // mutexes is not locked
    assert(!lock_a.owns_lock());
    assert(!lock_b.owns_lock());
 
    // unspecified series of calls...
    std::lock(lock_a, lock_b);
 
    // Result: 'a.mutex_' and 'b.mutex_' is in locked state
    // 'a' and 'b' can be modified safety
    assert(lock_a.owns_lock());
    assert(lock_b.owns_lock());
 
    if (order) {
      a.v_ += b.v_;
      b.v_ += a.v_;
 
      std::cout << a << b;
    }
    else {
      b.v_ += a.v_;
      a.v_ += b.v_;
 
      std::cout << b << a;
    }
 
    // 'lock_a' and 'lock_b' will be destroyed,
    // unlocking 'a.mutex_' and 'b.mutex_'
  }
 
  // not thread-safe; used before thread creation or in thread-safe 'update'
  std::ostream& print(std::ostream& stream) const {
    stream << v_ << " ";
    return stream;
  }
 
 private:
  using Mutex = std::mutex;
  using GuardLock = std::unique_lock<Mutex>;
 
  Mutex mutex_;
  int v_;
};
 
// not thread-safe; see 'Number::print'
std::ostream& operator<<(std::ostream& stream, const Number& number) {
  return number.print(stream);
}
 
int main() {
  Number a, b;
  std::cout << a << b;
 
  std::vector<std::thread> threads;
 
  for (unsigned i = 0; i < 4; ++i) {
    // without 'std::lock' deadlock may occur in this situation:
    //   thread #1 lock 'a.mutex_'
    //   thread #2 lock 'b.mutex_'
    //   thread #1 try to lock 'b.mutex_' and blocked (it's locked by #2)
    //   thread #2 try to lock 'a.mutex_' and blocked (it's locked by #1)
    //   ... deadlock
    threads.emplace_back(Number::update, std::ref(a), std::ref(b), true); // #1
    threads.emplace_back(Number::update, std::ref(b), std::ref(a), false); // #2
  }
 
  for (auto& i: threads) {
    i.join();
  }
 
  std::cout << '\n';
}

// Output: 
// 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584

析構函數

銷燬這個unique_lock對象。如果 *this 此時擁有一個相關聯的 mutex 並且已經獲得它,那麼就會解鎖該mutex.

賦值操作符 operator =

unique_lock& operator=( unique_lock&& other ); (since C++11)

Move賦值操作符。使用 other 中的內容來賦值給自己。
如果在此調用前,*this 已經擁有一個mutex並鎖定了它,那麼此調用會解鎖該mutex.

std::unique_lock::lock 函數

void lock(); (since C++11)
鎖定關聯的mutex. 高效地調用 mutex()->lock().

異常:

  • 拋出mutex()->lock()所拋出的異常。
  • 如果沒有相關聯的 mutex, std::system_error就會拋出,所攜帶的錯誤碼是std::errc::operation_not_permitted.
  • 如果mutex已經被本unique_lock鎖定了(換句話說,owns_lock爲true),那麼std::system_error就會被拋出,錯誤碼是std::errc::resource_deadlock_would_occur.

參見示例程序:

#include <mutex>
#include <thread>
#include <iostream>
#include <vector>
#include <chrono>
 
int main()
{
    int counter = 0;
    std::mutex counter_mutex;
    std::vector<std::thread> threads;
 
    auto worker_task = [&](int id) {
        // Note, this is just lock! See the document of the constructor. 
        // The reason is, it is RAAI (Resource Acquisition is Initialization)
        std::unique_lock<std::mutex> lock(counter_mutex);  
        
        ++counter;
        std::cout << id << ", initial counter: " << counter << '\n';
        lock.unlock();
        
        // don't hold the lock while we simulate an expensive operation
        std::this_thread::sleep_for(std::chrono::seconds(1));
 
        lock.lock();
        ++counter;
        std::cout << id << ", final counter: " << counter << '\n';
    };
 
    for (int i = 0; i < 10; ++i) {
        // vector::push_back() cannot work due to std::thread is not copyable.
        threads.emplace_back(worker_task, i);
    }
 
    for (auto &thread : threads) thread.join();
}


/**
Possible Output:
0, initial counter: 1
1, initial counter: 2
2, initial counter: 3
3, initial counter: 4
4, initial counter: 5
5, initial counter: 6
6, initial counter: 7
7, initial counter: 8
8, initial counter: 9
9, initial counter: 10
1, final counter: 11
0, final counter: 12
3, final counter: 13
5, final counter: 14
2, final counter: 15
4, final counter: 16
7, final counter: 17
9, final counter: 18
6, final counter: 19
8, final counter: 20
**/

std::unique_lock::try_lock 函數

bool try_lock(); (since C++11)
試圖鎖定相關聯的mutex,而不會阻塞。高效地調用 mutex()->try_lock().
如果沒有相關聯的mutex或者該mutex已經被該unique_lock鎖定,那麼 std::system_error 就會被拋出。

異常:

  • 任何 mutex()->try_lock() 拋出的異常都會被拋出 (Mutex類型不會被try_lock拋出,但一個自定義的Lockable類型可能會被拋出)。
  • 如果沒有相關聯的mutex,那麼std::system_error就會被拋出,其錯誤碼是std::errc::operation_not_permitted.
  • 如果mutex已經被本unique_lock鎖定了,std::system_error也會被拋出,錯誤碼是std::errc::resource_deadlock_would_occur.

std::unique_lock::try_lock_for 函數

template<class Rep, class Period>
bool try_lock_for( const std::chrono::duration<Rep, Period>& timeout_duration ); (since C++11)

試圖鎖定相關聯的mutex. 會阻塞住,直到指定的時間段超時或者鎖定成功。一旦成功鎖定,就返回true,否則返回false. 高效地調用 mutex()->try_lock_for(timeout_duration).
一個穩定的時鐘被用來衡量此時間段。本函數可能阻塞地比timeout_duration的時間更久一些,原因是系統調度或資源競爭所導致的延時。
如果沒有相關聯的mutex或該mutex已經被鎖定,則std::system_error就會被拋出。

返回值:
獲得mutex則返回true,否則false.

異常:

  • 任何被mutex()->try_lock_for(timeout_duration)拋出的異常都將被拋出;
  • 若沒有相關聯的mutex,則std::system_error就會被拋出,錯誤碼是std::errc::operation_not_permitted;
  • 若mutex已經被鎖的那個,則std::system_error就會被拋出,錯誤碼是std::errc::resource_deadlock_would_occur.

std::unique_lock::unlock 函數

void unlock(); (since C++11)
解鎖相關的mutex.
如果沒有相關聯的mutex或該mutex已經被鎖定,則std::system_error就會被拋出。

異常:

  • 任何被mutex()->unlock()拋出的異常都會被拋出。
  • 如果沒有相關聯的mutex或者mutex並沒有被鎖定,則std::system_error就會被拋出,錯誤碼是std::errc::operation_not_permitted.

std::unique_lock::mutex 函數

mutex_type* mutex() const; (since C++11)
返回一個指向所關聯的mutex的指針,或者如果沒有相關聯的mutex的話就返回一個空指針。

std::unique_lock::owns_lock 函數

bool owns_lock() const; (since C++11)
檢查 *this 是否已經鎖住mutex.
是,則返回true;否則返回false.

std::unique_lock::operator bool 函數

explicit operator bool() const; (since C++11)

檢查 *this 是否已鎖定一個mutex. 高效地調用owns_lock().
是,則返回true;否則返回false.

示例程序

#include <mutex>
#include <thread>
#include <chrono>
#include <iostream>
 
struct Box {
    explicit Box(int num) : num_things{num} {}
 
    int num_things;
    std::mutex m;
};
 
void transfer(Box &a, Box &b, int num)
{
    // don't actually take the locks yet
    std::unique_lock<std::mutex> lock1(a.m, std::defer_lock);
    std::unique_lock<std::mutex> lock2(b.m, std::defer_lock);
 
    // lock both unique_locks without deadlock
    std::lock(lock1, lock2);
 
    a.num_things -= num;
    b.num_things += num;
 
    // 'a.m' and 'b.m' mutexes unlocked outside of '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::cout << "acc1: " << acc1.num_things << std::endl;
    std::cout << "acc2: " << acc2.num_things << std::endl;
}

/** Output: 
acc1: 95
acc2: 55
**/

(譯註: 原示例程序沒有任何輸出且變量命名比較令人費解,故略作修改。)

(完)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章