1. 程式人生 > >C++併發程式設計(C++11)

C++併發程式設計(C++11)

前言
首先需要說明,本部落格的主要內容參考自Forhappy && Haippy博主的分享,本人主要是參照博主的資料進行了學習和總結,並適當的衍生或補充了相關的其他知識內容。
C++11有了std::thread 以後,可以在語言層面編寫多執行緒程式了,直接的好處就是多執行緒程式的可移植性得到了很大的提高。
C++11 新標準中引入了四個標頭檔案來支援多執行緒程式設計,他們分別是,,,和。
:該頭文主要聲明瞭兩個類, std::atomic 和 std::atomic_flag,另外還聲明瞭一套 C 風格的原子型別和與 C 相容的原子操作的函式。
:該標頭檔案主要聲明瞭 std::thread 類,另外 std::this_thread 名稱空間也在該標頭檔案中。
:該標頭檔案主要聲明瞭與互斥量(mutex)相關的類,包括 std::mutex 系列類,std::lock_guard, std::unique_lock, 以及其他的型別和函式。
:該標頭檔案主要聲明瞭與條件變數相關的類,包括 std::condition_variable 和 std::condition_variable_any。
:該標頭檔案主要聲明瞭 std::promise, std::package_task 兩個 Provider 類,以及 std::future 和 std::shared_future 兩個 Future 類,另外還有一些與之相關的型別和函式,std::async() 函式就宣告在此標頭檔案中。

1. HelloWorld

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <thread> 
using namespace std;
void thread_task() 
{
    cout <<  "Hello World!" << std::endl;
}

int main(int argc, const char *argv[])
{
    thread t(thread_task);
    t.join();
    system("pause"
); return 0; }

這裡寫圖片描述

2. Thread Constructor

1)預設建構函式:thread() noexcept,建立一個空的 thread 執行物件。
2)初始化建構函式: template

#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
#include <functional>
#include <atomic>

using namespace std;

void exec_proc1(int
n) { for (int i = 0; i < 5; ++i) { cout << "pass value, executing thread " << n << endl; //阻止執行緒執行到10毫秒 this_thread::sleep_for(chrono::milliseconds(10)); } } void exec_proc2(int& n) { for (int i = 0; i < 5; ++i) { cout << "pass reference, executing thread " << n << endl; ++n; //阻止執行緒執行到10毫秒 this_thread::sleep_for(chrono::milliseconds(10)); } } int main() { int n = 0; // t1,使用預設建構函式,什麼都沒做 thread t1; // t2,使用有參建構函式,傳入函式名稱(地址)exec_pro1,並以傳值的方式傳入args // 將會執行exec_proc1中的程式碼 thread t2(exec_proc1, n + 1); // t3,使用有參建構函式,傳入函式名稱(地址)exec_pro1,並以傳引用的方式傳入args // 將會執行exec_proc1中的程式碼 thread t3(exec_proc2, ref(n)); // t4,使用移動建構函式,由t4接管t3的任務,t3不再是執行緒了 thread t4(move(t3)); // 可被 joinable 的 thread 物件必須在他們銷燬之前被主執行緒 join 或者將其設定為 detached. t2.join(); t4.join(); cout << "the result of n is " << n << endl; system("pause"); return 0; }

這裡寫圖片描述

3. 賦值操作

1)move 賦值操作:thread& operator= (thread&& rhs) noexcept,如果當前物件不可 joinable,需要傳遞一個右值引用(rhs)給 move 賦值操作;如果當前物件可被 joinable,則 terminate() 報錯。
2)拷貝賦值操作被禁用:thread& operator= (const thread&) = delete,thread 物件不可被拷貝。

#include <stdio.h>
#include <stdlib.h>
#include <chrono>   
#include <iostream>  
#include <thread>  
using namespace std;

void exec_produce(int duration) {
    //阻止執行緒執行到duration秒
    this_thread::sleep_for(chrono::seconds(duration));
    //this_thread::get_id()獲取當前執行緒id
    cout << "exec_produce thread " << this_thread::get_id()
        << " has sleeped " << duration << " seconds" << endl;
}

int main(int argc, const char *argv[])
{
    thread threads[5];
    cout << "create 5 threads ..." << endl;
    for (int i = 0; i < 5; i++) {
        threads[i] = thread(exec_produce, i + 1);
    }
    cout << "finished creating 5 threads, and waiting for joining" << endl;
    //下面程式碼會報錯,原因就是copy操作不可用,相當於是delete操作,所以報錯
    /*for(auto it : threads) {
        it.join();
    }*/
    for (auto& it: threads) {
        it.join();
    }
    cout << "Finished!!!" << endl;
    system("pause");
    return 0;
}

這裡寫圖片描述

