【C/C++開發】C++11 併發指南三(std::mutex 詳解)

本系列文章主要介紹 C++11 併發編程,計劃分爲 9 章介紹 C++11 的併發和多線程編程,分別以下:css

C++11 併發指南一(C++11 多線程初探)(本章計劃 1-2 篇,已完成 1 篇)html

C++11 併發指南二(std::thread 詳解)(本章計劃 1-2 篇,已完成 1 篇)ios

C++11 併發指南三(std::mutex 詳解)(本章計劃 1-2 篇,已完成 2 篇)編程

  1. C++11 併發指南三(std::mutex 詳解)
  2. C++11 併發指南三(Lock 詳解)

C++11 併發指南四(future 詳解)(本章計劃 3 篇,已完成 3 篇)promise

  1. C++11 併發指南四(<future> 詳解一 std::promise 介紹)
  2. C++11 併發指南四(<future> 詳解二 std::packaged_task 介紹)
  3. C++11 併發指南四(<future> 詳解三 std::future & std::shared_future)

C++11 併發指南五(std::condition_variable 詳解)(本章計劃 1 篇,已完成 1 篇)多線程

C++11 併發指南六(atomic 詳解)(本章計劃 4 篇,已完成 4 篇)併發

  1. C++11 併發指南六(atomic 類型詳解一 atomic_flag 介紹)
  2. C++11 併發指南六( <atomic> 類型詳解二 std::atomic )
  3. C++11 併發指南六(atomic 類型詳解三 std::atomic (續))
  4. C++11 併發指南六(atomic 類型詳解四 C 風格原子操做介紹)

C++11 併發指南七(C++11 內存模型)(本章計劃 3-4 篇,已完成 1 篇,3 篇在草稿中)異步

  1. C++11 併發指南七(C++11 內存模型一:介紹)

C++11 併發指南八(雜項)(本章計劃 1-2 篇,已完成 0 篇,1 篇在草稿中)async

C++11 併發指南九(綜合運用: C++11 多線程下生產者消費者模型詳解)(本章計劃 1-2 篇,已完成 1 篇)ide

按照目前的進度來看大約完成了整體進度的 60% 左右,但願對你們理解和掌握 C++11 的併發和多線程編程有必定幫助。



上一篇《C++11 併發指南二(std::thread 詳解)》中主要講到了 std::thread 的一些用法,並給出了兩個小例子,本文將介紹 std::mutex 的用法。

Mutex 又稱互斥量,C++ 11中與 Mutex 相關的類(包括鎖類型)和函數都聲明在 <mutex> 頭文件中,因此若是你須要使用 std::mutex,就必須包含 <mutex> 頭文件。

<mutex> 頭文件介紹

Mutex 系列類(四種)

  • std::mutex,最基本的 Mutex 類。
  • std::recursive_mutex,遞歸 Mutex 類。
  • std::time_mutex,定時 Mutex 類。
  • std::recursive_timed_mutex,定時遞歸 Mutex 類。

Lock 類(兩種)

  • std::lock_guard,與 Mutex RAII 相關,方便線程對互斥量上鎖。
  • std::unique_lock,與 Mutex RAII 相關,方便線程對互斥量上鎖,但提供了更好的上鎖和解鎖控制。

其餘類型

  • 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 能夠保證多個線程對該函數只調用一次。

std::mutex 介紹

下面以 std::mutex 爲例介紹 C++11 中的互斥量用法。

std::mutex 是C++11 中最基本的互斥量,std::mutex 對象提供了獨佔全部權的特性——即不支持遞歸地對 std::mutex 對象上鎖,而 std::recursive_lock 則能夠遞歸地對互斥量對象上鎖。

std::mutex 的成員函數

  • 構造函數,std::mutex不容許拷貝構造,也不容許 move 拷貝,最初產生的 mutex 對象是處於 unlocked 狀態的。
  • lock(),調用線程將鎖住該互斥量。線程調用該函數會發生下面 3 種狀況:(1). 若是該互斥量當前沒有被鎖住,則調用線程將該互斥量鎖住,直到調用 unlock以前,該線程一直擁有該鎖。(2). 若是當前互斥量被其餘線程鎖住,則當前的調用線程被阻塞住。(3). 若是當前互斥量被當前調用線程鎖住,則會產生死鎖(deadlock)。
  • unlock(), 解鎖,釋放對互斥量的全部權。
  • try_lock(),嘗試鎖住互斥量,若是互斥量被其餘線程佔有,則當前線程也不會被阻塞。線程調用該函數也會出現下面 3 種狀況,(1). 若是當前互斥量沒有被其餘線程佔有,則該線程鎖住互斥量,直到該線程調用 unlock 釋放互斥量。(2). 若是當前互斥量被其餘線程鎖住,則當前調用線程返回 false,而並不會被阻塞掉。(3). 若是當前互斥量被當前調用線程鎖住,則會產生死鎖(deadlock)。

