鍍金池/ 教程/ C/ 2.2 向線程函數(shù)傳遞參數(shù)
3.4 本章總結(jié)
6.3 基于鎖設計更加復雜的數(shù)據(jù)結(jié)構(gòu)
6.1 為并發(fā)設計的意義何在?
5.2 <code>C++</code>中的原子操作和原子類型
A.7 自動推導變量類型
2.1 線程管理的基礎
8.5 在實踐中設計并發(fā)代碼
2.4 運行時決定線程數(shù)量
2.2 向線程函數(shù)傳遞參數(shù)
第4章 同步并發(fā)操作
2.3 轉(zhuǎn)移線程所有權(quán)
8.3 為多線程性能設計數(shù)據(jù)結(jié)構(gòu)
6.4 本章總結(jié)
7.3 對于設計無鎖數(shù)據(jù)結(jié)構(gòu)的指導建議
關(guān)于這本書
A.1 右值引用
2.6 本章總結(jié)
D.2 &lt;condition_variable&gt;頭文件
A.6 變參模板
6.2 基于鎖的并發(fā)數(shù)據(jù)結(jié)構(gòu)
4.5 本章總結(jié)
A.9 本章總結(jié)
前言
第10章 多線程程序的測試和調(diào)試
5.4 本章總結(jié)
第9章 高級線程管理
5.1 內(nèi)存模型基礎
2.5 識別線程
第1章 你好,C++的并發(fā)世界!
1.2 為什么使用并發(fā)?
A.5 Lambda函數(shù)
第2章 線程管理
4.3 限定等待時間
D.3 &lt;atomic&gt;頭文件
10.2 定位并發(fā)錯誤的技術(shù)
附錄B 并發(fā)庫的簡單比較
5.3 同步操作和強制排序
A.8 線程本地變量
第8章 并發(fā)代碼設計
3.3 保護共享數(shù)據(jù)的替代設施
附錄D C++線程庫參考
第7章 無鎖并發(fā)數(shù)據(jù)結(jié)構(gòu)設計
D.7 &lt;thread&gt;頭文件
D.1 &lt;chrono&gt;頭文件
4.1 等待一個事件或其他條件
A.3 默認函數(shù)
附錄A 對<code>C++</code>11語言特性的簡要介紹
第6章 基于鎖的并發(fā)數(shù)據(jù)結(jié)構(gòu)設計
封面圖片介紹
7.2 無鎖數(shù)據(jù)結(jié)構(gòu)的例子
8.6 本章總結(jié)
8.1 線程間劃分工作的技術(shù)
4.2 使用期望等待一次性事件
8.4 設計并發(fā)代碼的注意事項
D.5 &lt;mutex&gt;頭文件
3.1 共享數(shù)據(jù)帶來的問題
資源
9.3 本章總結(jié)
10.3 本章總結(jié)
10.1 與并發(fā)相關(guān)的錯誤類型
D.4 &lt;future&gt;頭文件
3.2 使用互斥量保護共享數(shù)據(jù)
9.1 線程池
1.1 何謂并發(fā)
9.2 中斷線程
4.4 使用同步操作簡化代碼
A.2 刪除函數(shù)
1.3 C++中的并發(fā)和多線程
1.4 開始入門
第5章 C++內(nèi)存模型和原子類型操作
消息傳遞框架與完整的ATM示例
8.2 影響并發(fā)代碼性能的因素
7.1 定義和意義
D.6 &lt;ratio&gt;頭文件
A.4 常量表達式函數(shù)
7.4 本章總結(jié)
1.5 本章總結(jié)
第3章 線程間共享數(shù)據(jù)

2.2 向線程函數(shù)傳遞參數(shù)

清單2.4中,向std::thread構(gòu)造函數(shù)中的可調(diào)用對象,或函數(shù)傳遞一個參數(shù)很簡單。需要注意的是,默認參數(shù)要拷貝到線程獨立內(nèi)存中,即使參數(shù)是引用的形式,也可以在新線程中進行訪問。再來看一個例子:

void f(int i, std::string const& s);
std::thread t(f, 3, "hello");

代碼創(chuàng)建了一個調(diào)用f(3, "hello")的線程。注意,函數(shù)f需要一個std::string對象作為第二個參數(shù),但這里使用的是字符串的字面值,也就是char const *類型。之后,在線程的上下文中完成字面值向std::string對象的轉(zhuǎn)化。需要特別要注意,當指向動態(tài)變量的指針作為參數(shù)傳遞給線程的情況,代碼如下:

void f(int i,std::string const& s);
void oops(int some_param)
{
  char buffer[1024]; // 1
  sprintf(buffer, "%i",some_param);
  std::thread t(f,3,buffer); // 2
  t.detach();
}

這種情況下,buffer②是一個指針變量,指向本地變量,然后本地變量通過buffer傳遞到新線程中②。并且,函數(shù)有很有可能會在字面值轉(zhuǎn)化成std::string對象之前崩潰(oops),從而導致一些未定義的行為。并且想要依賴隱式轉(zhuǎn)換將字面值轉(zhuǎn)換為函數(shù)期待的std::string對象,但因std::thread的構(gòu)造函數(shù)會復制提供的變量,就只復制了沒有轉(zhuǎn)換成期望類型的字符串字面值。

解決方案就是在傳遞到std::thread構(gòu)造函數(shù)之前就將字面值轉(zhuǎn)化為std::string對象:

void f(int i,std::string const& s);
void not_oops(int some_param)
{
  char buffer[1024];
  sprintf(buffer,"%i",some_param);
  std::thread t(f,3,std::string(buffer));  // 使用std::string,避免懸垂指針
  t.detach();
}

還可能遇到相反的情況:期望傳遞一個引用,但整個對象被復制了。當線程更新一個引用傳遞的數(shù)據(jù)結(jié)構(gòu)時,這種情況就可能發(fā)生,比如:

void update_data_for_widget(widget_id w,widget_data& data); // 1
void oops_again(widget_id w)
{
  widget_data data;
  std::thread t(update_data_for_widget,w,data); // 2
  display_status();
  t.join();
  process_widget_data(data); // 3
}

雖然update_data_for_widget①的第二個參數(shù)期待傳入一個引用,但是std::thread的構(gòu)造函數(shù)②并不知曉;構(gòu)造函數(shù)無視函數(shù)期待的參數(shù)類型,并盲目的拷貝已提供的變量。當線程調(diào)用update_data_for_widget函數(shù)時,傳遞給函數(shù)的參數(shù)是data變量內(nèi)部拷貝的引用,而非數(shù)據(jù)本身的引用。因此,當線程結(jié)束時,內(nèi)部拷貝數(shù)據(jù)將會在數(shù)據(jù)更新階段被銷毀,且process_widget_data將會接收到?jīng)]有修改的data變量③??梢允褂?code>std::ref將參數(shù)轉(zhuǎn)換成引用的形式,從而可將線程的調(diào)用改為以下形式:

std::thread t(update_data_for_widget,w,std::ref(data));

在這之后,update_data_for_widget就會接收到一個data變量的引用,而非一個data變量拷貝的引用。

如果你熟悉std::bind,就應該不會對以上述傳參的形式感到奇怪,因為std::thread構(gòu)造函數(shù)和std::bind的操作都在標準庫中定義好了,可以傳遞一個成員函數(shù)指針作為線程函數(shù),并提供一個合適的對象指針作為第一個參數(shù):

class X
{
public:
  void do_lengthy_work();
};
X my_x;
std::thread t(&X::do_lengthy_work,&my_x); // 1

這段代碼中,新線程將my_x.do_lengthy_work()作為線程函數(shù);my_x的地址①作為指針對象提供給函數(shù)。也可以為成員函數(shù)提供參數(shù):std::thread構(gòu)造函數(shù)的第三個參數(shù)就是成員函數(shù)的第一個參數(shù),以此類推(代碼如下,譯者自加)。

class X
{
public:
  void do_lengthy_work(int);
};
X my_x;
int num(0);
std::thread t(&X::do_lengthy_work, &my_x, num);

有趣的是,提供的參數(shù)可以移動,但不能拷貝。"移動"是指:原始對象中的數(shù)據(jù)轉(zhuǎn)移給另一對象,而轉(zhuǎn)移的這些數(shù)據(jù)就不再在原始對象中保存了(譯者:比較像在文本編輯的"剪切"操作)。std::unique_ptr就是這樣一種類型(譯者:C++11中的智能指針),這種類型為動態(tài)分配的對象提供內(nèi)存自動管理機制(譯者:類似垃圾回收)。同一時間內(nèi),只允許一個std::unique_ptr實現(xiàn)指向一個給定對象,并且當這個實現(xiàn)銷毀時,指向的對象也將被刪除。移動構(gòu)造函數(shù)(move constructor)和移動賦值操作符(move assignment operator)允許一個對象在多個std::unique_ptr實現(xiàn)中傳遞(有關(guān)"移動"的更多內(nèi)容,請參考附錄A的A.1.1節(jié))。使用"移動"轉(zhuǎn)移原對象后,就會留下一個空指針(NULL)。移動操作可以將對象轉(zhuǎn)換成可接受的類型,例如:函數(shù)參數(shù)或函數(shù)返回的類型。當原對象是一個臨時變量時,自動進行移動操作,但當原對象是一個命名變量,那么轉(zhuǎn)移的時候就需要使用std::move()進行顯示移動。下面的代碼展示了std::move的用法,展示了std::move是如何轉(zhuǎn)移一個動態(tài)對象到一個線程中去的:

void process_big_object(std::unique_ptr<big_object>);

std::unique_ptr<big_object> p(new big_object);
p->prepare_data(42);
std::thread t(process_big_object,std::move(p));

std::thread的構(gòu)造函數(shù)中指定std::move(p),big_object對象的所有權(quán)就被首先轉(zhuǎn)移到新創(chuàng)建線程的的內(nèi)部存儲中,之后傳遞給process_big_object函數(shù)。

標準線程庫中和std::unique_ptr在所屬權(quán)上有相似語義類型的類有好幾種,std::thread為其中之一。雖然,std::thread實例不像std::unique_ptr那樣能占有一個動態(tài)對象的所有權(quán),但是它能占有其他資源:每個實例都負責管理一個執(zhí)行線程。執(zhí)行線程的所有權(quán)可以在多個std::thread實例中互相轉(zhuǎn)移,這是依賴于std::thread實例的可移動不可復制性。不可復制保性證了在同一時間點,一個std::thread實例只能關(guān)聯(lián)一個執(zhí)行線程;可移動性使得程序員可以自己決定,哪個實例擁有實際執(zhí)行線程的所有權(quán)。