c++11 新特性實戰 (一):多執行緒操作

鬼谷子com發表於2020-09-29

c++11 新特性實戰 (一)

c++11多執行緒操作

執行緒

thread

int main()
{
    thread t1(Test1);
    t1.join();
    thread t2(Test2);
    t2.join();
    thread t3 = t1;
    thread t4(t1);
    thread t5 = std::move(t1);
    thread t6(std::move(t1));
    return 0;
}

t3,t4建立失敗,因為thread的拷貝構造和賦值運算子過載的原型是:

thread(const thread&) = delete;
thread& operator=(const thread&) = delete;

被禁用了,但是t5, t6執行緒是建立成功的。std::move把t1轉換為右值,呼叫的是函式原型為thread& operator=(thread&& _Other) noexceptthread(thread&& _Other) noexcept

當執行緒物件t1被移動拷貝和移動賦值給t5和t6的時候,t1就失去了執行緒控制權,也就是一個執行緒只能同時被一個執行緒物件所控制。最直觀的是t1.joinable()返回值為false,joinable()函式後面介紹。

使用類成員函式作為執行緒引數

class Task
{
public:
    Task(){}
    void Task1() {}
    void Task2() {}
private:
};

int main()
{
    Task task;
    thread t3(&Task::Task1, &task);
    t3.join();
    return 0;
}

關鍵點是要建立一個類物件,並作為第二個引數傳入thread()執行緒的建構函式中去。

管理當前執行緒的函式

yield

此函式的準確性為依賴於實現,特別是使用中的 OS 排程器機制和系統狀態。例如,先進先出實時排程器( Linux 的 SCHED_FIFO )將懸掛當前執行緒並將它放到準備執行的同優先順序執行緒的佇列尾(而若無其他執行緒在同優先順序,則 yield 無效果)。

#include <iostream>
#include <chrono>
#include <thread>
 
// 建議其他執行緒執行一小段時間的“忙睡眠”
void little_sleep(std::chrono::microseconds us)
{
    auto start = std::chrono::high_resolution_clock::now();
    auto end = start + us;
    do {
        std::this_thread::yield();
    } while (std::chrono::high_resolution_clock::now() < end);
}
 
int main()
{
    auto start = std::chrono::high_resolution_clock::now();
 
    little_sleep(std::chrono::microseconds(100));
 
    auto elapsed = std::chrono::high_resolution_clock::now() - start;
    std::cout << "waited for "
              << std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count()
              << " microseconds\n";
}

get_id

這個函式不用過多介紹了,就是用來獲取當前執行緒id的,用來標識執行緒的身份。

 std::thread::id this_id = std::this_thread::get_id();

sleep_for

位於this_thread名稱空間下,msvc下支援兩種時間引數。

std::this_thread::sleep_for(2s);
std::this_thread::sleep_for(std::chrono::seconds(1));

sleep_untile

引數構建起來挺麻煩的,一般場景下要求執行緒睡眠的就用sleep_for就行了

using std::chrono::system_clock;
time_t tt = system_clock::to_time_t(system_clock::now());
struct std::tm *ptm = localtime(&tt);
 std::this_thread::sleep_until(system_clock::from_time_t(mktime(ptm)));

互斥

mutex

對於互斥量看到一個很好的比喻:

單位上有一臺印表機(共享資料a),你要用印表機(執行緒1要運算元據a),同事老王也要用印表機(執行緒2也要運算元據a),但是印表機同一時間只能給一個人用,此時,規定不管是誰,在用印表機之前都要向領導申請許可證(lock),用完後再向領導歸還許可證(unlock),許可證總共只有一個,沒有許可證的人就等著在用印表機的同事用完後才能申請許可證(阻塞,執行緒1lock互斥量後其他執行緒就無法lock,只能等執行緒1unlock後,其他執行緒才能lock),那麼,這個許可證就是互斥量。互斥量保證了使用印表機這一過程不被打斷。

程式碼示例:

mutex mtx;

int gNum = 0;
void Test1()
{
    mtx.lock();
    for(int n = 0; n < 5; ++n)
        gNum++;
    mtx.unlock();
}

void Test2()
{
    std::cout << "gNum = " << gNum << std::endl;
}

int main()
{
    thread t1(Test1);
    t1.join();
    thread t2(Test2);
    t2.join();
    return 0;
}