下面給出一個與 std::mutex 的小例子(參考

複製代碼
#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 (int argc, const char* argv[]) {
    std::thread threads[10];
    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;
}
複製代碼

std::recursive_mutex 介紹

std::recursive_mutex 與 std::mutex 同樣,也是一種能夠被上鎖的對象,可是和 std::mutex 不一樣的是,std::recursive_mutex 容許同一個線程對互斥量屢次上鎖(即遞歸上鎖),來得到對互斥量對象的多層全部權,std::recursive_mutex 釋放互斥量時須要調用與該鎖層次深度相同次數的 unlock(),可理解爲 lock() 次數和 unlock() 次數相同,除此以外,std::recursive_mutex 的特性和 std::mutex 大體相同。

std::time_mutex 介紹

std::time_mutex 比 std::mutex 多了兩個成員函數,try_lock_for(),try_lock_until()。

try_lock_for 函數接受一個時間範圍,表示在這一段時間範圍以內線程若是沒有得到鎖則被阻塞住(與 std::mutex 的 try_lock() 不一樣,try_lock 若是被調用時沒有得到鎖則直接返回 false),若是在此期間其餘線程釋放了鎖,則該線程能夠得到對互斥量的鎖,若是超時(即在指定時間內仍是沒有得到鎖),則返回 false。

try_lock_until 函數則接受一個時間點做爲參數,在指定時間點未到來以前線程若是沒有得到鎖則被阻塞住,若是在此期間其餘線程釋放了鎖,則該線程能夠得到對互斥量的鎖,若是超時(即在指定時間內仍是沒有得到鎖),則返回 false。

下面的小例子說明了 std::time_mutex 的用法(參考)。

複製代碼
#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;
}
複製代碼

std::recursive_timed_mutex 介紹

和 std:recursive_mutex 與 std::mutex 的關係同樣,std::recursive_timed_mutex 的特性也能夠從 std::timed_mutex 推導出來,感興趣的同鞋能夠自行查閱。 ;-)

std::lock_guard 介紹

與 Mutex RAII 相關,方便線程對互斥量上鎖。例子(參考):

複製代碼
#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;
}
複製代碼

std::unique_lock 介紹

與 Mutex RAII 相關,方便線程對互斥量上鎖,但提供了更好的上鎖和解鎖控制。例子(參考):

複製代碼
#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);
    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;
}
複製代碼

好了,本文暫時講到這裏,還剩下 std::try_lock,std::lock,std::call_once 三個函數沒有講到,留在下一篇博客中講吧 ;-)

前面兩講《C++11 併發指南二(std::thread 詳解)》,《C++11 併發指南三(std::mutex 詳解)》分別介紹了 std::thread 和 std::mutex,相信讀者對 C++11 中的多線程編程有了一個最基本的認識,本文將介紹 C++11 標準中 <future> 頭文件裏面的類和相關函數。

<future> 頭文件中包含了如下幾個類和函數:

  • Providers 類:std::promise, std::package_task
  • Futures 類:std::future, shared_future.
  • Providers 函數:std::async()
  • 其餘類型:std::future_error, std::future_errc, std::future_status, std::launch.

std::promise 類介紹

promise 對象能夠保存某一類型 T 的值,該值可被 future 對象讀取(可能在另一個線程中),所以 promise 也提供了一種線程同步的手段。在 promise 對象構造時能夠和一個共享狀態(一般是std::future)相關聯,並能夠在相關聯的共享狀態(std::future)上保存一個類型爲 T 的值。

能夠經過 get_future 來獲取與該 promise 對象相關聯的 future 對象,調用該函數以後,兩個對象共享相同的共享狀態(shared state)

  • promise 對象是異步 Provider,它能夠在某一時刻設置共享狀態的值。
  • future 對象能夠異步返回共享狀態的值,或者在必要的狀況下阻塞調用者並等待共享狀態標誌變爲 ready,而後才能獲取共享狀態的值。

下面以一個簡單的例子來講明上述關係

複製代碼
#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'; // 打印 value: 10.
}

int main ()
{
    std::promise<int> prom; // 生成一個 std::promise<int> 對象.
    std::future<int> fut = prom.get_future(); // 和 future 關聯.
    std::thread t(print_int, std::ref(fut)); // 將 future 交給另一個線程t.
    prom.set_value(10); // 設置共享狀態的值, 此處和線程t保持同步.
    t.join();
    return 0;
}
複製代碼

std::promise 構造函數

default (1)
promise();
with allocator (2)
template <class Alloc> promise (allocator_arg_t aa, const Alloc& alloc);
copy [deleted] (3)
promise (const promise&) = delete;
move (4)
promise (promise&& x) noexcept;
  1. 默認構造函數,初始化一個空的共享狀態。
  2. 帶自定義內存分配器的構造函數,與默認構造函數相似,可是使用自定義分配器來分配共享狀態。
  3. 拷貝構造函數,被禁用。
  4. 移動構造函數。

