# 線程(thread)
線程(譯注:大約是C++11中最激動人心的特性了)是一種對程序中的執行或者計算的表述。跟許多現代計算一樣,C++11中的線程之間能夠共享地址空間。從這點上來看,它不同于進程:進程一般不會直接跟其它進程共享數據。在過去,C++針對不同的硬件和操作系統有著不同的線程實現版本。如今,C++將線程加入到了標準件庫中:一個標準線程ABI。
許多大部頭書籍以及成千上萬的論文都曾涉及到并發、并行以及線程。在這一條FAQ里幾乎不涉及這些內容。事實上,要做到清楚地思考并發非常難。如果你想編寫并發程序,請至少看一本書。不要依賴于一本手冊、一個標準或者一條FAQ。
在用一個函數或者函數對象(包括lambda)構造std::thread時,一個線程便啟動了。
```
#include <thread>
void f();
struct F {
void operator()();
};
int main()
{
std::thread t1{f}; // f() 在一個單獨的線程中執行
std::thread t2{F()}; // F()() 在一個單獨的線程中執行
}
```
然而,無論f()和F()執行任何功能,都不能給出有用的結果。這是因為程序可能會在t1執行f()之前或之后以及t2執行F()之前或之后終結。我們所期望的是能夠等到兩個任務都完成,這可以通過下述方法來實現:
```
int main()
{
std::thread t1{f}; // f() 在一個單獨的線程中執行
std::thread t2{F()}; // F()()在一個單獨的線程中執行
t1.join(); // 等待t1
t2.join(); // 等待t2
}
```
上面例子中的join()保證了在t1和t2完成后程序才會終結。這里”join”的意思是等待線程返回后再終結。
通常我們需要傳遞一些參數給要執行的任務。例如:
```
void f(vector<double>&);
struct F {
vector<double>& v;
F(vector<double>& vv) :v{vv} { }
void operator()();
};
int main()
{
// f(some_vec) 在一個單獨的線程中執行
std::thread t1{std::bind(f,some_vec)};
// F(some_vec)() 在一個單獨的線程中執行
std::thread t2{F(some_vec)};
t1.join();
t2.join();
}
```
上例中的標準庫函數bind會將一個函數對象作為它的參數。
通常我們需要在執行完一個任務后得到返回的結果。對于那些簡單的對返回值沒有概念的,我建議使用std::future。另一種方法是,我們可以給任務傳遞一個參數,從而這個任務可以把結果存在這個參數中。例如:
```
void f(vector<double>&, double* res); // 將結果存在res中
struct F {
vector<double>& v;
double* res;
F(vector<double>& vv, double* p) :v{vv}, res{p} { }
void operator()(); //將結果存在res中
};
int main()
{
double res1;
double res2;
// f(some_vec,&res1) 在一個單獨的線程中執行
std::thread t1{std::bind(f,some_vec,&res1)};
// F(some_vec,&res2)() 在一個單獨的線程中執行
std::thread t2{F(some_vec,&res2)};
t1.join();
t2.join();
std::cout << res1 << " " << res2 << ‘\n’;
}
```
但是關于錯誤呢?如果一個任務拋出了異常應該怎么辦?如果一個任務拋出一個異常并且它沒有捕獲到這個異常,這個任務將會調用std::terminate()。調用這個函數一般意味著程序的結束。我們常常會為避免這個問題做諸多嘗試。std::future可以將異常傳送給父線程(這正是我喜歡future的原因之一)。否則,返回錯誤代碼。
除非一個線程的任務已經完成了,當一個線程超出所在的域的時候,程序會結束。很明顯,我們應該避免這一點。
沒有辦法來請求(也就是說盡量文雅地請求它盡可能早的退出)一個線程結束或者是強制(也就是說殺死這個線程)它結束。下面是可供我們選擇的操作:
* 設計我們自己的協作的中斷機制(通過使用共享數據來實現。父線程設置這個數據,子線程檢查這個數據(子線程將會在該數據被設置后很快退出))。
* 使用thread::native_handle()來訪問線程在操作系統中的符號
* 殺死進程(std::quick_exit())
* 殺死程序(std::terminate())
這些是委員會能夠統一的所有的規則。特別地,來自POSIX的代表強烈地反對任何形式的“線程取消”。然而許多C++的資源模型都依賴于析構器。對于每種系統和每種可能的應有并沒有完美的解決方案。
線程中的一個基本問題是數據競爭。也就是當在統一地址空間的兩個線程獨立訪問一個對象時將會導致沒有定義的結果。如果一個(或者兩個)對對象執行寫操作,而另一個(或者兩個)對該對象執行讀操作,兩個線程將在誰先完成操作方面進行競爭。這樣得到的結果不僅僅是沒定義的,而且常常無法預測最后的結果。為解決這個問題,C++0x提供了一些規則和保證從而能夠讓程序員避免數據競爭。
* C++標準庫函數不能直接或間接地訪問正在被其它線程訪問的對象。一種例外是該函數通過參數(包括this)來直接或間接訪問這個對象。
* C++標準庫函數不能直接或間接修改正在被其它線程訪問的對象。一種例外是該函數通過非const參數(包括this)來直接或間接訪問這個對象。
* C++標準函數庫的實現需要避免在同時修改統一序列中的不同成員時的數據競爭。
除非已使用別的方式做了聲明,多個線程同時訪問一個流對象、流緩沖區對象,或者C庫中的流可能會導致數據競爭。因此除非你能夠控制,絕不要讓兩個線程來共享一個輸出流。
你可以
* 等待一個線程[一定的時間](http://chenlq.net/chinese-version-of-c-0-x-faq-time-utility.html)
* 通過[互斥](http://chenlq.net/cpp11-faq-chinese-version-series-exclusive.html)來控制對數據的訪問
* 通過[鎖來控制對數據的訪問](http://chenlq.net/c-0-x-faq-chinese-version-lock.html)
* 使用[條件變量](http://chenlq.net/chinese-version-of-c-11-faq-condition-variable.html)來等待另一個線程的行為
* 通過[future](http://chenlq.net/c-0-x-faq-chinese-version-std-future-and-the-the-std-promise.html)來從線程中返回值
同時可參考:
* Standard: 30 Thread support library [thread]
* 17.6.4.7 Data race avoidance [res.on.data.races]
* ???
* H. Hinnant, L. Crowl, B. Dawes, A. Williams, J. Garland, et al.:
[Multi-threading Library for Standard C++ (Revision 1)](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2320.html)
N2497==08-0007
* H.-J. Boehm, L. Crowl:
C++ object lifetime interactions with the threads API
N2880==09-0070.
* L. Crowl, P. Plauger, N. Stoughton:
Thread Unsafe Standard Functions
N2864==09-0054.
* WG14:
[Thread Cancellation](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2455.pdf) N2455=070325.
(翻譯:Yibo Zhu)
- C++11 FAQ中文版 - C++11 FAQ
- Stroustrup先生關于中文版的授權許可郵件
- Stroustrup先生關于C++11 FAQ的一些說明
- 關于C++11的一般性的問題
- 您是如何看待C++11的?
- 什么時候C++0x會成為一部正式的標準呢?
- 編譯器何時將會實現C++11標準呢?
- 我們何時可以用到新的標準庫文件?
- C++0x將提供何種新的語言特性呢?
- C++11會提供哪些新的標準庫文件呢?
- C++0x努力要達到的目標有哪些?
- 指導標準委員會的具體設計目標是什么?
- 在哪里可以找到標準委員會的報告?
- 從哪里可以獲得有關C++11的學術性和技術性的參考資料?
- 還有哪些地方我可以讀到關于 C++0x的資料?
- 有關于C++11的視頻嗎?
- C++0x難學嗎?
- 標準委員會是如何運行的?
- 誰在標準委員會里?
- 實現者應以什么順序提供C++11特性?
- 將會是C++1x嗎?
- 標準中的"concepts"怎么了?
- 有你不喜歡的C++特性嗎?
- 關于獨立的語言特性的問題
- __cplusplus宏
- alignment(對齊方式)
- 屬性(Attributes)
- atomic_operations
- auto – 從初始化中推斷數據類型
- C99功能特性
- 枚舉類——具有類域和強類型的枚舉
- carries_dependency
- 復制和重新拋出異常
- 常量表達式(constexpr)
- decltype – 推斷表達式的數據類型
- 控制默認函數——默認或者禁用
- 控制默認函數——移動(move)或者復制(copy)
- 委托構造函數(Delegating constructors)
- 并發性動態初始化和析構
- noexcept – 阻止異常的傳播與擴散
- 顯式轉換操作符
- 擴展整型
- 外部模板聲明
- 序列for循環語句
- 返回值類型后置語法
- 類成員的內部初始化
- 繼承的構造函數
- 初始化列表
- 內聯命名空間
- Lambda表達式
- 用作模板參數的局部類型
- long long(長長整數類型)
- 內存模型
- 預防窄轉換
- nullptr——空指針標識
- 對重載(override)的控制: override
- 對重載(override)的控制:final
- POD
- 原生字符串標識
- 右角括號
- 右值引用
- Simple SFINAE rule
- 靜態(編譯期)斷言 — static_assert
- 模板別名(正式的名稱為"template typedef")
- 線程本地化存儲 (thread_local)
- unicode字符
- 統一初始化的語法和語義
- (廣義的)聯合體
- 用戶定義數據標識(User-defined literals)
- 可變參數模板(Variadic Templates)
- 關于標準庫的問題
- abandoning_a_process
- 算法方面的改進
- array
- async()
- atomic_operations
- 條件變量(Condition variables)
- 標準庫中容器方面的改進
- std::function 和 std::bind
- std::forward_list
- std::future和std::promise
- 垃圾回收(應用程序二進制接口)
- 無序容器(unordered containers)
- 鎖(locks)
- metaprogramming(元編程)and type traits
- 互斥
- 隨機數的產生
- 正則表達式(regular expressions)
- 具有作用域的內存分配器
- 共享資源的智能指針——shared_ptr
- smart pointers
- 線程(thread)
- 時間工具程序
- 標準庫中的元組(std::tuple)
- unique_ptr
- weak_ptr
- system error