其他相關函式作用說明
1)get_id() 獲取執行緒 ID。
2)joinable() 檢查執行緒是否可被 join
3)join() Join 執行緒。
4)detach() Detach 執行緒
5)swap() Swap 執行緒
6)native_handle() 返回 native handle。
7)hardware_concurrency() 檢測硬體併發特性

4. std::mutex

mutex 又稱互斥量,C++ 11中與 mutex 相關的類(包括鎖型別)和函式都宣告在 標頭檔案中,所以如果需要使用 std::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 可以保證多個執行緒對該函式只調用一次。

(1)std::mutex
std::mutex 是C++11 中最基本的互斥量,std::mutex 物件提供了獨佔所有權的特性——即不支援遞迴地對 std::mutex 物件上鎖,而 std::recursive_lock 則可以遞迴地對互斥量物件上鎖。

std::mutex 的成員函式
1)建構函式,std::mutex不允許拷貝構造,也不允許 move 拷貝,最初產生的 mutex 物件是處於 unlocked 狀態的。
2)lock(),呼叫執行緒將鎖住該互斥量。執行緒呼叫該函式會發生下面 3 種情況:(1). 如果該互斥量當前沒有被鎖住,則呼叫執行緒將該互斥量鎖住,直到呼叫 unlock之前,該執行緒一直擁有該鎖。(2). 如果當前互斥量被其他執行緒鎖住,則當前的呼叫執行緒被阻塞住。(3). 如果當前互斥量被當前呼叫執行緒鎖住,則會產生死鎖(deadlock)。
3)unlock(), 解鎖,釋放對互斥量的所有權。
4)try_lock(),嘗試鎖住互斥量,如果互斥量被其他執行緒佔有,則當前執行緒也不會被阻塞。執行緒呼叫該函式也會出現下面 3 種情況,(1). 如果當前互斥量沒有被其他執行緒佔有,則該執行緒鎖住互斥量,直到該執行緒呼叫 unlock 釋放互斥量。(2). 如果當前互斥量被其他執行緒鎖住,則當前呼叫執行緒返回 false,而並不會被阻塞掉。(3). 如果當前互斥量被當前呼叫執行緒鎖住,則會產生死鎖(deadlock)。

#include <iostream>      
#include <thread>      
#include <mutex>  
using namespace std;

/**
就像大家更熟悉的const一樣,volatile是一個型別修飾符(type specifier)。
它是被設計用來修飾被不同執行緒訪問和修改的變數。如果不加入volatile,
基本上會導致這樣的結果:要麼無法編寫多執行緒程式,要麼編譯器失去大量優化的機會。
**/
volatile int counter = 0; 
const int MAX_TIMES_VALUE = 10000;
mutex my_mutex;           

void my_task() {
    for (int i = 0; i < MAX_TIMES_VALUE; ++ i) {
        //嘗試獲取鎖,try_lock()失敗時返回false
        if (my_mutex.try_lock()) {   
            ++counter;
            my_mutex.unlock();
        }
    }
}

int main() {
    thread threads[10];
    for (int i = 0; i < 10; ++ i) {
        threads[i] = thread(my_task);
    }

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

    cout << "Finished : the result of counter is " << counter << endl;
    system("pause");
    return 0;
}

這裡寫圖片描述

(2)std::recursive_mutex
std::recursive_mutex 與 std::mutex 一樣,也是一種可以被上鎖的物件,但是和 std::mutex 不同的是,std::recursive_mutex 允許同一個執行緒對互斥量多次上鎖(即遞迴上鎖),來獲得對互斥量物件的多層所有權,std::recursive_mutex 釋放互斥量時需要呼叫與該鎖層次深度相同次數的 unlock(),可理解為 lock() 次數和 unlock() 次數相同,除此之外,std::recursive_mutex 的特性和 std::mutex 大致相同。

(3)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。

#include <iostream>     
#include <chrono>      
#include <thread>  
#include <mutex>  
using namespace std;

timed_mutex my_mutex;

void my_task(int val, char tag) {
    //每200ms嘗試獲取鎖,如果獲取到跳出while迴圈,否則輸出一次執行緒編號
    //比如0-200ms,在200ms之前如果獲取不到鎖,則執行緒阻塞,時間到了200ms如果取得了鎖,
    //則加鎖,否則返回false
    while (!my_mutex.try_lock_for(chrono::milliseconds(200))) {
        //int pid = this_thread::get_id().hash();
        cout << val;
    }
    //成功取得鎖,然後將執行緒sleep到1000ms
    this_thread::sleep_for(chrono::milliseconds(1000));
    cout << tag << endl;
    my_mutex.unlock();
}

int main ()
{
    thread threads[10];
    char end_tag[] = {'!', '@', '#', '$', '%', '^', '&', '*', '(', ')'};
    //建立10個執行緒,分別執行my_task()中的程式碼
    for (int i=0; i<10; ++i) {
        threads[i] = thread(my_task, i, end_tag[i]);
    }

    for (auto& it : threads) {
        it.join();
    }
    system("pause");
    return 0;
}