另外,std::promise 的 operator= 沒有拷貝語義,即 std::promise 普通的賦值操做被禁用,operator= 只有 move 語義,因此 std::promise 對象是禁止拷貝的。

例子:

複製代碼
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <future>         // std::promise, std::future

std::promise<int> prom;

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

int main ()
{
    std::thread th1(print_global_promise);
    prom.set_value(10);
    th1.join();

    prom = std::promise<int>();    // prom 被move賦值爲一個新的 promise 對象.

    std::thread th2 (print_global_promise);
    prom.set_value (20);
    th2.join();

  return 0;
}
複製代碼

 std::promise::get_future 介紹

該函數返回一個與 promise 共享狀態相關聯的 future 返回的 future 對象能夠訪問由 promise 對象設置在共享狀態上的值或者某個異常對象。只能從 promise 共享狀態獲取一個 future 對象。在調用該函數以後,promise 對象一般會在某個時間點準備好(設置一個值或者一個異常對象),若是不設置值或者異常,promise 對象在析構時會自動地設置一個 future_error 異常(broken_promise)來設置其自身的準備狀態。上面的例子中已經提到了 get_future,此處再也不重複。

std::promise::set_value 介紹

generic template (1)
void set_value (const T& val);
void set_value (T&& val);
specializations (2)
void promise<R&>::set_value (R& val);   // when T is a reference type (R&)
void promise<void>::set_value (void);   // when T is void

設置共享狀態的值,此後 promise 的共享狀態標誌變爲 ready.

 std::promise::set_exception 介紹

爲 promise 設置異常,此後 promise 的共享狀態變標誌變爲 ready,例子以下,線程1從終端接收一個整數,線程2將該整數打印出來,若是線程1接收一個非整數,則爲 promise 設置一個異常(failbit) ,線程2 在std::future::get 是拋出該異常。

複製代碼
#include <iostream>       // std::cin, std::cout, std::ios
#include <functional>     // std::ref
#include <thread>         // std::thread
#include <future>         // std::promise, std::future
#include <exception>      // std::exception, std::current_exception

void get_int(std::promise<int>& prom) {
    int x;
    std::cout << "Please, enter an integer value: ";
    std::cin.exceptions (std::ios::failbit);   // throw on failbit
    try {
        std::cin >> x;                         // sets failbit if input is not int
        prom.set_value(x);
    } catch (std::exception&) {
        prom.set_exception(std::current_exception());
    }
}

void print_int(std::future<int>& fut) {
    try {
        int x = fut.get();
        std::cout << "value: " << x << '\n';
    } catch (std::exception& e) {
        std::cout << "[exception caught: " << e.what() << "]\n";
    }
}

int main ()
{
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();

    std::thread th1(get_int, std::ref(prom));
    std::thread th2(print_int, std::ref(fut));

    th1.join();
    th2.join();
    return 0;
}
複製代碼

std::promise::set_value_at_thread_exit 介紹

設置共享狀態的值,可是不將共享狀態的標誌設置爲 ready,當線程退出時該 promise 對象會自動設置爲 ready。若是某個 std::future 對象與該 promise 對象的共享狀態相關聯,而且該 future 正在調用 get,則調用 get 的線程會被阻塞,當線程退出時,調用 future::get 的線程解除阻塞,同時 get 返回 set_value_at_thread_exit 所設置的值。注意,該函數已經設置了 promise 共享狀態的值,若是在線程結束以前有其餘設置或者修改共享狀態的值的操做,則會拋出 future_error( promise_already_satisfied )。

std::promise::swap 介紹

交換 promise 的共享狀態。

上一講《C++11 併發指南四(<future> 詳解一 std::promise 介紹)》主要介紹了 <future> 頭文件中的 std::promise 類,本文主要介紹 std::packaged_task。

std::packaged_task 包裝一個可調用的對象,而且容許異步獲取該可調用對象產生的結果,從包裝可調用對象意義上來說,std::packaged_task 與 std::function 相似,只不過 std::packaged_task 將其包裝的可調用對象的執行結果傳遞給一個 std::future 對象(該對象一般在另一個線程中獲取 std::packaged_task 任務的執行結果)。

std::packaged_task 對象內部包含了兩個最基本元素,1、被包裝的任務(stored task),任務(task)是一個可調用的對象,如函數指針、成員函數指針或者函數對象,2、共享狀態(shared state),用於保存任務的返回值,能夠經過 std::future 對象來達到異步訪問共享狀態的效果。