join()表示主執行緒等待子執行緒結束再繼續執行,如果我們的期望是列印迴圈自增之後的gNum的值,那t1.join()就放在t2建立之前呼叫。因為t2的建立就標誌著t2執行緒建立好然後開始執行了

通常mutex不單獨使用,因為lock和unlock必須配套使用,如果忘記unlock很可能造成死鎖,即使unlock寫了,但是如果在執行之前程式捕獲到異常,也還是一樣會死鎖。如何解決使用mutex造成的死鎖問題呢?下面介紹unique_gard和lock_guard的時候詳細說明。

timed_mutex

提供互斥設施,實現有時限鎖定

std::mutex cout_mutex; // 控制到 std::cout 的訪問
std::timed_mutex mutex;

void job(int id)
{
    using Ms = std::chrono::milliseconds;
    std::ostringstream stream;

    for (int i = 0; i < 3; ++i) {
        if (mutex.try_lock_for(Ms(100))) {
            stream << "success ";
            std::this_thread::sleep_for(Ms(100));
            mutex.unlock();
        } else {
            stream << "failed ";
        }
        std::this_thread::sleep_for(Ms(100));
    }

    std::lock_guard<std::mutex> lock(cout_mutex);
    std::cout << "[" << id << "] " << stream.str() << "\n";
}

int main()
{
    std::vector<std::thread> threads;
    for (int i = 0; i < 4; ++i) {
        threads.emplace_back(job, i);
    }

    for (auto& i: threads) {
        i.join();
    }
}

這裡的第28行衍生出一個知識點:STL的emplace_back函式。這是c++11新增的容器類的操作函式,如果第二個引數忽略,用法和push_back相似,都是在stl後面追加元素。函式原型:

template<class... _Valty>
decltype(auto) emplace_back(_Valty&&... _Val);

是一個變長的模板函式,例子中的程式碼傳遞的是一個函式指標jobemplace_back的實現會把job傳遞給std::thread的建構函式,與push_back需要是std::thread型別作為引數不同,所以emplace_back是直接在容器中構造了要新增的元素,省去了再次把引數拷貝到stl中的過程,效率更高。目前來看還沒有什麼副作用,所以推薦以後在使用stl的時候使用emplace_back取代push_back.

使用timed_mutex的時候也無法用unique_lock這樣的RAII機制來控制加解鎖操作,所以不同的互斥量的使用場景要區分清楚。在對的時候使用對的東西也是碼農進階的一個標誌~

try_lock_until的例子:

void f()
{
    auto now=std::chrono::steady_clock::now();
    test_mutex.try_lock_until(now + std::chrono::seconds(10));
    std::cout << "hello world\n";
}

int main()
{
    std::lock_guard<std::timed_mutex> l(test_mutex);
    std::thread t(f);
    t.join();
}

recursive_mutex

提供能被同一執行緒遞迴鎖定的互斥設施

recursive_mutex 類是同步原語,能用於保護共享資料免受從個多執行緒同時訪問。

recursive_mutex 提供排他性遞迴所有權語義:

  1. 呼叫方執行緒在從它成功呼叫 locktry_lock 開始的時期裡佔有 recursive_mutex 。此時期間,執行緒可以進行對 locktry_lock 的附加呼叫。所有權的時期線上程呼叫 unlock 匹配次數時結束。

  2. 執行緒佔有 recursive_mutex 時,若其他所有執行緒試圖要求 recursive_mutex 的所有權,則它們將阻塞(對於呼叫 lock )或收到 false 返回值(對於呼叫 try_lock )。

  3. 可鎖定 recursive_mutex 次數的最大值是未指定的,但抵達該數後,對 lock 的呼叫將丟擲 std::system_error 而對 try_lock 的呼叫將返回 false 。

recursive_mutex 在仍為某執行緒佔有時被銷燬,則程式行為未定義。 recursive_mutex 類滿足互斥體 (Mutex) 標準佈局型別**(StandardLayoutType) 的所有要求。

#include <iostream>
#include <thread>
#include <string>
#include <mutex>
 
class X {
    std::recursive_mutex m;
    std::string shared;
  public:
    void fun1() {
      std::lock_guard<std::recursive_mutex> lk(m);
      shared = "fun1";
      std::cout << "in fun1, shared variable is now " << shared << '\n';
    }
    void fun2() {
      std::lock_guard<std::recursive_mutex> lk(m);
      shared = "fun2";
      std::cout << "in fun2, shared variable is now " << shared << '\n';
      fun1(); // 遞迴鎖在此處變得有用
      std::cout << "back in fun2, shared variable is " << shared << '\n';
    };
};
 
