C++11并發與多線程筆記(3)線程傳參詳解,detach 大坑,成員函數做線程函數
- 1、傳遞臨時對象作為線程參數
- 1.1 要避免的陷阱1
- 1.2 要避免的陷阱2
- 1.3 總結
- 2、臨時對象作為線程參數
- 2.1 線程id概念
- 2.2 臨時對象構造時機抓捕
- 3、傳遞類對象、智能指針作為線程參數
- 3.1 類對象作為線程參數
- 3.2 智能指針作為線程參數
- 4、用成員函數指針做線程函數
1、傳遞臨時對象作為線程參數
1.1 要避免的陷阱1
#include <iostream>
#include <thread>
using namespace std;void myPrint(const int &i, char* pmybuf)
{//如果線程從主線程detach了//i不是mvar真正的引用,實際上值傳遞,即使主線程運行完畢了,子線程用i仍然是安全的,但仍不推薦傳遞引用//推薦改為const int icout << i << endl;//pmybuf還是指向原來的字符串,所以這么寫是不安全的cout << pmybuf << endl;
}int main()
{int mvar = 1;int& mvary = mvar;char mybuf[] = "this is a test";thread myThread(myPrint, mvar, mybuf);//第一個參數是函數名,后兩個參數是函數的參數myThread.join();//myThread.detach();cout << "Hello World!" << endl;
}
在使用detach時,不推薦引用傳遞,指針傳遞肯定有問題
1.2 要避免的陷阱2
#include <iostream>
#include <thread>
#include <string>
using namespace std;void myPrint(const int i, const string& pmybuf)
{cout << i << endl;cout << pmybuf << endl;
}int main()
{int mvar = 1;int& mvary = mvar;char mybuf[] = "this is a test";//如果detach了,這樣仍然是不安全的//因為存在主線程運行完了,mybuf被回收了,系統采用mybuf隱式類型轉換成string//推薦先創建一個臨時對象thread myThread(myPrint, mvar, string(mybuf));就絕對安全了thread myThread01(myPrint, mvar, mybuf);//myThread01.join();myThread01.detach();cout << "I love China!" << endl;
}
在創建線程的同時構造臨時對象的方法傳遞參數是可行的
1.3 總結
- 如果傳遞int這種基本數據類型,推薦使用值傳遞,不要用引用
- 如果傳遞類對象,避免使用隱式類型轉換,全部都是創建線程這一行就創建出臨時對象,然后在函數參數里,用引用來接,否則還會創建出一個對象
- 終極結論:建議不使用detach,只使用join,這樣就不存在局部變量失效導致線程對內存的非法引用問題。
2、臨時對象作為線程參數
2.1 線程id概念
- id是個數字,每個線程(不管是主線程還是子線程)實際上都對應著一個數字,而且每個線程對應的這個數字都不一樣。
- 線程id可以用C++標準庫里的函數來獲取。**std::this_thread::get_id()**來獲取
2.2 臨時對象構造時機抓捕
#include<iostream>
#include<thread>
using namespace std;
class A {
public:int m_a;A(int a) :m_a(a) {cout << "A(int a):m_a(a)構造函數執行" << this << " 線程id為:" <<this_thread::get_id << endl;}A(const A& a):m_a(a.m_a){cout << "A(const A& a):m_a(a.m_a)拷貝構造函數執行" << this <<" 線程id為:" << this_thread::get_id << endl;}~A() {cout << "~A() 析構函數執行" << this <<" 線程id為:" << this_thread::get_id << endl;}};
void myPrint(const int i, const A& pmybuf) {cout << i << endl;cout << pmybuf.m_a<< endl;
}
int main() {int mvar = 1;int myseconder = 12;cout<<"主線程id為:"<< this_thread::get_id() << endl;thread myThread01(myPrint, mvar, A(myseconder));myThread01.detach();cout << "hello world" << endl;
}
3、傳遞類對象、智能指針作為線程參數
3.1 類對象作為線程參數
#include <iostream>
#include <thread>
using namespace std;class A {
public:mutable int m_i; //m_i即使實在const中也可以被修改A(int i) :m_i(i) {}
};void myPrint(const A& pmybuf)//雖然是參數引用,但還是沒有修改主線程中m_i值
{pmybuf.m_i = 199;//修改的值不影響main函數cout << "子線程myPrint的參數地址是" << &pmybuf << "thread = " << std::this_thread::get_id() << endl;
}int main()
{A myObj(10);//myPrint(const A& pmybuf)中引用不能去掉,如果去掉會多創建一個對象//const也不能去掉,去掉會出錯//即使是傳遞的const引用,但在子線程中還是會調用拷貝構造函數構造一個新的對象,//所以在子線程中修改m_i的值不會影響到主線程//如果希望子線程中修改m_i的值影響到主線程,可以用thread myThread(myPrint, std::ref(myObj));,省去了傳遞時的拷貝構造函數//這樣const就是真的引用了,myPrint定義中的const就可以去掉了,類A定義中的mutable也可以去掉了thread myThread(myPrint, myObj);myThread.join();//myThread.detach();cout << "Hello World!" << endl;
}
分析:
- void myPrint(const A& pmybuf)//雖然是參數引用(& pmybuf),但還是沒有修改主線程中m_i值
- 如果希望子線程中修改m_i的值影響到主線程,可以用thread myThread(myPrint, std::ref(myObj));
3.2 智能指針作為線程參數
#include <iostream>
#include <thread>
#include <memory>
using namespace std;void myPrint(unique_ptr<int> ptn)
{cout << "thread = " << std::this_thread::get_id() << endl;
}int main()
{unique_ptr<int> myp(new int(10));//獨占式智能指針只能通過std::move()才可以傳遞給另一個指針//傳遞后up就指向空,新的ptn指向原來的內存//所以這時就不能用detach了,因為如果主線程先執行完,ptn指向的對象就被釋放了thread myThread(myPrint, std::move(myp));myThread.join();//myThread.detach();return 0;
}
分析:
- unique_ptr 構造函數已經將拷貝函數和拷貝賦值刪除了,禁止拷貝,如果需要將unique_ptr對象傳入,需要使用std::move(myp)
4、用成員函數指針做線程函數
class A {
public:int m_a;A(int a) :m_a(a) {cout << "A(int a):m_a(a)構造函數執行" << this << " 線程id為:" <<this_thread::get_id << endl;}A(const A& a):m_a(a.m_a){cout << "A(const A& a):m_a(a.m_a)拷貝構造函數執行" << this <<" 線程id為:" << this_thread::get_id << endl;}~A() {cout << "~A() 析構函數執行" << this <<" 線程id為:" << this_thread::get_id << endl;}void thread_work(int i) {cout << "子線程thread_work執行" << this <<" thread_work線程id為:" << this_thread::get_id << endl;}void operator()(int i){cout << "子線程operator()執行" << this <<" operator()線程id為:" << this_thread::get_id << endl;}
};int main() {cout<<"主線程id為:"<< this_thread::get_id() << endl;A myobj(10);//生成一個類對象;//參數1:傳成員函數地址//參數2:對象名//參數3:函數所需的參數thread myThread(&A::thread_work,myobj,15)// &myobj==std::ref(myobj) 不調用拷貝構造函數了,那后續如果調用myThread就不安全了//thread myThread(myobj,15) //使用 operator()作為子線程入口myThread.detach();cout << "hello world" << endl;
}