能夠經過 std::packged_task::get_future 來獲取與共享狀態相關聯的 std::future 對象。在調用該函數以後,兩個對象共享相同的共享狀態,具體解釋以下:

  • std::packaged_task 對象是異步 Provider,它在某一時刻經過調用被包裝的任務來設置共享狀態的值。
  • std::future 對象是一個異步返回對象,經過它能夠得到共享狀態的值,固然在必要的時候須要等待共享狀態標誌變爲 ready.

std::packaged_task 的共享狀態的生命週期一直持續到最後一個與之相關聯的對象被釋放或者銷燬爲止。下面一個小例子大體講了 std::packaged_task 的用法:

複製代碼
#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 << "Finished!\n";
    return from - to;
}

int main ()
{
    std::packaged_task<int(int,int)> task(countdown); // 設置 packaged_task
    std::future<int> ret = task.get_future(); // 得到與 packaged_task 共享狀態相關聯的 future 對象.

    std::thread th(std::move(task), 10, 0);   //建立一個新線程完成計數任務.

    int value = ret.get();                    // 等待任務完成並獲取結果.

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

    th.join();
    return 0;
}
複製代碼

執行結果爲:

複製代碼
concurrency ) ./Packaged_Task1 
10
9
8
7
6
5
4
3
2
1
Finished!
The countdown lasted for 10 seconds.
複製代碼

std::packaged_task 構造函數

default (1)
packaged_task() noexcept;
initialization (2)
template <class Fn>
  explicit packaged_task (Fn&& fn);
with allocator (3)
template <class Fn, class Alloc>
  explicit packaged_task (allocator_arg_t aa, const Alloc& alloc, Fn&& fn);
copy [deleted] (4)
packaged_task (const packaged_task&) = delete;
move (5)
packaged_task (packaged_task&& x) noexcept;

std::packaged_task 構造函數共有 5 中形式,不過拷貝構造已經被禁用了。下面簡單地介紹一下上述幾種構造函數的語義:

  1. 默認構造函數,初始化一個空的共享狀態,而且該 packaged_task 對象無包裝任務。
  2. 初始化一個共享狀態,而且被包裝任務由參數 fn 指定。
  3. 帶自定義內存分配器的構造函數,與默認構造函數相似,可是使用自定義分配器來分配共享狀態。
  4. 拷貝構造函數,被禁用。
  5. 移動構造函數。

下面例子介紹了各種構造函數的用法:

複製代碼
#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

int main ()
{
    std::packaged_task<int(int)> foo; // 默認構造函數.

    // 使用 lambda 表達式初始化一個 packaged_task 對象.
    std::packaged_task<int(int)> bar([](int x){return x*2;});

    foo = std::move(bar); // move-賦值操做,也是 C++11 中的新特性.

    // 獲取與 packaged_task 共享狀態相關聯的 future 對象.
    std::future<int> ret = foo.get_future();

    std::thread(std::move(foo), 10).detach(); // 產生線程,調用被包裝的任務.

    int value = ret.get(); // 等待任務完成並獲取結果.
    std::cout << "The double of 10 is " << value << ".\n";

return 0;
}
複製代碼

與 std::promise 相似, std::packaged_task 也禁用了普通的賦值操做運算,只容許 move 賦值運算。

std::packaged_task::valid 介紹

檢查當前 packaged_task 是否和一個有效的共享狀態相關聯,對於由默認構造函數生成的 packaged_task 對象,該函數返回 false,除非中間進行了 move 賦值操做或者 swap 操做。

請看下例:

複製代碼
#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

// 在新線程中啓動一個 int(int) packaged_task.
std::future<int> launcher(std::packaged_task<int(int)>& tsk, int arg)
{
    if (tsk.valid()) {
        std::future<int> ret = tsk.get_future();
        std::thread (std::move(tsk),arg).detach();
        return ret;
    }
    else return std::future<int>();
}

int main ()
{
    std::packaged_task<int(int)> tsk([](int x){return x*2;});

    std::future<int> fut = launcher(tsk,25);

    std::cout << "The double of 25 is " << fut.get() << ".\n";

    return 0;
}
複製代碼

std::packaged_task::get_future 介紹

返回一個與 packaged_task 對象共享狀態相關的 future 對象。返回的 future 對象能夠得到由另一個線程在該 packaged_task 對象的共享狀態上設置的某個值或者異常。

請看例子(其實前面已經講了 get_future 的例子):

複製代碼
#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

int main ()
{
    std::packaged_task<int(int)> tsk([](int x) { return x * 3; })); // package task

    std::future<int> fut = tsk.get_future();   // 獲取 future 對象.

    std::thread(std::move(tsk), 100).detach();   // 生成新線程並調用packaged_task.

    int value = fut.get();                     // 等待任務完成, 並獲取結果.

    std::cout << "The triple of 100 is " << value << ".\n";

    return 0;
}
複製代碼

std::packaged_task::operator()(Args... args) 介紹