這裡寫圖片描述
結果分析:
0-9號執行緒從0ms時刻開始執行,由於執行緒排程的隨機性,假使最開始執行的是0號執行緒,則0號執行緒可以在0ms時刻便取得鎖,這時候0號執行緒持鎖sleep,這時執行緒排程會去執行1-9號執行緒,顯然這時是無法取得鎖的,所以什麼呼叫try_lock_for()在0-200ms內去嘗試取鎖,在200ms之前由於取不到鎖會分別阻塞,到了200ms這個時刻由於取鎖失敗,try_lock_for()返回false,所以在200ms這個時刻會在控制檯中輸出1-9這九個字元,之後類似的,直到到了1000ms這個時刻,0號執行緒釋放了鎖,並輸出與其對應的tag。
之後的過程便成了9個執行緒的排程執行的過程了,和上面描述基本類似的過程。

(4)std::recursive_timed_mutex
和 std:recursive_mutex 與 std::mutex 的關係一樣,std::recursive_timed_mutex 的特性也可以從 std::timed_mutex 推匯出來

(5)std::lock_guard
與 mutex RAII 相關,方便執行緒對互斥量上鎖,是一種自解鎖。std::lock_guard是一個區域性變數,建立時,對mutex 上鎖,析構時對mutex解鎖。這個功能在函式體比較長,尤其是存在多個分支的時候很有用

#include <iostream>       
#include <thread>        
#include <mutex>         
using namespace std;

mutex my_mutex;

void print (int x) {
    cout << "value is " << x;
    cout << endl; 
    this_thread::sleep_for(chrono::milliseconds(200));
}

void my_task (int id) {
    // lock_guard建立區域性變數my_lock,會在lock_guard的構造方法中對my_mutex加鎖
    lock_guard<mutex> my_lock (my_mutex);
    //由於自解鎖的作用,下面的程式碼相當於臨界區,執行過程不會被打斷
    print(id);
    //執行結束時會析構my_lock,然後在解構函式中對my_mutex解鎖
}

int main ()
{
    thread threads[10];

    for (int i=0; i<10; ++i) {
        threads[i] = thread(my_task,i+1);
    }

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

    system("pause");
    return 0;
}

這裡寫圖片描述

(6)std::unique_lock
與 mutex RAII 相關,方便執行緒對互斥量上鎖,但提供了更好的上鎖和解鎖控制,相對於std::lock_guard來說,std::unique_lock更加靈活,std::unique_lock不擁有與其關聯的mutex。建構函式的第二個引數可以指定為std::defer_lock,這樣表示在構造unique_lock時,傳入的mutex保持unlock狀態。然後通過呼叫std::unique_lock物件的lock()方法或者將將std::unique_lock物件傳入std::lock()方法來鎖定mutex。
std::unique_lock比std::lock_guard需要更大的空間,因為它需要儲存它所關聯的mutex是否被鎖定,如果被鎖定,在析構該std::unique_lock時,就需要unlock它所關聯的mutex。std::unique_lock的效能也比std::lock_guard稍差,因為在lock或unlock mutex時,還需要更新mutex是否鎖定的標誌。大多數情況下,推薦使用std::lock_guard但是如果需要更多的靈活性,比如上面這個例子,或者需要在程式碼之間傳遞lock的所有權,這可以使用std::unique_lock,std::unique_lock的靈活性還在於我們可以主動的呼叫unlock()方法來釋放mutex,因為鎖的時間越長,越會影響程式的效能,在一些特殊情況下,提前釋放mutex可以提高程式執行的效率。
使用std::unique_lock預設構造方法和std::lock_guard類似,多了可以主動unlock,其他相當於一個自解鎖,所以類似於unique_lock my_lock(my_mutex)的用法就不再舉例。
下面舉例使用兩個引數構造使用,和鎖的所有權傳遞問題

#include <iostream>  
#include <thread>  
#include <mutex>    
using namespace std;

mutex my_mutex;  
mutex some_mutex;

//使用含兩個引數的建構函式
void my_task (int n, char c) {
    unique_lock<mutex> my_lock (my_mutex, defer_lock);
    my_lock.lock();
    for (int i=0; i<n; ++i) {
        cout << c;
    }
    cout << endl;
    //會自動unlock
}

unique_lock<mutex> prepare_task()
{
    unique_lock<mutex> lock(some_mutex);

    cout << "print prepare data" << endl;
    //返回對some_mutex的所有權,尚未解鎖
    return lock;
}
void finish_task(int v)
{
    //取得prepare_task建立的鎖所有權
    unique_lock<mutex> lk(prepare_task());
    cout << "finished :" << v << endl;
    //析構,解鎖
}