int main() 
{
    X x;
    std::thread t1(&X::fun1, &x);
    std::thread t2(&X::fun2, &x);
    t1.join();
    t2.join();
}

這裡的 std::recursive_mutex m;如果修改成std::mutex m;程式執行的時候會直接崩潰,原因是兩個std::lock_guard使用的是同一個std::mutex,造成func1()加鎖的時候死鎖 。

std::recursive_mutexstd::mutex最顯著的區別是,前者對同一個互斥量重複加鎖,釋放的時候是遞迴釋放,所以在此場景中不會產生std::mutex一樣的死鎖問題。

recursive_timed_mutex

提供能被同一執行緒遞迴鎖定的互斥設施,並實現有時限鎖定

使用方法和std::recursive_mutex類似,不做詳細介紹~

shared_mutex(C++17)

shared_timed_mutex(C++14)

這兩個屬於更高標準的std包含的內容,感興趣的讀者自行了解~

通用互斥管理

lock_guard

void Test1()
{
    std::lock_guard<std::mutex> lg(mtx);
    for(int n = 0; n < 5; ++n)
    {
        gNum++;
        std::cout << "gNum = " << gNum << std::endl;
    }
}
int main()
{
    thread t1(Test1);
    thread t2(Test1);
    t1.join();
    t2.join();
    return 0;
}

lock_guard相當於利用RAII機制(“資源獲取就是初始化”)把mutex封裝了一下,在構造中lock,在析構中unlock。避免了中間過程出現異常導致的mutex不能夠正常unlock.自己在寫程式碼的過程中也可以通過RAII機制封裝一個簡單的堆記憶體管理的類:

template<typename Type>
class WRaii
{
public:
    WRaii(const Type& value){
        m_Type = new Type(value);
    }
    ~WRaii(){
        if(m_Type)
            delete m_Type;
    }
    Type* operator ->() const
    {
        return m_Type;
    }
private:
    Type*   m_Type  = nullptr;
};

class Test
{
public:
    Test(int num) { m_Num = num; }
    void Do() { std::cout << __FUNCTION__ << std::endl;}
    int Num() { return m_Num; }
private:
    int m_Num = 5;
};

int main()
{
    WRaii<int> ra(5);//呼叫int的構造
    WRaii<Test> ra1(6);//呼叫Test的構造
    int res = ra1->Num();
}

其實這個例子不太合適,這樣寫好之後就相當於一個棧記憶體的變數了,不如直接int a(5)這樣寫,哈哈,其實想要實現的是如果有需要成對呈現的操作,且有邏輯上的先後關係的話就可以把成對的操作放到上面模板類的構造和析構裡面,這樣就能夠確保成對的操作成對出現了

scoped_lock(c++17)

unique_lock

unique_lock和lock_guard不同的是:unique_lock的成員函式lockunlock允許使用者更靈活的加鎖和解鎖。

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock

std::mutex mtx;           // mutex for critical section

void print_block (int n, char c) {
  // critical section (exclusive access to std::cout signaled by lifetime of lck):
  std::unique_lock<std::mutex> lck (mtx, std::defer_lock);
  bool res = lck.try_lock();
  res = mtx.try_lock();
  for (int i=0; i<n; ++i) { std::cout << c; }
  std::cout << '\n';
}

int main ()
{
  std::thread th1 (print_block,50,'*');
  std::thread th2 (print_block,50,'$');

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

  return 0;
}

c++11有三種加鎖策略:

策略 tag type 描述
(預設) 請求鎖,阻塞當前執行緒直到成功獲得鎖。
std::defer_lock std::defer_lock_t 不請求鎖。
std::try_to_lock std::try_to_lock_t 嘗試請求鎖,但不阻塞執行緒,鎖不可用時也會立即返回。
std::adopt_lock std::adopt_lock_t 假定當前執行緒已經獲得互斥物件的所有權,所以不再請求鎖。

上面的例子中用到了std::defer_lock策略,第十行res為true,是一行res為false,不能對同一mutex重複加鎖。這裡如果加鎖策略是預設值,執行到第十行的時候程式會直接崩潰,原因我還不太清楚~,總之try_lock和預設的鎖策略是衝突的,不能一起使用。