調用該 packaged_task 對象所包裝的對象(一般爲函數指針,函數對象,lambda 表達式等),傳入的參數爲 args. 調用該函數通常會發生兩種狀況:

  • 若是成功調用 packaged_task 所包裝的對象,則返回值(若是被包裝的對象有返回值的話)被保存在 packaged_task 的共享狀態中。
  • 若是調用 packaged_task 所包裝的對象失敗,而且拋出了異常,則異常也會被保存在 packaged_task 的共享狀態中。

以上兩種狀況都使共享狀態的標誌變爲 ready,所以其餘等待該共享狀態的線程能夠獲取共享狀態的值或者異常並繼續執行下去。

共享狀態的值能夠經過在 future 對象(由 get_future得到)上調用 get 來得到。

因爲被包裝的任務在 packaged_task 構造時指定,所以調用 operator() 的效果由 packaged_task 對象構造時所指定的可調用對象來決定:

  • 若是被包裝的任務是函數指針或者函數對象,調用 std::packaged_task::operator() 只是將參數傳遞給被包裝的對象。
  • 若是被包裝的任務是指向類的非靜態成員函數的指針,那麼 std::packaged_task::operator() 的第一個參數應該指定爲成員函數被調用的那個對象,剩餘的參數做爲該成員函數的參數。
  • 若是被包裝的任務是指向類的非靜態成員變量,那麼 std::packaged_task::operator() 只容許單個參數。

std::packaged_task::make_ready_at_thread_exit 介紹

該函數會調用被包裝的任務,並向任務傳遞參數,相似 std::packaged_task 的 operator() 成員函數。可是與 operator() 函數不一樣的是,make_ready_at_thread_exit 並不會當即設置共享狀態的標誌爲 ready,而是在線程退出時設置共享狀態的標誌。

若是與該 packaged_task 共享狀態相關聯的 future 對象在 future::get 處等待,則當前的 future::get 調用會被阻塞,直到線程退出。而一旦線程退出,future::get 調用繼續執行,或者拋出異常。

注意,該函數已經設置了 promise 共享狀態的值,若是在線程結束以前有其餘設置或者修改共享狀態的值的操做,則會拋出 future_error( promise_already_satisfied )。

std::packaged_task::reset() 介紹

重置 packaged_task 的共享狀態,可是保留以前的被包裝的任務。請看例子,該例子中,packaged_task 被重用了屢次:

複製代碼
#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

// a simple task:
int triple (int x) { return x*3; }

int main ()
{
    std::packaged_task<int(int)> tsk (triple); // package task


    std::future<int> fut = tsk.get_future();
    std::thread (std::move(tsk), 100).detach();
    std::cout << "The triple of 100 is " << fut.get() << ".\n";


    // re-use same task object:
    tsk.reset();
    fut = tsk.get_future();
    std::thread(std::move(tsk), 200).detach();
    std::cout << "Thre triple of 200 is " << fut.get() << ".\n";

    return 0;
}
複製代碼

std::packaged_task::swap() 介紹

交換 packaged_task 的共享狀態。

好了,std::packaged_task 介紹到這裏,本文參考了 http://www.cplusplus.com/reference/future/packaged_task/ 相關的內容。後一篇文章我將向你們介紹 std::future,std::shared_future 以及 std::future_error,另外還會介紹 <future> 頭文件中的 std::async,std::future_category 函數以及相關枚舉類型。

上一講《C++11 併發指南四(<future> 詳解二 std::packaged_task 介紹)》主要介紹了 <future> 頭文件中的 std::packaged_task 類,本文主要介紹 std::future,std::shared_future 以及 std::future_error,另外還會介紹 <future> 頭文件中的 std::async,std::future_category 函數以及相關枚舉類型。

std::future 介紹

前面已經屢次提到過 std::future,那麼 std::future 到底是什麼呢?簡單地說,std::future 能夠用來獲取異步任務的結果,所以能夠把它當成一種簡單的線程間同步的手段。std::future 一般由某個 Provider 建立,你能夠把 Provider 想象成一個異步任務的提供者,Provider 在某個線程中設置共享狀態的值,與該共享狀態相關聯的 std::future 對象調用 get(一般在另一個線程中) 獲取該值,若是共享狀態的標誌不爲 ready,則調用 std::future::get 會阻塞當前的調用者,直到 Provider 設置了共享狀態的值(此時共享狀態的標誌變爲 ready),std::future::get 返回異步任務的值或異常(若是發生了異常)。

一個有效(valid)的 std::future 對象一般由如下三種 Provider 建立,並和某個共享狀態相關聯。Provider 能夠是函數或者類,其實咱們前面都已經提到了,他們分別是:

一個 std::future 對象只有在有效(valid)的狀況下才有用(useful),由 std::future 默認構造函數建立的 future 對象不是有效的(除非當前非有效的 future 對象被 move 賦值另外一個有效的 future 對象)。

 在一個有效的 future 對象上調用 get 會阻塞當前的調用者,直到 Provider 設置了共享狀態的值或異常(此時共享狀態的標誌變爲 ready),std::future::get 將返回異步任務的值或異常(若是發生了異常)。