int main ()
{
    thread t1 (my_task, 50, '1');
    thread t2 (my_task, 50, '2');

    t1.join();
    t2.join();

    thread threads[5];
    for(int i = 0; i < 5; ++ i)
    {
        threads[i] = thread(finish_task, i);
    }
    for(auto& it : threads) {
        it.join();
    }
    system("pause");
    return 0;
}

這裡寫圖片描述
其他不同建構函式使用方法簡介
1)try-locking :unique_lock(mutex_type& m, try_to_lock_t tag)
新建立的 unique_lock 物件管理 mutex 物件 m,並嘗試呼叫 m.try_lock() 對 mutex 物件進行上鎖,但如果上鎖不成功,並不會阻塞當前執行緒。
2)deferred :unique_lock(mutex_type& m, defer_lock_t tag) noexcept
新建立的 unique_lock 物件管理 mutex 物件 m,但是在初始化的時候並不鎖住 mutex 物件。m 應該是一個沒有當前執行緒鎖住的 mutex 物件。
3)adopting :unique_lock(mutex_type& m, adopt_lock_t tag)
新建立的 unique_lock 物件管理 mutex 物件 m, m 應該是一個已經被當前執行緒鎖住的 mutex 物件。(並且當前新建立的 unique_lock 物件擁有對鎖(Lock)的所有權)。
4)locking for:template

#include <iostream>      
#include <thread>      
#include <chrono>    
#include <mutex>    
using namespace std;

int value;
once_flag value_flag;

void setValue (int x) {
    value = x; 
}

void my_task (int id) {

    this_thread::sleep_for(chrono::milliseconds(1000));

    //使setValue函式只被第一次執行的執行緒執行
    call_once (value_flag, setValue, id);
}

int main ()
{
    thread threads[10];

    for (int i=0; i<10; ++i) {
        threads[i] = thread(my_task,i+1);
    }

    for (auto& it : threads){
        it.join();
    }
    cout << "Finished!! the result of value is : " << value << endl;
    system("pause");
    return 0;
}

這裡寫圖片描述

5. std::promise

promise 物件可以儲存某一型別 T 的值,該值可被 future 物件讀取(可能在另外一個執行緒中),因此 promise 也提供了一種執行緒同步的手段。在 promise 物件構造時可以和一個共享狀態(通常是std::future)相關聯,並可以在相關聯的共享狀態(std::future)上儲存一個型別為 T 的值。
可以通過 get_future 來獲取與該 promise 物件相關聯的 future 物件,呼叫該函式之後,兩個物件共享相同的共享狀態(shared state)
1)promise 物件是非同步 Provider,它可以在某一時刻設定共享狀態的值。
2)future 物件可以非同步返回共享狀態的值,或者在必要的情況下阻塞呼叫者並等待共享狀態標誌變為 ready,然後才能獲取共享狀態的值。
入門示例

#include <iostream>    
#include <functional>   
#include <thread>      
#include <future>   
using namespace std;

//通過std::future獲取共享狀態的值
void printShareState(future<int>& state) {
    // 獲取共享狀態的值.
    int x = state.get(); 
    cout << "share state value : " << x << endl;
}

int main ()
{
    // 建立一個 promise<int> 物件,狀態值為int型別
    promise<int> prom; 
    // 和 future 關聯
    future<int> fut = prom.get_future(); 
    // 將 future 交給另外一個執行緒t.
    thread t(printShareState, ref(fut)); 
    // 設定共享狀態的值, 此處和執行緒t保持同步.
    prom.set_value(10); 
    t.join();
    system("pause");
    return 0;
}

這裡寫圖片描述

(1)建構函式
1)預設建構函式promise(),初始化一個空的共享狀態。
2)帶自定義記憶體分配器的建構函式template promise (allocator_arg_t aa, const Alloc& alloc),與預設建構函式類似,但是使用自定義分配器來分配共享狀態。
3)拷貝建構函式promise (const promise&) = delete,被禁用。
4)移動建構函式promise (promise&& x) noexcept。
另外,std::promise 的 operator= 沒有拷貝語義,即 std::promise 普通的賦值操作被禁用,operator= 只有 move 語義,所以 std::promise 物件是禁止拷貝的

#include <iostream>  
#include <thread>    
#include <future> 
using namespace std;

//使用預設建構函式構造一個空共享狀態的promise物件
promise<int> prom;

void printShareStateValue () {
    future<int> fut = prom.get_future();
    int x = fut.get();
    cout << "share state value is " << x << endl;
}

int main ()
{
    thread t1(printShareStateValue);
    prom.set_value(10);
    t1.join();
    //promise<int>()建立一個匿名空的promise物件,使用移動拷貝建構函式給prom
    prom = promise<int>();   

    thread t2 (printShareStateValue);
    prom.set_value (20);
    t2.join();
    system("pause");
    return 0;
}

這裡寫圖片描述