std::mutex mt;
std::unique_lock<std::mutex> lck(mt, std::defer_lock);
assert(lck.owns_lock() == false);
lck.lock();
assert(lck.owns_lock() == true);

演示第一種加鎖策略,其他策略請讀者自行嘗試~

defer_lock_t

try_to_lock_t

adopt_lock_t

defer_lock

try_to_lock

adopt_lock

上面6個代表了三種加鎖策略的型別。

通用鎖演算法

try_lock

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::try_lock
#include <chrono>

std::mutex foo,bar;

void task_a () {
    foo.lock();
    std::cout << "task a\n";
    bar.lock();
    // ...
//    std::this_thread::sleep_for(std::chrono::microseconds(1000)); //1
    foo.unlock();
//    std::this_thread::sleep_for(std::chrono::microseconds(1000)); //2
    bar.unlock();
}

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

int main ()
{
    std::thread th1 (task_a);
    std::thread th2 (task_b);
    th1.join();
    th2.join();
    return 0;
}

std::try_lock全域性函式

嘗試使用其try_lock成員函式鎖定所有作為引數傳遞的物件(非阻塞)。

該函式為每個引數(第一個a,然後b,最後是cde中的其他引數,以相同的順序)呼叫try_lock成員函式,直到所有呼叫成功,或者其中一個呼叫失敗(通過返回 錯誤或引發異常)。

如果函式由於呼叫失敗而結束,則對try_lock呼叫成功的所有物件都將解鎖,並且該函式將返回鎖定失敗的物件的引數順序號。 對引數列表中的其餘物件不執行進一步的呼叫。

13和15行都註釋掉的話, x = -1 表示foo和bar都加鎖成功

13去掉註釋, x = 0, 表示引數列表裡第一個加鎖失敗的是foo

15行去掉註釋, x = 1,表示引數列表裡第一個加鎖失敗的是bar

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

鎖定作為引數傳遞的所有物件,並在必要時阻止呼叫執行緒。

該函式使用對物件的成員lock,try_lock和unlock的未指定呼叫序列來鎖定物件,以確保所有引數在返回時都被鎖定(不產生任何死鎖)。

如果該函式無法鎖定所有物件(例如,由於內部呼叫之一引發了異常),則該函式會在失敗之前首先解鎖成功鎖定的所有物件(如果有)。

注:有點像資料庫事務的邏輯,要成功都成功,有一個失敗就rollback。

單次呼叫

once_flag

call_once

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

呼叫傳遞引數args的fn,除非另一個執行緒已經執行(或正在執行)具有相同標誌的對call_once的呼叫。

如果另一個執行緒已經在主動執行帶有相同標誌的對call_once的呼叫,則將導致被動執行:被動執行不呼叫fn但不會返回,直到主動執行本身返回並且此時所有可見副作用都已同步在所有使用相同標誌的併發呼叫中。

如果對call_once的主動呼叫通過丟擲異常(傳播到其呼叫執行緒)而結束,並且存在被動執行,則在這些被動執行中選擇一個,並稱為新的主動呼叫。

請注意,一旦返回了主動執行,所有當前的被動執行和將來對call_once的呼叫(具有相同標誌)也將返回而不會變為主動執行。

活動執行使用fn和args的左值或右值引用的衰變副本,而忽略fn返回的值

條件變數

condition_variable

// condition_variable example
#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_id (int id) {
  //(id != 1)
     //std::this_thread::sleep_for(MS(10));
  std::unique_lock<std::mutex> lck(mtx);
  while (!ready) cv.wait(lck);//RAII機制阻塞執行緒,ready的判斷是為了防止如果有的執行緒還沒有跑到這一步主執行緒就呼叫了go()函式,則會造成部分執行緒未執行wait,也就不會接收到notify_all()通知
  // ...
  std::cout << "thread " << id << '\n';
}

void go() {
  std::unique_lock<std::mutex> lck(mtx);
  ready = true;
  cv.notify_all();
}

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

  std::cout << "10 threads ready to race...\n";
  go();                       // go!

  for (auto& th : threads) th.join();

  return 0;
}

程式碼分析:程式碼實現的是建立10個執行緒,然後讓執行緒都處於等待的狀態,然後通過對同一個mutex操作的notify_all函式同時喚醒10個執行緒,列印順序就是10個執行緒的名次。