下面以一個簡單的例子說明上面一段文字吧(參考):

複製代碼
// 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 << '.';

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

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

    return 0;
}
複製代碼

 std::future 成員函數

std::future 構造函數

std::future 通常由 std::async, std::promise::get_future, std::packaged_task::get_future 建立,不過也提供了構造函數,以下表所示:

default (1)
future() noexcept;
copy [deleted] (2)
future (const future&) = delete;
move (3)
future (future&& x) noexcept;

 

不過 std::future 的拷貝構造函數是被禁用的,只提供了默認的構造函數和 move 構造函數(注:C++ 新特新)。另外,std::future 的普通賦值操做也被禁用,只提供了 move 賦值操做。以下代碼所示:

 std::future<int> fut;           // 默認構造函數
  fut = std::async(do_some_task);   // move-賦值操做。

std::future::share()

返回一個 std::shared_future 對象(本文後續內容將介紹 std::shared_future ),調用該函數以後,該 std::future 對象自己已經不和任何共享狀態相關聯,所以該 std::future 的狀態再也不是 valid 的了。

複製代碼
#include <iostream>       // std::cout
#include <future>         // std::async, std::future, std::shared_future

int do_get_value() { return 10; }

int main ()
{
    std::future<int> fut = std::async(do_get_value);
    std::shared_future<int> shared_fut = fut.share();

    // 共享的 future 對象能夠被屢次訪問.
    std::cout << "value: " << shared_fut.get() << '\n';
    std::cout << "its double: " << shared_fut.get()*2 << '\n';

    return 0;
}
複製代碼

std::future::get()

std::future::get 一共有三種形式,以下表所示(參考):

generic template (1)
T get();
reference specialization (2)
R& future<R&>::get();       // when T is a reference type (R&)
void specialization (3)
void future<void>::get();   // when T is void

當與該 std::future 對象相關聯的共享狀態標誌變爲 ready 後,調用該函數將返回保存在共享狀態中的值,若是共享狀態的標誌不爲 ready,則調用該函數會阻塞當前的調用者,而此後一旦共享狀態的標誌變爲 ready,get 返回 Provider 所設置的共享狀態的值或者異常(若是拋出了異常)。

請看下面的程序:

複製代碼
#include <iostream>       // std::cin, std::cout, std::ios
#include <functional>     // std::ref
#include <thread>         // std::thread
#include <future>         // std::promise, std::future
#include <exception>      // std::exception, std::current_exception

void get_int(std::promise<int>& prom) {
    int x;
    std::cout << "Please, enter an integer value: ";
    std::cin.exceptions (std::ios::failbit);   // throw on failbit
    try {
        std::cin >> x;                         // sets failbit if input is not int
        prom.set_value(x);
    } catch (std::exception&) {
        prom.set_exception(std::current_exception());
    }
}

void print_int(std::future<int>& fut) {
    try {
        int x = fut.get();
        std::cout << "value: " << x << '\n';
    } catch (std::exception& e) {
        std::cout << "[exception caught: " << e.what() << "]\n";
    }
}

int main ()
{
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();

    std::thread th1(get_int, std::ref(prom));
    std::thread th2(print_int, std::ref(fut));

    th1.join();
    th2.join();
    return 0;
}
複製代碼

std::future::valid()

檢查當前的 std::future 對象是否有效,即釋放與某個共享狀態相關聯。一個有效的 std::future 對象只能經過 std::async(), std::future::get_future 或者 std::packaged_task::get_future 來初始化。另外由 std::future 默認構造函數建立的 std::future 對象是無效(invalid)的,固然經過 std::future 的 move 賦值後該 std::future 對象也能夠變爲 valid。

複製代碼
#include <iostream>       // std::cout
#include <future>         // std::async, std::future
#include <utility>        // std::move

int do_get_value() { return 11; }

int main ()
{
    // 由默認構造函數建立的 std::future 對象,
    // 初始化時該 std::future 對象處於爲 invalid 狀態.
    std::future<int> foo, bar;
    foo = std::async(do_get_value); // move 賦值, foo 變爲 valid.
    bar = std::move(foo); // move 賦值, bar 變爲 valid, 而 move 賦值之後 foo 變爲 invalid.

    if (foo.valid())
        std::cout << "foo's value: " << foo.get() << '\n';
    else
        std::cout << "foo is not valid\n";

    if (bar.valid())
        std::cout << "bar's value: " << bar.get() << '\n';
    else
        std::cout << "bar is not valid\n";

    return 0;
}
複製代碼

std::future::wait()

等待與當前std::future 對象相關聯的共享狀態的標誌變爲 ready.