(2)其他成員函式
1)std::promise::get_future()
該函式返回一個與 promise 共享狀態相關聯的 future 。返回的 future 物件可以訪問由 promise 物件設定在共享狀態上的值或者某個異常物件。只能從 promise 共享狀態獲取一個 future 物件。在呼叫該函式之後,promise 物件通常會在某個時間點準備好(設定一個值或者一個異常物件),如果不設定值或者異常,promise 物件在析構時會自動地設定一個 future_error 異常(broken_promise)來設定其自身的準備狀態
2)std::promise::set_value()
void set_value (const T& val);
void set_value (T&& val);
void promise

#include <iostream>      
#include <functional>  
#include <thread>       
#include <future>        
#include <exception>     
using namespace std;

void getAnInteger(promise<int>& prom) {
    int x;
    cout << "input an integer : ";
    //設定如試圖從不能解析為整數的字串裡想要讀一個整數等,順便說下eof也會造成failbit被置位
    //則產生異常
    cin.exceptions (ios::failbit);  
    try {
        cin >> x;
        prom.set_value(x);
    } catch (exception&) {
        prom.set_exception(current_exception());
    }
}

void printAnInteger(future<int>& fut) {
    try {
        int x = fut.get();
        cout << "the value of Integer is " << x << endl;
    } catch (exception& e) {
        cout << "exception content:{ " << e.what() << " }" << endl;;
    }
}

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

    thread t1(getAnInteger, ref(prom));
    thread t2(printAnInteger, ref(fut));

    t1.join();
    t2.join();
    system("pause");
    return 0;
}

這裡寫圖片描述

6. std::packaged_task

std::packaged_task 包裝一個可呼叫的物件,並且允許非同步獲取該可呼叫物件產生的結果,從包裝可呼叫物件意義上來講,std::packaged_task 與 std::function 類似,只不過 std::packaged_task 將其包裝的可呼叫物件的執行結果傳遞給一個 std::future 物件(該物件通常在另外一個執行緒中獲取 std::packaged_task 任務的執行結果)。
std::packaged_task 物件內部包含了兩個最基本元素,一、被包裝的任務(stored task),任務(task)是一個可呼叫的物件,如函式指標、成員函式指標或者函式物件,二、共享狀態(shared state),用於儲存任務的返回值,可以通過 std::future 物件來達到非同步訪問共享狀態的效果。
可以通過 std::packged_task::get_future 來獲取與共享狀態相關聯的 std::future 物件。在呼叫該函式之後,兩個物件共享相同的共享狀態,具體解釋如下:
std::packaged_task 物件是非同步 Provider,它在某一時刻通過呼叫被包裝的任務來設定共享狀態的值。
std::future 物件是一個非同步返回物件,通過它可以獲得共享狀態的值,當然在必要的時候需要等待共享狀態標誌變為 ready.
std::packaged_task 的共享狀態的生命週期一直持續到最後一個與之相關聯的物件被釋放或者銷燬為止

#include <iostream> 
#include <future>    
#include <chrono>
#include <thread> 
using namespace std;

//
int my_task (int from, int to) {
    for (int i = from; i != to; --i) {
        cout << i << endl;
        this_thread::sleep_for(chrono::seconds(1));
    }
    cout << "Finished!!" << endl;
    return from - to;
}

int main ()
{
    // 設定packaged_task,形式上類似於std::function
    // 如std::function<void(EventKeyboard::KeyCode, Event*)> onKeyPressed;
    packaged_task<int(int,int)> task(my_task); 
    // 獲得與 packaged_task 共享狀態相關聯的 future 物件.
    future<int> ret = task.get_future(); 

    thread th(move(task), 10, 0);   //建立一個新執行緒完成計數任務.

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

    cout << "the result of the future state value is " << value << endl;

    th.join();
    system("pause");
    return 0;
}

這裡寫圖片描述

(1)std::packaged_task 建構函式
1)預設建構函式packaged_task() noexcept,初始化一個空的共享狀態,並且該 packaged_task 物件無包裝任務。
2)template explicit packaged_task (Fn&& fn),初始化一個共享狀態,並且被包裝任務由引數 fn 指定。
3)帶自定義記憶體分配器的建構函式template

#include <iostream>     
#include <utility>      
#include <future>       
#include <thread>       
using namespace std;

int main ()
{
    // 預設建構函式.
    packaged_task<int(int)> foo; 

    // 使用 lambda 表示式初始化一個 packaged_task 物件.
    packaged_task<int(int)> bar([](int x){return x * 3;});

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

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

    thread(move(foo), 30).detach(); // 產生執行緒,呼叫被包裝的任務.

    int value = ret.get(); // 等待任務完成並獲取結果.
    cout << "The final result is " << value << ".\n";
    system("pause");

    return 0;
}

這裡寫圖片描述

(2)std::packaged_task::valid() 檢查當前 packaged_task 是否和一個有效的共享狀態相關聯,對於由預設建構函式生成的 packaged_task 物件,該函式返回 false,除非中間進行了 move 賦值操作或者 swap 操作

