併發在生活中隨處可見,邊走路邊說話,邊聽歌邊寫代碼。計算機術語中的"併發",指的是在單個系統裏同時執行多個獨立的活動,而不是順序的一個接一個的執行。對於單核CPU來講,在某個時刻只可能處理一個任務,但它卻不是徹底執行完一個任務再執行一個下一任務,而是一直在任務間切換,每一個任務完成一點就去執行下一個任務,看起來就像任務在並行發生,雖然不是嚴格的同時執行多個任務,可是咱們仍然稱之爲併發(concurrency)。真正的併發是在在多核CPU上,可以真正的同時執行多個任務,稱爲硬件併發(hardware concurrency)。ios
併發並不是沒有代價,在單核CPU併發執行兩個任務須要付出上下文切換的時間代價。以下圖:編程
假設A和B兩個任務都被分紅10個大小相等的塊,單核CPU交替的執行兩個任務,每次執行其中一塊,其花費的時間並非先完成A任務再玩成B任務所花費時間的兩倍,而是要更多。這是由於系統從一個任務切換到另外一個任務須要執行一次上下文切換,這是須要時間的(圖中的灰色塊)。上下文切換須要操做系統爲當前運行的任務保存CPU的狀態和指令指針,算出要切換到哪一個任務,併爲要切換的任務從新加載處理器狀態。而後將新任務的指令和數據載入到緩存中。緩存
將應用程序分爲多個獨立的、單線程的進程,他們能夠同時運行。進程內部實現原理比較複雜,這裏就很少說了。安全
這些獨立的進程能夠經過常規的進程間通訊機制進行通訊,如管道、信號、消息隊列、共享內存、存儲映射I/O、信號量、套接字等。多線程
缺點:併發
優勢:分佈式
線程很像輕量級的進程,可是一個進程中的全部線程都共享相同的地址空間,線程間的大部分數據均可以共享。線程間的通訊通常都經過共享內存來實現。函數
優勢:性能
缺點:測試
主要緣由有兩個:任務拆分和提升性能。
在編寫軟件的時候,將相關的代碼放在一塊兒,將無關的代碼分開,這是一個好主意,這樣可以讓程序更加容易理解和測試。將程序劃分紅不一樣的任務,每一個線程執行一個任務或者多個任務,能夠將整個程序的邏輯變得更加簡單。
在兩種狀況下,併發可以提升性能。
C++98
標準中並無線程庫的存在,而在C++11
中終於提供了多線程的標準庫,提供了管理線程、保護共享數據、線程間同步操做、原子操做等類。
多線程庫對應的頭文件是#include <thread>
,類名爲std::thread
。
一個簡單的串行程序以下:
#include <iostream> #include <thread> void function_1() { std::cout << "I'm function_1()" << std::endl; } int main() { function_1(); return 0; }
這是一個典型的單線程的單進程程序,任何程序都是一個進程,main()
函數就是其中的主線程,單個線程都是順序執行。
將上面的程序改形成多線程程序其實很簡單,讓function_1()
函數在另外的線程中執行:
#include <iostream> #include <thread> void function_1() { std::cout << "I'm function_1()" << std::endl; } int main() { std::thread t1(function_1); // do other things t1.join(); return 0; }
分析:
std::thread
對象t1
,構造的時候傳遞了一個參數,這個參數是一個函數,這個函數就是這個線程的入口函數,函數執行完了,整個線程也就執行完了。start
的函數來顯式的啓動線程。std::thread
對象被銷燬以前作出這個決定。這個例子中,對象t1
是棧上變量,在main
函數執行結束後就會被銷燬,因此須要在main
函數結束以前作決定。t1.join()
,主線程會一直阻塞着,直到子線程完成,join()
函數的另外一個任務是回收該線程中使用的資源。線程對象和對象內部管理的線程的生命週期並不同,若是線程執行的快,可能內部的線程已經結束了,可是線程對象還活着,也有可能線程對象已經被析構了,內部的線程還在運行。
假設t1
線程是一個執行的很慢的線程,主線程並不想等待子線程結束就想結束整個任務,直接刪掉t1.join()
是不行的,程序會被終止(析構t1
的時候會調用std::terminate
,程序會打印terminate called without an active exception
)。
與之對應,咱們能夠調用t1.detach()
,從而將t1
線程放在後臺運行,全部權和控制權被轉交給C++
運行時庫,以確保與線程相關聯的資源在線程退出後能被正確的回收。參考UNIX
的守護進程(daemon process)的概念,這種被分離的線程被稱爲守護線程(daemon threads)。線程被分離以後,即便該線程對象被析構了,線程仍是可以在後臺運行,只是因爲對象被析構了,主線程不可以經過對象名與這個線程進行通訊。例如:
#include <iostream> #include <thread> void function_1() { //延時500ms 爲了保證test()運行結束以後纔打印 std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::cout << "I'm function_1()" << std::endl; } void test() { std::thread t1(function_1); t1.detach(); // t1.join(); std::cout << "test() finished" << std::endl; } int main() { test(); //讓主線程晚於子線程結束 std::this_thread::sleep_for(std::chrono::milliseconds(1000)); //延時1s return 0; } // 使用 t1.detach()時 // test() finished // I'm function_1() // 使用 t1.join()時 // I'm function_1() // test() finished
分析:
500ms
的延時,因此在尚未打印的時候,test()
已經執行完成了,t1
已經被析構了,可是它負責的那個線程仍是可以運行,這就是detach()
的做用。main
函數中的1s
延時,會發現什麼都沒有打印,由於主線程執行的太快,整個程序已經結束了,那個後臺線程被C++
運行時庫回收了。t1.detach()
換成t1.join()
,test
函數會在t1
線程執行結束以後,纔會執行結束。一旦一個線程被分離了,就不可以再被join
了。若是非要調用,程序就會崩潰,可使用joinable()
函數判斷一個線程對象可否調用join()
。
void test() { std::thread t1(function_1); t1.detach(); if(t1.joinable()) t1.join(); assert(!t1.joinable()); }