C++11並發編程

來源:互聯網
上載者:User

標籤:c++並發編程

     C++11開始支援多線程編程,之前多線程編程都需要系統的支援,在不同的系統下建立線程需要不同的API如pthread_create(),Createthread(),beginthread()等。現在C++11中引入了一個新的線程庫,C++11提供了新標頭檔,主要包含 <thread>、<mutex>、<atomic>、<condition_varible>、<future>五個部分;<thread>等用於支援多線程,同時包含了用於啟動、管理線程的諸多工具,同時,該庫還提供了包括像互斥量、鎖、原子量等在內的同步機制。在這個系列的文章中,我將嘗試和大家分享這個新庫提供的部分特性

為了讓你的代碼支援多線程庫,你環境上的編譯器必須支援 C++11,不過幸運的是現在絕大部分的編譯器都支援C++11了,除非你手頭上的編譯器版本是古董層級了,真這樣的話,還是建議你趕快升級,不光是為了這次的學習,後面你肯定會用上的。

多線程編程的好處

在看本文之前,如果對什麼是線程、什麼是進程仍存有疑惑,請先Google之,這兩個概念不在本文的範圍之內。用多線程只有一個目的,那就是更好的利用cpu的資源,下面兩個圖很好的反應了這點。

以單cpu為例,假如有個進程,進程功能是從網路上接受資料,將接收到的資料進行處理。我們知道,網路上的資料是有時延的,甚至在你接收資料的這段時間內由於網路等問題接收不到資料,如果是單線程的進程,那麼此時你的進程什麼也做不了,苦苦的在等待對端的資料。此時cpu被浪費了(假設系統就這一個進程,不考慮將進程切換到其它進程的)。

如果進程含有兩個線程,一個線程專門接收資料,暫且叫做A線程吧;另外一個線程專門負責處理接收的資料,就叫B線程吧。那麼情況就不一樣了,當由於網路等問題接收不到資料時,阻塞的只是A線程,完全可以通過某種調度機制將cpu調度給B線程讓其工作。這樣,cpu得到了最大的利用而不至於浪費在那兒。

好了,既然線程的用處這麼大,C++11又加了支援多線程的庫,那麼讓我們一窺其廬山真面目吧。

如何啟動一個線程

在C++11中,啟動一個新的線程非常簡單,當你建立一個 std::thread 的執行個體時,它便會自行啟動。同時在建立線程執行個體時,須提供給該線程一段將要執行的函數,方法之一是在建立線程執行個體傳遞一個函數指標。

好吧,不管學什麼程式設計語言,Hello world!感覺總是不會少的,這彷彿已經成為了一個標準。這次我們仍以經典的 "Hello world” 為例來向大家如何建立一個線程:  

#include <thread>#include <iostream>void hello(){    std::cout << "Hello world from thread !"               << std::endl;}int main(){    std::thread t(hello);    t.join();    return 0;}
[[email protected] c++]# g++ -std=c++11 -o a a.cpp[[email protected] c++]# ./aterminate called after throwing an instance of 'std::system_error'  what():  Enable multithreading to use std::thread: Operation not permitted已放棄

雲行時報錯了,編譯的時候加上-lpthread

[[email protected] c++]# g++ -std=c++11 -lpthread -o a a.cpp[[email protected] c++]# ./aHello world from thread !

看到了吧,首先,我們引入了標頭檔<thread>。在這個標頭檔中,C++11 提供了管理線程的類和函數。之後,我們定義了一個無傳回型別的函數hello,這個函數除了在標準輸出資料流中列印一行文字之外什麼也不做。而後,我們定義了一個 std::thread 類型的變數 t,在定義的時候用hello函數名(其實就是一個函數指標)作為 std::thread 類建構函式的參數 。

這個例子中值得注意的是函數 join()。調用join()將導致當前線程等待被 join 的線程結束(在本例中即線程 main 必須等待線程 t結束後方可繼續執行)。如果你不調用 join() ,其結果是未定義的 —— 程式可能如你所願列印出 "Hello world  from thread" 以及一個換行,但是也可能只列印出 "Hello world  from thread" 卻沒有換行,甚至什麼都不做,那是因為線程 main 可能線上程 t結束之前就返回了。 

其次還需注意的就是在編譯的時候需加上參數-std=c++11,否則編譯不過。

使用Lambda運算式啟動線程

如果線程所要執行的代碼非常短小時,你完全沒必要專門為之建立一個函數,取而代之的是使用 Lambda 運算式(關於Lambda 運算式,有興趣的朋友可以上網查查它的用法,它也是C++11新加入的特性)。我們可以很輕易地將上述例子改寫為使用 Lambda 運算式的形式: 