#include <iostream>     
#include <utility>      
#include <future>     
#include <thread>   
using namespace std;

// 在新執行緒中啟動一個 int(int) packaged_task.
future<int> launcher(packaged_task<int(int)>& tsk, int arg)
{
    if (tsk.valid()) {
        future<int> ret = tsk.get_future();
        //建立匿名執行緒,執行緒執行結束後直接退出不需要和主執行緒會合
        thread (move(tsk),arg).detach();
        return ret;
    }
    else return future<int>();
}

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

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

    if(fut.get() == 50) {
        cout << "task is valid refrence to a shared state" << endl;
    } else {
        cout << "do nothing" << endl;
    }
    system("pause");
    return 0;
}

這裡寫圖片描述

(3)std::packaged_task::get_future()
返回一個與 packaged_task 物件共享狀態相關的 future 物件。返回的 future 物件可以獲得由另外一個執行緒在該 packaged_task 物件的共享狀態上設定的某個值或者異常

(4)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() 只允許單個引數

(5)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 )

(6)std::packaged_task::swap()
交換 packaged_task 的共享狀態

(7)std::packaged_task::reset()
重置 packaged_task 的共享狀態,但是保留之前的被包裝的任務

#include <iostream>    
#include <utility> 
#include <future>   
#include <thread>   
using namespace std;

int tripleX (int x) 
{ 
    return x*3; 
}

int main ()
{
    packaged_task<int(int)> tsk (tripleX);


    future<int> fut = tsk.get_future();
    //thread t1(move(tsk), 100);
    tsk(100);
    cout << "The triple of 100 is " << fut.get() << endl;
    //t1.join();
    //重置tsk的共享狀態
    //this_thread::sleep_for(chrono::milliseconds(500));
    tsk.reset();
    fut = tsk.get_future();
    thread(move(tsk), 200).detach();
    cout << "Thre triple of 200 is " << fut.get() << endl;

    system("pause");
    return 0;
}

這裡寫圖片描述

附:std::function使用例項
直接附程式碼

#include <functional>
#include <iostream>
using namespace std;

std::function< int(int)> Functional;

// 普通函式
int TestFunc(int a)
{
    return a;
}

// Lambda表示式
auto lambda = [](int a)->int{ return a; };

// 仿函式(functor)
class Functor
{
public:
    int operator()(int a)
    {
        return a;
    }
};

// 1.類成員函式
// 2.類靜態函式
class TestClass
{
public:
    int ClassMember(int a) { return a; }
    static int StaticMember(int a) { return a; }
};

int main()
{
    // 普通函式
    Functional = TestFunc;
    int result = Functional(10);
    cout << "普通函式:"<< result << endl;

    // Lambda表示式
    Functional = lambda;
    result = Functional(20);
    cout << "Lambda表示式:"<< result << endl;

    // 仿函式
    Functor testFunctor;
    Functional = testFunctor;
    result = Functional(30);
    cout << "仿函式:"<< result << endl;

    // 類成員函式
    TestClass testObj;
    Functional = std::bind(&TestClass::ClassMember, testObj, std::placeholders::_1);
    result = Functional(40);
    cout << "類成員函式:"<< result << endl;

    // 類靜態函式
    Functional = TestClass::StaticMember;
    result = Functional(50);
    cout << "類靜態函式:"<< result << endl;
    system("pause");
    return 0;
}

附:C++11 lambda表示式使用例項
盜個圖表示一下lambda表示式的語法
這裡寫圖片描述
1 表示Lambda表示式的引入標誌,在‘[]’裡面可以填入‘=’或‘&’表示該lambda表示式“捕獲”(lambda表示式在一定的scope可以訪問的資料)的資料時以什麼方式捕獲的,‘&’表示一引用的方式;‘=’表明以值傳遞的方式捕獲,除非專門指出。
2 表示Lambda表示式的引數列表
3 表示Mutable 標識
4 表示異常標識
5 表示返回值
6 表示“函式”體,也就是lambda表示式需要進行的實際操作
例項可參考上例中的:
auto lambda = [](int a)->int{ return a; };

7. 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 可以是函式或者類,其實我們前面都已經提到了,他們分別是:
1)std::async() 函式,本文後面會介紹 std::async() 函式。
2)std::promise::get_future(),get_future 為 promise 類的成員函式
3)std::packaged_task::get_future(),此時 get_future為 packaged_task 的成員函式。
一個 std::future 物件只有在有效(valid)的情況下才有用(useful),由 std::future 預設建構函式建立的 future 物件不是有效的(除非當前非有效的 future 物件被 move 賦值另一個有效的 future 物件)。
在一個有效的 future 物件上呼叫 get 會阻塞當前的呼叫者,直到 Provider 設定了共享狀態的值或異常(此時共享狀態的標誌變為 ready),std::future::get 將返回非同步任務的值或異常(如果發生了異常)。

