多執行緒傳參詳解
阿新 • • 發佈:2019-01-05
1.傳遞臨時物件做執行緒引數
1.1要避免的陷阱1
用detach()時,如果主執行緒先結束,變數就會被回收;所以用detach()的話,不推薦用引用,同時絕對不能用指標。
1.2要避免的陷阱2
只要臨時物件的用臨時構造A類物件作為引數傳遞給執行緒,那麼就一定能夠在主執行緒結束之前,把執行緒函式的第二個引數構建出來,從而確保即便detach()子執行緒也安全執行,程式如下:
#include<iostream> #include<thread> #include<string> using namespace std; class A { public: int m_i; //型別轉換建構函式,可以把一個int轉換成一個類A物件。 A(int a):m_i(a){cout << "A::A(int a)建構函式執行!"<< endl;} A(const A &a) :m_i(a.m_i){cout << "A::A(A &a)複製建構函式執行!" << endl;} ~A(){cout << "A::~A()解構函式執行!" << endl;} }; void myprint1(const int &i, char *pmybuf) { //通過檢視記憶體可知變數mvar與它的引用mvary地址是相同的,但是傳遞到myprint()中, //&i的地址不是mvar的地址,所以這是個假引用,此時引用傳遞與傳值是一樣的。 //分析可得,並不是mvar的引用,實際是值傳遞,那麼我們認為,即便是主執行緒detach了子執行緒, //那麼子執行緒用i值任然是安全的! cout << i << endl; //第二個引數*pmybuf 是指標,*pmybuf的地址與mybuf[]相同; //指標在detach子執行緒時,絕對會有問題; cout << pmybuf << endl; } //此時用 string& 通過一個隱形轉換來接收mybuf[]的值,但此時安全嗎? //但是mybuf是 在什麼時候轉換成string? //事實上存在mybuf都被回收了,系統才將mybuf去轉string的可能性 //所以此方法也是不安全的 void myprint2(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!"; //thread myobj(myprint1, mvar, mybuf); //string(mybuf)生成臨時string物件;我們這裡直接將mybuf轉換成string物件, //這是一個可以保證線上程中用肯定有效的物件。 //下個程式進行驗證 thread myobj(myprint2, mvar, string(mybuf)); myobj.detach(); cout << "主執行緒執行!" << endl; system("pause"); return 0; }
通過自定義類的方式驗證 臨時物件可以保證主執行緒結束之前,把執行緒函式的引數構建出來
#include<iostream> #include<thread> #include<string> using namespace std; class A { public: int m_i; //型別轉換建構函式,可以把一個int轉換成一個類A物件。 A(int a) :m_i(a) { cout << "A::A(int a)建構函式執行!" << endl; } A(const A &a) :m_i(a.m_i) { cout << "A::A(A &a)複製建構函式執行!" << endl; } ~A() { cout << "A::~A()解構函式執行!" << endl; } }; void myprint(const int i, const A &pmybuf) { cout << &pmybuf << endl;// 列印的是pmybuf物件的地址 } int main() { int mvar = 1; int mysecondpar = 12; //我們希望mysecondpar轉成A型別物件傳遞給myprint的第二個引數 /*主執行緒什麼都不操作,快速結束主執行緒,執行程式後可以發現, 沒有輸出“A::A(int a)建構函式執行!”說明主執行緒執行完畢,*/ thread myobj(myprint, mvar, mysecondpar); /*在建立執行緒的同時構造臨時物件的方法傳遞引數是可行的! //可以保證在主執行緒結束之前,構造出來!*/ thread myobj(myprint, mvar, A(mysecondpar)); myobj.detach(); //cout << "主執行緒執行!" << endl; return 0; }
1.3總結
- 若傳遞int這種簡單型別引數,建議都是值傳遞,不要引用,防止節外生枝
- 如果傳遞類物件,避免隱式型別轉換。全部都在建立執行緒這一行就構建出臨時物件,然後在函式引數裡用引用來接;否則系統還會多構造一次物件。
- 終極結論:建議不使用detach(),只使用join();這樣就不存在區域性變數失效導致執行緒對記憶體的非法引用問題。
2.臨時物件作為執行緒引數繼續
2.1執行緒ID概念
Id是個數字,每一個執行緒(不管是主執行緒還是子執行緒)實際上都對應一個數字,而且每一個執行緒對應的這個數字都不同。也就是說,不同的執行緒,他的執行緒id必然不同;
執行緒ID可以用C++標準庫裡的函式來獲取。std::this_thread::get_id()來獲取。
2.2臨時物件構造時機抓捕
通過這個例子也可以看出,臨時物件後在main()函式中已經構造完畢了。
#include<iostream>
#include<thread>
#include<string>
using namespace std;
class A
{
public:
int m_i;
//型別轉換建構函式,可以把一個int轉換成一個類A物件。
A(int a) :m_i(a)
{
cout << "A::A(int a)建構函式執行!"<<this<<"threadid:" <<std::this_thread::get_id()<< endl;
}
A(const A &a) :m_i(a.m_i)
{
cout << "A::A(A &a)複製建構函式執行!" << this << "threadid:" << std::this_thread::get_id() << endl;
}
~A()
{
cout << "A::~A()解構函式執行!" << this << "threadid:" << std::this_thread::get_id() << endl;
}
};
void myprint2(const A &pmybuf)
{
cout << "子物件myprint的引數地址是" <<&pmybuf<<"threadid"<<std::this_thread::get_id()<<endl;// 列印的是pmybuf物件的地址
}
int main()
{
cout << "主執行緒id:" << std::this_thread::get_id() <<endl;
int mvar = 2;
//thread myobj(myprint2, mvar); //致命問題是在子執行緒中構造A類物件
thread myobj(myprint2, A(mvar)); //用了臨時物件後,所有的A類物件都在main()函式中已經構造完畢了
myobj.join();
//myobj.detach(); //子執行緒與主執行緒分別執行
return 0;
}
3.傳遞類物件、智慧指標作為執行緒引數
std::ref()函式的作用 可以實現真正的引用
#include<iostream>
#include<thread>
#include<string>
using namespace std;
class A
{
public:
mutable int m_i;
//型別轉換建構函式,可以把一個int轉換成一個類A物件。
A(int a) :m_i(a)
{
cout << "A::A(int a)建構函式執行!" << this << "threadid:" << std::this_thread::get_id() << endl;
}
A(const A &a) :m_i(a.m_i)
{
cout << "A::A(A &a)複製建構函式執行!" << this << "threadid:" << std::this_thread::get_id() << endl;
}
~A()
{
cout << "A::~A()解構函式執行!" << this << "threadid:" << std::this_thread::get_id() << endl;
}
};
void myprint2(const A &pmybuf)
{
pmybuf.m_i = 199; //我們修改該值不會影響main()函式
cout << "子物件myprint的引數地址是" << &pmybuf << "threadid" << std::this_thread::get_id() << endl;// 列印的是pmybuf物件的地址
}
int main()
{
A myobj(10); //生成一個類物件
thread mytobj(myprint2, std::ref(myobj));
mytobj.join();
return 0;
}
#include<iostream>
#include<thread>
#include<string>
using namespace std;
void myprint2(unique_ptr<int> pzn)
{
;
}
int main()
{
unique_ptr<int> myp(new int(100));
thread mytobj(myprint2,std::move(myp));
mytobj.join();
return 0;
}
4. 用成員函式指標做執行緒函式
#include<iostream>
#include<thread>
#include<string>
using namespace std;
class A
{
public:
mutable int m_i;
//型別轉換建構函式,可以把一個int轉換成一個類A物件。
A(int a) :m_i(a)
{
cout << "A::A(int a)建構函式執行!" << this << "threadid:" << std::this_thread::get_id() << endl;
}
A(const A &a) :m_i(a.m_i)
{
cout << "A::A(A &a)複製建構函式執行!" << this << "threadid:" << std::this_thread::get_id() << endl;
}
~A()
{
cout << "A::~A()解構函式執行!" << this << "threadid:" << std::this_thread::get_id() << endl;
}
void thread_work(int num)
{
cout << "子執行緒thread——work執行!" << this << "threadid:" << std::this_thread::get_id() << endl;
}
};
int main()
{
A myobj(10);
thread mytobj(&A::thread_work, &myobj, 15);
mytobj.join();
return 0;
}
注:該文是C++11併發多執行緒視訊教程筆記,詳情學習:https://study.163.com/course/courseMain.htm?courseId=1006067356