#include <thread>#include <iostream>int main(){    std::thread t([](){       std::cout << "Hello world from thread                !"<< std::endl;});       t.join();    return 0;}

[[email protected] c++]# ./b

Hello world from thread                !

如上,我們使用了 Lambda 運算式替換掉原來的函數指標。不需要任何懷疑,這段代碼和之前使用函數指標的代碼實現了完全相同的功能。 

給你的線程執行函數加上參數

是不是覺得開啟一個線程就是為了列印一句話有點小題大做是吧,好的,我們現在是該做點什麼了。在上個線程函數的基礎上,除了列印一句話,我們再加個功能:接受兩個參數,計算它們的和並列印出。

#include <thread>#include <iostream>//線程執行函數接受兩個int型別參數void hello(int x,int y){   std::cout<<"Hello world from thread !" <<std::endl;   std::cout<<"x+y=" <<x+y <<std::endl;}int main(){  std::thread t (hello,10,20);  t.join();  return 0;}
[[email protected] c++]# ./c
Hello world from thread !
x+y=30

程式的運行結果正是我們想要的,沒錯,向線程函數傳參就是這麼簡單:在建立線程執行個體的時候和函數指標一起傳遞過去

區分線程

我們可以回一下我們是如何區分進程的?每個進程都有一個編號,稱為pid(進程id),我們就是通過它來區分不同的進程,不光是我們人,其實作業系統也是通過這個pid來區分管理不同的進程。 

線程也一樣, 每個線程都有唯一的 ID 以便我們加以區分,我們稱之為tid(線程id)。使用 std::this_thread 類的 get_id() 便可擷取對應線程的tid。下面的例子將建立一些線程並使用 std::this_thread::get_id() 來擷取當前線程的tid,並列印出:

#include <thread>#include <iostream>#include <vector>void hello(){       std::cout << "Hello world from thread: "               << std::this_thread::get_id()               << std::endl;}int main(){    std::vector<std::thread> threads;    for(int i = 0; i < 6; ++i)    {         threads.push_back(std::thread(hello));    }    for(auto& thread : threads)    {        thread.join();    }   return 0;}
[[email protected] c++]# ./threadHello world from thread: Hello world from thread: Hello world from thread: Hello world from thread: 140598791485184140598816663296140598799877888Hello world from thread: 140598783092480Hello world from thread: 140598774699776140598808270592[[email protected] c++]# ./threadHello world from thread: 140198417712896Hello world from thread: 140198409320192Hello world from thread: 140198392534784Hello world from thread: 140198375749376Hello world from thread: 140198384142080Hello world from thread: 140198400927488

看到沒,上述結果我肯定不是你所希望看到的,結果似乎混亂了,如果你在你的電腦上多運行幾遍,甚至還會出現其它各種結果,那麼這種匪夷所思的運行結果究竟是什麼原因導致的?

我們知道線程之間是交叉啟動並執行,在上面這個例子,我們並沒有去控制線程的執行順序,某個線程在運行期間可能隨時被搶佔, 同時可以看到,上面例子的ostream 分幾個步驟(首先輸出一個 string,然後是 ID,最後輸出換行),因此三個線程可能先都只執行了第一個步驟將 Hello world from thread 列印出來,然後每個線程都依次執行完剩餘的兩個步驟(列印ID,然後換行),這就導致了上面的運行結果。

那麼有沒有方法解決上面的問題了,答案是肯定的,接下來我們將看到如何使用鎖機制控制線程的執行順序。

 

#include <thread>#include <iostream>#include <vector>pthread_mutex_t  lock = PTHREAD_MUTEX_INITIALIZER;void hello(){       pthread_mutex_lock(&lock);           std::cout << "Hello world from thread: "               << std::this_thread::get_id()               << std::endl;    pthread_mutex_unlock(&lock);}int main(){    std::vector<std::thread> threads;    for(int i = 0; i < 6; ++i)    {         //pthread_mutex_lock(&lock);        threads.push_back(std::thread(hello));        //pthread_mutex_unlock(&lock);    }    for(auto& thread : threads)    {        thread.join();    }   return 0;}

 

[[email protected] c++]# ./threadHello world from thread: 140515972613888Hello world from thread: 140515939043072Hello world from thread: 140515930650368Hello world from thread: 140515964221184Hello world from thread: 140515955828480Hello world from thread: 140515947435776[[email protected] c++]# ./threadHello world from thread: 140654524454656Hello world from thread: 140654532847360Hello world from thread: 140654516061952Hello world from thread: 140654507669248Hello world from thread: 140654499276544Hello world from thread: 140654490883840

看到沒有,現在再執行thread程式的時候,就不會亂了。

轉載了碼農有道,並把後部分完成了。


C++11並發編程

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.