這裡需要簡單說一下std::condition_variable::wait()函式,在wait()的時候會釋放鎖,在被notify_one或者notify_all喚醒之後重新上鎖。wait()的一個過載形式是:

template<class _Predicate>
void wait(unique_lock<mutex>& _Lck, _Predicate _Pred);

_Pred是一個返回bool的表示式,當返回值為true,wait()接收到notify訊號就解除阻塞;當返回值為false,wait()接收到notify訊號依然阻塞。

所以通過我們對wait()的瞭解就可以清楚的分析出程式的執行流程,目前程式執行的結果是:

1 2 5 8 9 0 3 4 6 7

如果在15行的迴圈里加上列印語句會發現所有的子執行緒都wait()成功了,然後釋放了鎖,go()函式獲取到鎖就可以走接下來的流程,通知所有的執行緒競爭執行。

如果加上12、13行,先執行go()了 ready = true之後就只有id=1的執行緒會阻塞了,所以例子中的寫法還是稍微有點問題的,感興趣的朋友可以自己去擴充~

    #include <iostream>
    #include <string>
    #include <thread>
    #include <mutex>
    #include <condition_variable>
    
    std::mutex m;
    std::condition_variable cv;
    std::string data;
    bool ready = false;
    bool processed = false;
    
    void worker_thread()
    {
        // 等待直至 main() 傳送資料
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{return ready;});
    
        // 等待後,我們佔有鎖。
        std::cout << "Worker thread is processing data\n";
        data += " after processing";
    
        // 傳送資料回 main()
        processed = true;
        std::cout << "Worker thread signals data processing completed\n";
    
        // 通知前完成手動解鎖,以避免等待執行緒才被喚醒就阻塞(細節見 notify_one )
        lk.unlock();
        cv.notify_one();
    }
    
    int main()
    {
        std::thread worker(worker_thread);
    
        data = "Example data";
        // 傳送資料到 worker 執行緒
        {
            std::lock_guard<std::mutex> lk(m);
            ready = true;
            std::cout << "main() signals data ready for processing\n";
        }
        cv.notify_one();
    
        // 等候 worker
        {
            std::unique_lock<std::mutex> lk(m);
            cv.wait(lk, []{return processed;});
        }
        std::cout << "Back in main(), data = " << data << '\n';
    
        worker.join();
    }
    

執行結果:

main() signals data ready for processing
Worker thread is processing data
Worker thread signals data processing completed
Back in main(), data = Example data after processing

condition_variable 類是同步原語,能用於阻塞一個執行緒,或同時阻塞多個執行緒,直至另一執行緒修改共享變數(條件)並通知 condition_variable

有意修改變數的執行緒必須

  1. 獲得 std::mutex (常通過 std::lock_guard

  2. 在保有鎖時進行修改

  3. std::condition_variable 上執行 notify_onenotify_all (不需要為通知保有鎖)

    即使共享變數是原子的,也必須在互斥下修改它,以正確地釋出修改到等待的執行緒。

    任何有意在 std::condition_variable 上等待的執行緒必須

    1. 在與用於保護共享變數者相同的互斥上獲得 std::unique_lock<std::mutex>
    2. 執行下列之一:
      1. 檢查條件,是否為已更新或提醒它的情況
      2. 執行 waitwait_forwait_until ,等待操作自動釋放互斥,並懸掛執行緒的執行。
      3. condition_variable 被通知時,時限消失或虛假喚醒發生,執行緒被喚醒,且自動重獲得互斥。之後執行緒應檢查條件,若喚醒是虛假的,則繼續等待。

    放一下wait_for的例子:

    // condition_variable::wait_for example
    #include <iostream>           // std::cout
    #include <thread>             // std::thread
    #include <chrono>             // std::chrono::seconds
    #include <mutex>              // std::mutex, std::unique_lock
    #include <condition_variable> // std::condition_variable, std::cv_status
    
    std::condition_variable cv;
    
    int value;
    
    void read_value() {
      std::cin >> value;
      cv.notify_one();
    }
    
    int main ()
    {
      std::cout << "Please, enter an integer (I'll be printing dots): \n";
      std::thread th (read_value);
    
      std::mutex mtx;
      std::unique_lock<std::mutex> lck(mtx);
      while (cv.wait_for(lck,std::chrono::seconds(1))==std::cv_status::timeout) {
        std::cout << '.' << std::endl;
      }
      std::cout << "You entered: " << value << '\n';
    
      th.join();
    
      return 0;
    }
    

condition_variable_any

與condition_variable相同,不同之處在於其等待函式可以將任何可鎖定型別用作引數(condition_variable物件只能採用unique_lock )。 除此之外,它們是相同的。

// condition_variable_any::wait (with predicate)
#include <iostream>           // std::cout
#include <thread>             // std::thread, std::this_thread::yield
#include <mutex>              // std::mutex
#include <condition_variable> // std::condition_variable_any

std::mutex mtx;
std::condition_variable_any cv;

int cargo = 0;
bool shipment_available() {return cargo!=0;}

void consume (int n) {
  for (int i=0; i<n; ++i) {
    mtx.lock();
    cv.wait(mtx,shipment_available);
    // consume:
    std::cout << cargo << '\n';
    cargo=0;
    mtx.unlock();
  }
}

int main ()
{
  std::thread consumer_thread (consume,10);

  // produce 10 items when needed:
  for (int i=0; i<10; ++i) {
    while (shipment_available()) std::this_thread::yield();
    mtx.lock();
    cargo = i+1;
    cv.notify_one();
    mtx.unlock();
  }

  consumer_thread.join();

  return 0;
}

notify_all_at_thread_exit

// notify_all_at_thread_exit
#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_id (int id) {
  std::unique_lock<std::mutex> lck(mtx);
  while (!ready) cv.wait(lck);
  // ...
  std::cout << "thread " << id << '\n';
}

