c++11線程支持庫:thread和mutex

thread

thread頭文件主要包含thread類和this_thread命名空間。

namespace std {
namespace this_thread {
    thread::id get_id() noexcept;
    void yield() noexcept; //建議實現重新調度各執行線程,以允許其他線程的運行
    template <class Clock, class Duration>
        void sleep_until(const chrono::time_point<Clock, Duration>& abs_time);
    template <class Rep, class Period>
        void sleep_for(const chrono::duration<Rep, Period>& rel_time);
}
}

thread::id是一個輕量級的平凡拷貝的類,作爲std::thread對象的唯一標識符,庫爲它實現了一些比較操作符、operator <<、和hash特化。

namespace std {
class thread {
 public:
    class id;
    typedef /* 由實現定義 */ native_handle_type;
 
    // 構造/複製/銷燬:
    thread() noexcept;
    template <class F, class ...Args> explicit thread(F&& f, Args&&... args);
    ~thread();
    thread(const thread&) = delete;
    thread(thread&&) noexcept;
    thread& operator=(const thread&) = delete;
    thread& operator=(thread&&) noexcept;
 
    // 成員:
    void swap(thread&) noexcept;
    bool joinable() const noexcept; //檢查 thread 對象是否標識活躍的執行線程
    void join(); //阻塞當前線程,直至 *this 所標識的線程完成其執行
    void detach();
    id get_id() const noexcept;
    native_handle_type native_handle();
 
    // 靜態成員:
    static unsigned hardware_concurrency() noexcept;
};
}

如果一個thread對象標識着一個活躍的運行線程,稱之爲joinable,具體來說是判斷std::thread::get_id()的返回值是不是等於std::thread::id()(std::thread::id的默認值)。因此thread經默認構造後不是joinable的;一個運行完畢但還沒被joined的線程仍被認爲是活躍的,所以是joinable的;一個被joined的線程是非joinable的。

#include <iostream>
#include <thread>
#include <chrono>
void foo()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
}
int main()
{
    std::thread t;
    std::cout << "before starting, joinable: " << std::boolalpha << t.joinable() << '\n';
    t = std::thread(foo);
    std::cout << "after starting, joinable: " << t.joinable() << '\n';
    t.join();
    std::cout << "after joining, joinable: " << t.joinable()<< '\n';
}

mutex

mutex頭文件主要包括:

  • mutex系列類
    std::mutex,最基本的 mutex 類。
    std::timed_mutex,定時 mutex 類。
    std::recursive_mutex,遞歸 mutex 類。
    std::recursive_timed_mutex,定時遞歸 mutex 類。
  • lock 類
    std::lock_guard,方便線程對互斥量上鎖。
    std::unique_lock,方便線程對互斥量上鎖,但提供了更詳細的上鎖和解鎖控制。
  • 輔助類型
    std::once_flag
    std::adopt_lock_t
    std::defer_lock_t
    std::try_to_lock_t
  • 函數
    std::try_lock,嘗試同時對多個互斥量上鎖。
    std::lock,可以同時對多個互斥量上鎖。
    std::call_once,如果多個線程需要同時調用某個函數,call_once 可以保證多個線程對該函數只調用一次。

mutex的概要聲明:

class mutex {
 public:
    constexpr mutex() noexcept;
    ~mutex();
    mutex(const mutex&) = delete;
    mutex& operator=(const mutex&) = delete;
 
    void lock();
    bool try_lock();
    void unlock();
    typedef /* 由實現定義 */ native_handle_type;
    native_handle_type native_handle();
};

recursive_mutex 允許同一個線程對互斥量多次上鎖,來獲得對互斥量對象的多層所有權。
timed_mutex 比 mutex 多了兩個成員函數:

template <class Rep, class Period>
bool try_lock_for(const chrono::duration<Rep, Period>& rel_time);
template <class Clock, class Duration>
bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time);

try_lock_for 接受一個時間範圍,表示在這一段時間範圍之內線程如果沒有獲得鎖則被阻塞住,如果在此期間獲得了對互斥量的鎖,返回true,如果超時仍未獲得鎖,則返回 false。
m.try_lock_until(std::chrono::system_clock::now() + std::chrono::seconds(5));
等價於m.try_lock_for(std::chrono::seconds(5));

lock_guard和unique_lock

lock_guard是一個mutex wrapper,概要聲明:

template <class Mutex>
class lock_guard {
 public:
    typedef Mutex mutex_type;
    explicit lock_guard(mutex_type& m);
    lock_guard(mutex_type& m, adopt_lock_t);
    ~lock_guard();
    lock_guard(lock_guard const&) = delete;
    lock_guard& operator=(lock_guard const&) = delete;
 private:
    mutex_type& pm;
};

構造時加鎖,析構時解鎖,adopt構造接管一個已上鎖的mutex。
unique_lock實現的功能類似,但提供更精細的控制,概要聲明:

template <class Mutex>
class unique_lock {
 public:
    typedef Mutex mutex_type;
 