#include <iostream>      
#include <future>          
#include <chrono>  
using namespace std;

bool is_prime(int x)
{
    for (int i = 2; i < x; ++i) {
        if (x % i == 0) {
            return false;
        }
    }
    return true;
}

int main()
{
    // 非同步呼叫判斷999999是不是質數
    future < bool > fut = async(is_prime, 9999999999);

    //由於上面程式碼中判斷素數的方法對於比較大的數,將耗費較多時間
    //開始判斷共享狀態是否變為了ready,等待
    cout << "waiting for ready..." << endl;
    chrono::milliseconds span(100);
    //每100ms內阻塞等待,如果超時輸出*,並繼續等待,否則結束while
    while (fut.wait_for(span) == future_status::timeout)
        cout << '*';

    bool ret = fut.get();     
    cout << endl;
    cout << "9999999999 " << (ret ? "is" : "is not") << " a prime." << endl;
    system("pause");
    return 0;
}

這裡寫圖片描述

(1)建構函式
1)預設建構函式future() noexcept
2)複製建構函式future (const future&) = delete,不可用
3)future (future&& x) noexcept,move建構函式
(2)賦值操作 普通賦值=已被過載為move操作
(3)std::future::share()
返回一個 std::shared_future 物件,呼叫該函式之後,該 std::future 物件本身已經不和任何共享狀態相關聯,因此該 std::future 的狀態不再是 valid 的了。

#include <iostream>    
#include <future>    
using namespace std;

int do_get_value() { 
    return 10; 
}

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

    // 共享的 future 物件可以被多次訪問, fut的狀態到這裡便不再是valid的了
    cout << "value: " << shared_fut.get() << '\n';
    cout << "its double: " << shared_fut.get()*2 << '\n';
    system("pause");
    return 0;
}

這裡寫圖片描述

(4)std::future::get()
1)T get();
2)R& future

#include <iostream>      
#include <functional>  
#include <thread>       
#include <future>        
#include <exception>     
using namespace std;

void getAnInteger(promise<int>& prom) {
    int x;
    cout << "input an integer : ";
    //設定如試圖從不能解析為整數的字串裡想要讀一個整數等,順便說下eof也會造成failbit被置位
    //則產生異常
    cin.exceptions (ios::failbit);  
    try {
        cin >> x;
        prom.set_value(x);
    } catch (exception&) {
        prom.set_exception(current_exception());
    }
}

void printAnInteger(future<int>& fut) {
    try {
        int x = fut.get();
        cout << "the value of Integer is " << x << endl;
    } catch (exception& e) {
        cout << "exception content:{ " << e.what() << " }" << endl;;
    }
}

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

    thread t1(getAnInteger, ref(prom));
    thread t2(printAnInteger, ref(fut));

    t1.join();
    t2.join();
    system("pause");
    return 0;
}

(5)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>    
#include <future>    
#include <utility>   
using namespace 
            
           

相關推薦

C++併發程式設計C++11

前言 首先需要說明,本部落格的主要內容參考自Forhappy && Haippy博主的分享,本人主要是參照博主的資料進行了學習和總結,並適當的衍生或補充了相關的其他知識內容。 C++11有了std::thread 以後,可以在語言層面編寫多執

Linux C語言程式設計上篇 | gcc的使用