void go() {
  std::unique_lock<std::mutex> lck(mtx);
  std::notify_all_at_thread_exit(cv,std::move(lck));
  ready = true;
}

int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(print_id,i);
  std::cout << "10 threads ready to race...\n";

  std::thread(go).detach();   // go!

  for (auto& th : threads) th.join();

  return 0;
}

跟之前的例子差不多,看一下應該就能理解~

cv_status

wait_for()的返回值型別。

Future

promise

// promise example
#include <iostream>       // std::cout
#include <functional>     // std::ref
#include <thread>         // std::thread
#include <future>         // std::promise, std::future

void print_int (std::future<int>& fut) {
  int x = fut.get();
  std::cout << "value: " << x << '\n';
}

int main ()
{
  std::promise<int> prom;                      // create promise

  std::future<int> fut = prom.get_future();    // engagement with future

  std::thread th1 (print_int, std::ref(fut));  // send future to new thread

  prom.set_value (10);                         // fulfill promise
                                               // (synchronizes with getting the future)
  th1.join();
  return 0;
}

Promise是一個物件,它可以儲存要由Future物件(可能在另一個執行緒中)檢索的T型別的值,並提供一個同步點。

在構造上,promise物件與新的共享狀態相關聯,在它們上可以儲存T型別的值或從std :: exception派生的異常。

可以通過呼叫成員get_future將該共享狀態與Future的物件相關聯。 呼叫之後,兩個物件共享相同的共享狀態:
-Promise物件是非同步提供程式,應在某個時候為共享狀態設定一個值。
-Future物件是一個非同步返回物件,可以檢索共享狀態的值,並在必要時等待其準備就緒。

共享狀態的生存期至少要持續到與之關聯的最後一個物件釋放它或銷燬它為止。 因此,如果它也與Future相關聯,則它可以倖免最初獲得它的Promise物件。

std::future::get()是阻塞的,直到另外一個執行緒呼叫std::promise::set_value()。

packaged_task

// packaged_task example
#include <iostream>     // std::cout
#include <future>       // std::packaged_task, std::future
#include <chrono>       // std::chrono::seconds
#include <thread>       // std::thread, std::this_thread::sleep_for

// count down taking a second for each value:
int countdown (int from, int to) {
  for (int i=from; i!=to; --i) {
    std::cout << i << '\n';
    std::this_thread::sleep_for(std::chrono::seconds(1));
  }
  std::cout << "Lift off!\n";
  return from-to;
}

int main ()
{
  std::packaged_task<int(int,int)> tsk (countdown);   // set up packaged_task
  std::future<int> ret = tsk.get_future();            // get future

  std::thread th (std::move(tsk),10,0);   // spawn thread to count down from 10 to 0

  // ...

  int value = ret.get();                  // wait for the task to finish and get result

  std::cout << "The countdown lasted for " << value << " seconds.\n";

  th.join();

  return 0;
}