    // 構造/複製/銷燬:
    unique_lock() noexcept; (1)
    unique_lock( unique_lock&& other ) noexcept; (2)
    explicit unique_lock(mutex_type& m); (3)
    unique_lock(mutex_type& m, defer_lock_t) noexcept; (4)
    unique_lock(mutex_type& m, try_to_lock_t); (5)
    unique_lock(mutex_type& m, adopt_lock_t); (6)
    template <class Clock, class Duration>
    unique_lock(mutex_type& m, const chrono::time_point<Clock, Duration>& abs_time); (7)
    template <class Rep, class Period>
    unique_lock(mutex_type& m, const chrono::duration<Rep, Period>& rel_time); (8)
    ~unique_lock();
    unique_lock(unique_lock const&) = delete;
    unique_lock& operator=(unique_lock const&) = delete;
    unique_lock(unique_lock&& u) noexcept;
    unique_lock& operator=(unique_lock&& u) noexcept;
 
    // 鎖定:
    void lock();
    bool try_lock();
    template <class Rep, class Period>
    bool try_lock_for(const chrono::duration<Rep, Period>& rel_time);
    template <class Clock, class Duration>
    bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time);
    void unlock();
 
    // 修改器:
    void swap(unique_lock& u) noexcept;
    mutex_type *release() noexcept; (9)
 
    // 觀察器:
    bool owns_lock() const noexcept; //檢查mutex是否已上鎖
    explicit operator bool () const noexcept; //同上
    mutex_type* mutex() const noexcept;
 
 private:
    mutex_type *pm;
    bool owns;
};

(1) 構造無關聯互斥的 unique_lock。
(2) 移動構造函數。以 other 的內容初始化 unique_lock 。令 other 無關聯互斥。
(3-8) 構造以 m 爲關聯互斥的 unique_lock 。另外:
3) 通過調用 m.lock() 鎖定關聯互斥。
4) 不鎖定關聯互斥。
5) 通過調用 m.try_lock() 嘗試鎖定關聯互斥而不阻塞。
6) 假定調用方線程已佔有 m 。
7) 通過調用 m.try_lock_for(timeout_duration) 嘗試鎖定關聯互斥。
8) 通過調用 m.try_lock_until(timeout_time) 嘗試鎖定關聯互斥。
(9) release斷開*this和對應mutex(如果存在的話)的聯繫。release不會進行解鎖操作,因此如果*this擁有mutex的所有權,調用者應該負責mutex的解鎖。release返回對應mutex,如果沒有對應mutex則返回空指針。

std::adopt_lock_t,std::defer_lock_t,std::try_to_lock_t是爲了輔助lock_guard和unique_lock的結構體類型,分別有std::adopt_lock,std::defer_lock,std::try_to_lock常量對象存在。

函數

// 依次調用每個對象的 try_lock
// 若調用 try_lock 失敗,則不再進一步調用 try_lock,並對任何已鎖對象調用 unlock
// 若調用 try_lock 拋出異常,則在重拋前對任何已鎖對象調用 unlock
// 成功時返回 -1 ,否則爲鎖定失敗對象的下標值(從0開始計數)
template <class L1, class L2, class... L3>
int try_lock(L1&, L2&, L3&...);

// 鎖定給定的可鎖定對象,使用免死鎖算法避免死鎖
// 若調用 lock 或 unlock 導致異常,則在重拋前對任何已鎖的對象調用 unlock
template <class L1, class L2, class... L3>
void lock(L1&, L2&, L3&...);

struct once_flag {
    constexpr once_flag() noexcept;
    once_flag(const once_flag&) = delete;
    once_flag& operator=(const once_flag&) = delete;
};

template<class Callable, class ...Args>
void call_once(once_flag& flag, Callable func, Args&&... args);

call_once 執行一次可調用對象 f ,即使同時從多個線程調用。細節爲:

  • 若在調用 call_once 的時刻, flag 指示已經調用了 f ,則 call_once 立即返回。
  • 否則, call_once 以參數 std​::​forward<Args>(args)... 調用 ​std​::​forward<Callable>(f)。不同於 std::thread 構造函數或 std::async ,call_once 不移動或複製參數,因爲不需要轉移它們到另一執行線程。
    • 若該調用拋異常,則傳播異常給 call_once 的調用方,並且不翻轉 flag ,以令其他調用繼續嘗試。
    • 若該調用正常返回,則翻轉 flag ,並保證以同一 flag 對 call_once 的其他調用立即返回。

call_once 的例子:

#include <iostream>
#include <thread>
#include <mutex>

std::once_flag flag1, flag2;

void simple_do_once()
{
    std::call_once(flag1, [](){ std::cout << "Simple example: called once\n"; });
}

void may_throw_function(bool do_throw)
{
  if (do_throw) {
    std::cout << "throw: call_once will retry\n";
    throw std::exception();
  }
  std::cout << "Didn't throw, call_once will not attempt again\n"; // 保證一次
}

void do_once(bool do_throw)
{
  try {
    std::call_once(flag2, may_throw_function, do_throw);
  }
  catch (...) {
  }
}

int main()
{
    std::thread st1(simple_do_once);
    std::thread st2(simple_do_once);
    std::thread st3(simple_do_once);
    std::thread st4(simple_do_once);
    st1.join();
    st2.join();
    st3.join();
    st4.join();

    std::thread t1(do_once, true);
    std::thread t2(do_once, true);
    std::thread t3(do_once, false);
    std::thread t4(do_once, true);
    t1.join();
    t2.join();
    t3.join();
    t4.join();
    
    return 0;
}

主要參考自cppreference

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