若是共享狀態的標誌不是 ready(此時 Provider 沒有在共享狀態上設置值(或者異常)),調用該函數會被阻塞當前線程,直到共享狀態的標誌變爲 ready。
一旦共享狀態的標誌變爲 ready,wait() 函數返回,當前線程被解除阻塞,可是 wait() 並不讀取共享狀態的值或者異常。下面的代碼說明了 std::future::wait() 的用法(參考

複製代碼
#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 do_check_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(do_check_prime, 194232491);

    std::cout << "Checking...\n";
    fut.wait();

    std::cout << "\n194232491 ";
    if (fut.get()) // guaranteed to be ready (and not block) after wait returns
        std::cout << "is prime.\n";
    else
        std::cout << "is not prime.\n";

    return 0;
}
複製代碼

執行結果以下:

concurrency ) ./Future-wait 
Checking...

194232491 is prime.
concurrency ) 

std::future::wait_for()

與 std::future::wait() 的功能相似,即等待與該 std::future 對象相關聯的共享狀態的標誌變爲 ready,該函數原型以下:

template <class Rep, class Period>
  future_status wait_for (const chrono::duration<Rep,Period>& rel_time) const;

而與 std::future::wait() 不一樣的是,wait_for() 能夠設置一個時間段 rel_time,若是共享狀態的標誌在該時間段結束以前沒有被 Provider 設置爲 ready,則調用 wait_for 的線程被阻塞,在等待了 rel_time 的時間長度後 wait_until() 返回,返回值以下:

返回值 描述
future_status::ready 共享狀態的標誌已經變爲 ready,即 Provider 在共享狀態上設置了值或者異常。
future_status::timeout 超時,即在規定的時間內共享狀態的標誌沒有變爲 ready。
future_status::deferred 共享狀態包含一個 deferred 函數。

請看下面的例子:

複製代碼
#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 do_check_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(do_check_prime, 194232491);

    std::cout << "Checking...\n";
    std::chrono::milliseconds span(1000); // 設置超時間隔.

    // 若是超時,則輸出".",繼續等待
    while (fut.wait_for(span) == std::future_status::timeout)
        std::cout << '.';

    std::cout << "\n194232491 ";
    if (fut.get()) // guaranteed to be ready (and not block) after wait returns
        std::cout << "is prime.\n";
    else
        std::cout << "is not prime.\n";

    return 0;
}
複製代碼

std::future::wait_until()

與 std::future::wait() 的功能相似,即等待與該 std::future 對象相關聯的共享狀態的標誌變爲 ready,該函數原型以下:

template <class Rep, class Period>
  future_status wait_until (const chrono::time_point<Clock,Duration>& abs_time) const;

而 與 std::future::wait() 不一樣的是,wait_until() 能夠設置一個系統絕對時間點 abs_time,若是共享狀態的標誌在該時間點到來以前沒有被 Provider 設置爲 ready,則調用 wait_until 的線程被阻塞,在 abs_time 這一時刻到來以後 wait_for() 返回,返回值以下:

返回值 描述
future_status::ready 共享狀態的標誌已經變爲 ready,即 Provider 在共享狀態上設置了值或者異常。
future_status::timeout 超時,即在規定的時間內共享狀態的標誌沒有變爲 ready。
future_status::deferred 共享狀態包含一個 deferred 函數。

 

std::shared_future 介紹

std::shared_future 與 std::future 相似,可是 std::shared_future 能夠拷貝、多個 std::shared_future 能夠共享某個共享狀態的最終結果(即共享狀態的某個值或者異常)。shared_future 能夠經過某個 std::future 對象隱式轉換(參見 std::shared_future 的構造函數),或者經過 std::future::share() 顯示轉換,不管哪一種轉換,被轉換的那個 std::future 對象都會變爲 not-valid.

std::shared_future 構造函數

std::shared_future 共有四種構造函數,以下表所示:

default (1)
shared_future() noexcept;
copy (2)
shared_future (const shared_future& x);
move (3)
shared_future (shared_future&& x) noexcept;
move from future (4)
shared_future (future<T>&& x) noexcept;

最後 move from future(4) 即從一個有效的 std::future 對象構造一個 std::shared_future,構造以後 std::future 對象 x 變爲無效(not-valid)。

std::shared_future 其餘成員函數

std::shared_future 的成員函數和 std::future 大部分相同,以下(每一個成員函數都給出了鏈接):

std::future_error 介紹

class future_error : public logic_error;

std::future_error 繼承子 C++ 標準異常體系中的 logic_error,有關 C++ 異常的繼承體系,請參考相關的C++教程 ;-)。

其餘與 std::future 相關的函數介紹

與 std::future 相關的函數主要是 std::async(),原型以下:

unspecified policy (1)
template <class Fn, class... Args>
  future<typename result_of<Fn(Args...)>::type>
    async(Fn&& fn, Args&&... args);