std::packaged_task用法類似std::function,但是返回結果可以通過關聯的std::future獲取。

future

// future example
#include <iostream>       // std::cout
#include <future>         // std::async, std::future
#include <chrono>         // std::chrono::milliseconds

// a non-optimized way of checking for prime numbers:
bool is_prime (int x) {
  for (int i=2; i<x; ++i) if (x%i==0) return false;
  return true;
}

int main ()
{
  // call function asynchronously:
  std::future<bool> fut = std::async (is_prime,444444443); 

  // do something while waiting for function to set future:
  std::cout << "checking, please wait";
  std::chrono::milliseconds span (100);
  while (fut.wait_for(span)==std::future_status::timeout)
    std::cout << '.' << std::flush;

  bool x = fut.get();     // retrieve return value

  std::cout << "\n444444443 " << (x?"is":"is not") << " prime.\n";

  return 0;
}

shared_future

async

// async example
#include <iostream>       // std::cout
#include <future>         // std::async, std::future

// a non-optimized way of checking for prime numbers:
bool is_prime (int x) {
  std::cout << "Calculating. Please, wait...\n";
  for (int i=2; i<x; ++i) if (x%i==0) return false;
  return true;
}

int main ()
{
  // call is_prime(313222313) asynchronously:
  std::future<bool> fut = std::async (is_prime,313222313);

  std::cout << "Checking whether 313222313 is prime.\n";
  // ...

  bool ret = fut.get();      // waits for is_prime to return

  if (ret) std::cout << "It is prime!\n";
  else std::cout << "It is not prime.\n";

  return 0;
}

launch

future_status

Future錯誤

future_error
future_category
future_errc

Future目前本身用的比較少,目前只是列出來比較常見的一些類和函式的用法,之後再遇到複雜的使用場景的時候再把相關內容補上~

執行緒池程式碼:

#ifndef THREADPOOL_H
#define THREADPOOL_H

#include <deque>
#include <thread>
#include <vector>
#include <mutex>
#include <condition_variable>

class Task
{
public:
    Task(int id);
    void Do();
private:
    int m_ID;
};

class MyThread : public std::thread
{
public:
    MyThread();
private:
    bool isFree;
};

class ThreadPool
{
public:
    ThreadPool();
~ThreadPool();
    void Start(int tNum);
    void AppendTask(const Task* task);
    void Stop();
    void Join();

private:
    void work();
private:
    std::deque<Task*> m_Tasks;
    std::vector<std::thread*> m_Threads;
    std::mutex  m_Mutex;
    std::condition_variable m_Cond;

    bool m_IsRunning;
};

#endif // THREADPOOL_H

#include <functional>
#include <iostream>
#include <chrono>
#include "threadpool.h"

using DoTask = std::function<void()>();
using MS = std::chrono::seconds;

ThreadPool::ThreadPool()
{

}

ThreadPool::~ThreadPool()
{
    if (m_IsRunning)
        Stop();
}

void ThreadPool::Start(int tNum)
{
    m_IsRunning = true;
    for(int n = 0; n < tNum; ++n)
        m_Threads.emplace_back(new std::thread(&ThreadPool::work, this));

    std::cout << __FUNCTION__ << std::endl;
}

void ThreadPool::AppendTask(const Task *task)
{
    m_Tasks.push_back(const_cast<Task*>(task));
    m_Cond.notify_one();
}

void ThreadPool::Stop()
{
    {
        std::unique_lock<std::mutex> mt(m_Mutex);
        m_IsRunning = false;
        m_Cond.notify_all();
    }

    for(std::thread* t : m_Threads)
    {
        if(t->joinable())
            t->join();
    }
}

void ThreadPool::Join()
{

}

void ThreadPool::work()
{
    while(m_IsRunning)
    {
        std::unique_lock<std::mutex> ul(m_Mutex);

        Task* task = nullptr;
        if(!m_Tasks.empty())
        {
            task = m_Tasks.back();
            m_Tasks.pop_back();
        }
        else
        {
            std::cout << "wait id = " << std::this_thread::get_id() << std::endl;
            m_Cond.wait(ul);
        }
        if(task)
            task->Do();
    }
}

Task::Task(int id)
    : m_ID(id)
{

}

void Task::Do()
{
    std::this_thread::sleep_for(MS(m_ID));
    std::cout << "thread id: " << std::this_thread::get_id() << " do task: " << m_ID << std::endl;
}

相關文章