原文鏈接:Learn C++ Multi-Threading in 5 Minutes C++14的新的多線程架構非常簡單易學,如果你對C或者C++很熟悉,那麼本文非常適合你。作者用C++14作為基準參考,但是所介紹的東西在C++17中也依然適用。本文只介紹基本的架構,在讀完本文後你應該也可以自己編 ...
原文鏈接:Learn C++ Multi-Threading in 5 Minutes
C++14的新的多線程架構非常簡單易學,如果你對C或者C++很熟悉,那麼本文非常適合你。作者用C++14作為基準參考,但是所介紹的東西在C++17中也依然適用。本文只介紹基本的架構,在讀完本文後你應該也可以自己編寫自己的多線程程式。
創建線程
創建線程有以下幾種方式:
1.使用函數指針
2.使用仿函數
3.使用lambda表達式
這些方式都比較類似,只有部分差別,我將在下麵具體講述每一種方式和他們的區別。
使用函數指針
來看看下麵這個函數,其參數包括一個vector的引用 v ,一個輸出結果的引用acm,還有兩個v的索引。這個函數會將v的beginIndex和endIndex之間的元素累加起來。
1 void accumulator_function2(const std::vector<int> &v, unsigned long long &acm, 2 unsigned int beginIndex, unsigned int endIndex) 3 { 4 acm = 0; 5 for (unsigned int i = beginIndex; i < endIndex; ++i) 6 { 7 acm += v[i]; 8 } 9 }
現在如果我們想將vector分為兩個部分,併在單獨的線程t1和t2中分別計算各部分的總和的話,我們可以這麼寫:
1 //Pointer to function 2 { 3 unsigned long long acm1 = 0; 4 unsigned long long acm2 = 0; 5 std::thread t1(accumulator_function2, std::ref(v), 6 std::ref(acm1), 0, v.size() / 2); 7 std::thread t2(accumulator_function2, std::ref(v), 8 std::ref(acm2), v.size() / 2, v.size()); 9 t1.join(); 10 t2.join(); 11 12 std::cout << "acm1: " << acm1 << endl; 13 std::cout << "acm2: " << acm2 << endl; 14 std::cout << "acm1 + acm2: " << acm1 + acm2 << endl; 15 }
上面這段你需要知道:
1.std::thread這個調用創建了一個新的線程。其第一個參數是函數指針 accumulator_function2 ,因此每個線程都會去執行這個函數。
2.剩下的我們傳給std::thread的構造函數的參數,都是我們需要去傳給accumulator_function2的參數。
3.重點:傳遞給accumulator_function2的參數預設情況下都是值傳遞的,除非你用std::ref把他包起來。所以我們這裡使用了std::ref來包住v、acm1、acm2。
4.使用std::thread創建的線程是沒有返回值的,所以如果你想從線程中返回些什麼,請使用引用將你想返回的值作為一個傳入參數。這裡的例子就是acm1和acm2。
5.每個線程一旦創建就立即執行了。
6.我們使用join()函數來等待線程執行完畢。
使用偽函數
你也可以使用偽函數來做同樣的事情,下麵是例子:
1 class CAccumulatorFunctor3 2 { 3 public: 4 void operator()(const std::vector<int> &v, 5 unsigned int beginIndex, unsigned int endIndex) 6 { 7 _acm = 0; 8 for (unsigned int i = beginIndex; i < endIndex; ++i) 9 { 10 _acm += v[i]; 11 } 12 } 13 unsigned long long _acm; 14 };
那麼創建線程的方式變成下麵這樣:
1 //Creating Thread using Functor 2 { 3 4 CAccumulatorFunctor3 accumulator1 = CAccumulatorFunctor3(); 5 CAccumulatorFunctor3 accumulator2 = CAccumulatorFunctor3(); 6 std::thread t1(std::ref(accumulator1), 7 std::ref(v), 0, v.size() / 2); 8 std::thread t2(std::ref(accumulator2), 9 std::ref(v), v.size() / 2, v.size()); 10 t1.join(); 11 t2.join(); 12 13 std::cout << "acm1: " << accumulator1._acm << endl; 14 std::cout << "acm2: " << accumulator2._acm << endl; 15 std::cout << "accumulator1._acm + accumulator2._acm : " << 16 accumulator1._acm + accumulator2._acm << endl; 17 }
上面這段你需要知道:
偽函數的使用方式大部分地方都和函數指針很像,除了:
1.第一個參數變成了偽函數對象。
2.我們不再需要使用引用來獲取返回值了,我們可以將返回值作為偽函數對象的一個成員變數來儲存。這裡的例子就是_acm。
使用lambda表達式
作為第三種選擇,我們可以在每個線程的構造函數中使用lambda表達式來定義我們想做的事,如下:
1 { 2 unsigned long long acm1 = 0; 3 unsigned long long acm2 = 0; 4 std::thread t1([&acm1, &v] { 5 for (unsigned int i = 0; i < v.size() / 2; ++i) 6 { 7 acm1 += v[i]; 8 } 9 }); 10 std::thread t2([&acm2, &v] { 11 for (unsigned int i = v.size() / 2; i < v.size(); ++i) 12 { 13 acm2 += v[i]; 14 } 15 }); 16 t1.join(); 17 t2.join(); 18 19 std::cout << "acm1: " << acm1 << endl; 20 std::cout << "acm2: " << acm2 << endl; 21 std::cout << "acm1 + acm2: " << acm1 + acm2 << endl; 22 }
同樣,大多數地方都和函數指針的方式很類似,除了:
1.作為傳參的替代方式,我們可以使用lambda表達式的捕獲(capture)方式來處理參數傳遞
Tasks, Futures, and Promises
除了std::thread,我們還可以使用 tasks.
tasks和std::thread工作的方式非常相似,只有一個最主要的不同:tasks可以返回一個值。因此,你可以暫存這個返回值來作為這個線程的更抽象的定義方式,併在你真的需要返回的結果的時候來從這個返回值中拿到數據。
下麵就是使用Tasks的例子:
1 #include <future> 2 //Tasks, Future, and Promises 3 { 4 auto f1 = [](std::vector<int> &v, 5 unsigned int left, unsigned int right) { 6 unsigned long long acm = 0; 7 for (unsigned int i = left; i < right; ++i) 8 { 9 acm += v[i]; 10 } 11 12 return acm; 13 }; 14 15 auto t1 = std::async(f1, std::ref(v), 16 0, v.size() / 2); 17 auto t2 = std::async(f1, std::ref(v), 18 v.size() / 2, v.size()); 19 20 //You can do other things here! 21 unsigned long long acm1 = t1.get(); 22 unsigned long long acm2 = t2.get(); 23 24 std::cout << "acm1: " << acm1 << endl; 25 std::cout << "acm2: " << acm2 << endl; 26 std::cout << "acm1 + acm2: " << acm1 + acm2 << endl; 27 }
上面這段你需要知道:
1.tasks使用std::async創建
2.std::async的返回值是一個叫std::future的類型。別被他的名字唬到,他的意思是t1和t2的值會在未來被真正的賦值。我們通過調用t1.get()來獲得他的真正的返回值。
3.如果future的返回值還沒有準備好(任務還沒有計算完成),那麼調用get()的主線程會被卡住,直到準備好了返回值(和join()的行為一樣)。
4.註意,我們傳遞給std::async的函數(實際上是lambda表達式)是有返回值的,這個返回值用過一個叫做std::promise的類型來傳遞。大多數情況下你不需要瞭解任何promise的細節,C++在幕後可以處理好這些事情。
5.預設的情況下,tasks也會在創建之後立刻運行(有辦法來修改這個行為,但是本文沒有涉及)。
線程創建的總結:
創建線程很簡單,你可以通過函數指針、偽函數、lambda表達式的方式來創建std::thread,也可以使用std::async的方式來獲得一個std::future類型的返回值。std::async也同樣可以使用函數指針、偽函數、lambda表達式來創建
(未完待續)