specific policy (2)
template <class Fn, class... Args>
  future<typename result_of<Fn(Args...)>::type>
    async(launch policy, Fn&& fn, Args&&... args);

上面兩組 std::async() 的不一樣之處是第一類 std::async 沒有指定異步任務(即執行某一函數)的啓動策略(launch policy),而第二類函數指定了啓動策略,詳見 std::launch 枚舉類型,指定啓動策略的函數的 policy 參數能夠是launch::async,launch::deferred,以及二者的按位或( | )。

std::async() 的 fn 和 args 參數用來指定異步任務及其參數。另外,std::async() 返回一個 std::future 對象,經過該對象能夠獲取異步任務的值或異常(若是異步任務拋出了異常)。

下面介紹一下 std::async 的用法。

複製代碼
#include <stdio.h>
#include <stdlib.h>

#include <cmath>
#include <chrono>
#include <future>
#include <iostream>

double ThreadTask(int n) {
    std::cout << std::this_thread::get_id()
        << " start computing..." << std::endl;

    double ret = 0;
    for (int i = 0; i <= n; i++) {
        ret += std::sin(i);
    }

    std::cout << std::this_thread::get_id()
        << " finished computing..." << std::endl;
    return ret;
}

int main(int argc, const char *argv[])
{
    std::future<double> f(std::async(std::launch::async, ThreadTask, 100000000));

#if 0
    while(f.wait_until(std::chrono::system_clock::now() + std::chrono::seconds(1))
            != std::future_status::ready) {
        std::cout << "task is running...\n";
    }
#else
    while(f.wait_for(std::chrono::seconds(1))
            != std::future_status::ready) {
        std::cout << "task is running...\n";
    }
#endif

    std::cout << f.get() << std::endl;

    return EXIT_SUCCESS;
}
複製代碼

 

其餘與 std::future 相關的枚舉類介紹

下面介紹與 std::future 相關的枚舉類型。與 std::future 相關的枚舉類型包括:

enum class future_errc;
enum class future_status;
enum class launch;

下面分別介紹以上三種枚舉類型:

std::future_errc 類型

std::future_errc 類型描述以下(參考):

類型
取值
描述
broken_promise 0 與該 std::future 共享狀態相關聯的 std::promise 對象在設置值或者異常以前一被銷燬。
future_already_retrieved 1 與該 std::future 對象相關聯的共享狀態的值已經被當前 Provider 獲取了,即調用了 std::future::get 函數。
promise_already_satisfied 2 std::promise 對象已經對共享狀態設置了某一值或者異常。
no_state 3 無共享狀態。

std::future_status 類型(參考

std::future_status 類型主要用在 std::future(或std::shared_future)中的 wait_for 和 wait_until 兩個函數中的。

類型 取值
描述
future_status::ready 0 wait_for(或wait_until) 由於共享狀態的標誌變爲 ready 而返回。
future_status::timeout 1 超時,即 wait_for(或wait_until) 由於在指定的時間段(或時刻)內共享狀態的標誌依然沒有變爲 ready 而返回。
future_status::deferred 2 共享狀態包含了 deferred 函數。

std::launch 類型

該枚舉類型主要是在調用 std::async 設置異步任務的啓動策略的。

類型 描述
launch::async Asynchronous: 異步任務會在另一個線程中調用,並經過共享狀態返回異步任務的結果(通常是調用 std::future::get() 獲取異步任務的結果)。
launch::deferred Deferred: 異步任務將會在共享狀態被訪問時調用,至關與按需調用(即延遲(deferred)調用)。

請看下例(參考):

複製代碼
#include <iostream>                // std::cout
#include <future>                // std::async, std::future, std::launch
#include <chrono>                // std::chrono::milliseconds
#include <thread>                // std::this_thread::sleep_for

void
do_print_ten(char c, int ms)
{
    for (int i = 0; i < 10; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(ms));
        std::cout << c;
    }
}

int
main()
{
    std::cout << "with launch::async:\n";
    std::future < void >foo =
        std::async(std::launch::async, do_print_ten, '*', 100);
    std::future < void >bar =
        std::async(std::launch::async, do_print_ten, '@', 200);
    // async "get" (wait for foo and bar to be ready):
    foo.get();
    bar.get();
    std::cout << "\n\n";

    std::cout << "with launch::deferred:\n";
    foo = std::async(std::launch::deferred, do_print_ten, '*', 100);
    bar = std::async(std::launch::deferred, do_print_ten, '@', 200);
    // deferred "get" (perform the actual calls):
    foo.get();
    bar.get();
    std::cout << '\n';

    return 0;
}
複製代碼

在個人機器上執行結果:

with launch::async:
*@**@**@**@**@*@@@@@

with launch::deferred:
**********@@@@@@@@@@
相關文章
相關標籤/搜索