嵌入式軟體開發主要使用C語言開發,編譯過程稱為交叉編譯 —— 在PC機上編譯出可以在嵌入式處理器上執行的程式,在真正進入嵌入式開發前,先來了解下如何使用gcc+make編譯C語言工程,如何用gdb除錯工程~ 1.C程式設計流程 1.1.編輯原始檔(.c) 使用文字編輯器(比如vi

C語言程式設計第二版第6章程式設計

1:找出與平均值相差最小的元素 #include"stdio.h" #include"math.h" #define N 10 void main() { int i; double a[N],v=0,min; printf("Please input

彙編與C混合程式設計6.19

一  異常放回 <1>IRQ/FIRQ pc <- lr - 4  <2>軟中斷異常pc <- lr  <3>預取指令終止異常 指令1指令2 <-沒有取到指令指令3指令4 <-pc lr : 儲存的是指令3的地址 

Java併發程式設計十二CountDownLatch和CyclicBarrier

一、CountDownLatch java.util.concurrent.CountDownLatch可以允許一個或多個執行緒等待其他執行緒操作。從countdown字面意義也可以理解,它是類似於一個倒計時鎖,這個倒計時是原子操作,同一時刻只能有一個執行緒操作倒計時。 CountDownL

Java併發程式設計十一Java中的原子操作類

一、原子操作類簡介 JDK1.5開始提供了java.util.concurrent.atomic包,其中有一系列用法簡單、效能高效、可以執行緒安全更新變數的原子操作類,目前(JDK1.7)大概有這麼些: 二、原子操作類實現原理 以AtomicInteger為例看下原始碼,其中的兩個

java併發程式設計十九----(JUC集合)總體框架介紹

本節我們將繼續學習JUC包中的集合類,我們知道jdk中本身自帶了一套非執行緒安全的集合類,我們先溫習一下java集合包裡面的集合類,然後系統的看一下JUC包裡面的集合類到底有什麼不同。 java集合類 java集合類裡面主要包含兩大類:一類是Collec

java併發程式設計十二--可重入內建鎖

    每個Java物件都可以用做一個實現同步的鎖,這些鎖被稱為內建鎖或監視器鎖。執行緒在進入同步程式碼塊之前會自動獲取鎖,並且在退出同步程式碼塊時會自動釋放鎖。獲得內建鎖的唯一途徑就是進入由這個鎖保護的同步程式碼塊或方法。     當某個執行緒請求一個由其他執行緒持有

Java高併發程式設計十二:Executor框架

Java中的執行緒既是工作單元,也是執行單元。工作單元包括Runnable和Callable,而執行單元是由Executor框架支援。 1. Executor框架簡介 1.1 Executor框架的兩級排程模型 在HotSpot VM的執行緒模型中,Java執行緒(java.la

Java高併發程式設計十一:Java中執行緒池

在開發過程中,合理地使用執行緒池能夠帶來3個好處。 降低資源消耗。通過重複利用已建立的執行緒降低執行緒建立和銷燬造成的消耗。 提高響應速度。當任務到達時,任務可以不需要等到執行緒建立就能立即執行。 提高執行緒的可管理性。執行緒是稀缺資源,如果無限制地建立

併發程式設計十一—— Java 執行緒池 實現原理與原始碼深度解析

史上最清晰的執行緒池原始碼分析 鼎鼎大名的執行緒池。不需要多說!!!!! 這篇部落格深入分析 Java 中執行緒池的實現。 總覽 下圖是 java 執行緒池幾個相關類的繼承結構:    先簡單說說這個繼承結構,Executor 位於最頂層,也是最簡單的,就一個 execute(

併發程式設計十二—— Java 執行緒池 實現原理與原始碼深度解析 之submit方法

在上一篇《併發程式設計(十一)—— Java 執行緒池 實現原理與原始碼深度解析(一)》中提到了執行緒池ThreadPoolExecutor的原理以及它的execute方法。這篇文章是接著上一篇文章寫的,如果你沒有閱讀上一篇文章,建議你去讀讀。本文解析ThreadPoolExecutor#submit。  

java併發程式設計十五之執行緒池

待續...package com.dason.juc2; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.co

Python併發程式設計十一:程序池,執行緒池,協程

目錄 注意 二、執行緒池 協程的本質 注意 1、不能無限的開程序和執行緒,最常用的就是開程序池,開執行緒池。 2、其中回撥函式非常重要,回撥函式其實可以作為一種程式設計思想,函數語言程式設

併發程式設計: 使用C++11實現無鎖stacklock-free stack)

C++11中CAS實現: template< class T> struct atomic { public: bool compare_exchange_weak( T& expected, T desired,                    

c++併發程式設計實戰C++11pdf 高清

C++併發程式設計實戰PDF高清完整版下載。C++併發程式設計實戰PDF是一本非常熱門的電子圖書。這本書籍是由由威廉姆斯所著的,裡面擁有非常詳細的講解,對於新手來說是本不錯的書。 下載地址:http://download.csdn.net/download/l

C++11併發程式設計——初始C++11多執行緒庫

1 前言   C++11標準在標準庫中為多執行緒提供了元件,這意味著使用C++編寫與平臺無關的多執行緒程式成為可能,而C++程式的可移植性也得到了有力的保證。   在之前我們主要使用的多執行緒庫要麼

Java併發程式設計6- J.U.C元件拓展

J.U.C-FutureTask 在Java中一般通過繼承Thread類或者實現Runnable介面這兩種方式來建立執行緒,但是這兩種方式都有個缺陷,就是不能在執行完成後獲取執行的結果,因此Java 1.5之後提供了Callable和Future介面,通過它們就可以在任務執行完畢之後得到任務的執行結果。

浙大版《C語言程式設計第3版》題目集 - 習題11-5 指定位置輸出字串20 分

題目連結:點選開啟連結   題目大意:略。   解題思路:略。   AC 程式碼 char *match( char *s, char ch1, char ch2 ) { char *p=s, *h; int fst=1,

浙大版《C語言程式設計第3版》題目集 - 習題11-7 奇數值結點連結串列20 分

題目連結:點選開啟連結   題目大意:略。   解題思路:題目說刪除了,其實可以轉化為再搞一個獲取奇數時的做法來做偶數的情況,最後把地址賦值給L,這樣思路就簡單許多~。還有這裡帶兩個星號的L,其實多了一個星號是因為傳參時,傳進去的是指標變數的地址(此地址非內容