?執行緒傳參詳解
一、傳遞臨時物件作為執行緒引數
1.1要避免的陷阱
直接看程式碼:
#include <iostream>
#include <thread>
using namespace std;
void myPrint(const int &i, char* pmybuf)
{
//如果執行緒從主執行緒detach了
// ####1.i不是mvar真正的引用,實際上還是值傳遞(通過檢視地址瞭解到),即使主執行緒執行完畢了,子執行緒用i仍然是安全的,但仍不推薦傳遞引用 !!!!
//實際上,使用join傳入的也是假引用!
// 推薦改為const int i !!!
cout << 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;
return 0;
}
1.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了,這樣仍然是不安全的
//#####2.因為存在主執行緒執行完了,mybuf被回收了,系統才進行mybuf隱式型別轉換成string!!!
//推薦先建立一個臨時物件thread myThread(myPrint, mvar, string(mybuf));就絕對安全了,這樣就會在主執行緒中完成string型別構造和引數的拷貝構造!!!(可通過自己構造類,加入執行緒中來測試一下);
thread myThread(myPrint, mvar, mybuf);
myThread.join();
//myThread.detach();
cout << "Hello World!" << endl;
return 0;
}
1.3總結
- 如果傳遞int這種簡單型別,推薦使用值傳遞,不要用引用
- 如果傳遞類物件,避免使用隱式型別轉換,全部都是建立執行緒這一行就建立出臨時物件,然後在函式引數裡,用引用來接,否則還會建立出一個物件
- 終極結論:建議不使用detach
二、臨時物件作為執行緒引數繼續講
2.1 執行緒id概念
- id是個數字,每個執行緒(不管是主執行緒還是子執行緒)實際上都對應著一個數字,而且每個執行緒對應的這個數字都不一樣
- 執行緒id可以用C++標準庫裡的函式來獲取。std::this_thread::get_id()來獲取
2.2 臨時物件構造時機分析
為什麼需要在建立執行緒的時候就建立臨時物件?不能隱式型別轉換呢?
①下面我們來看看隱式型別轉換:
#include <iostream>
using namespace std;
#include<thread>
class A
{
public:
int _ma;
A(int ma) : _ma(ma) { cout << "建構函式A()" << " 物件地址:" << this << " 執行緒PID:" << std::this_thread::get_id() << endl; }
~A() { cout << "解構函式~A()" << " 物件地址:" << this << " 執行緒PID:" << std::this_thread::get_id() << endl; }
A(const A& a) : _ma(a._ma) { cout << "拷貝建構函式A(const A &a)" << " 物件地址:" << this << " 執行緒PID:" << std::this_thread::get_id() << endl; }
};
void myprint2(const A& a)
{
cout << "物件地址:" << &a << " 子執行緒PID:" << std::this_thread::get_id() << endl;
}
int main()
{
cout << "執行緒PID:" << std::this_thread::get_id() << endl;
int temp = 1;
thread mytobj(myprint2, temp);//隱式型別轉換
//主執行緒等子執行緒
mytobj.join();
//主執行緒子執行緒分離
//mytobj.detach();
return 0;
}
結果:
可以看出:存在致命問題:竟然是在子執行緒中創造A類物件,子執行緒函式中的引用起效果。這說明:如果我們採用detach方式分離了主執行緒和子執行緒,一旦主執行緒比子執行緒引數隱式轉換前結束了,那麼子執行緒將會產生不可預估的結果。
②我們在來看看建立臨時物件的方式:
修改上面的thread mytobj(myprint2, temp);
,改為:thread mytobj(myprint2, A(temp));
用join和detach方式,都得結果:
可以看出:所有A類物件都在主函式中構造,子執行緒函式中的引用不起效果。 這說明:在採用detach分離了主執行緒和子執行緒,需要傳入臨時物件。
還有一個問題:如果子執行緒函式中不用&,會怎麼樣呢?
會比加引用多了一次子執行緒內的拷貝構造!浪費記憶體,為什麼呢?因為不同的編譯器或平臺,對於thread函式處理的方式是不一樣,可以用這套程式碼區Linux看看。(物件優化問題)
三、傳遞類物件、智慧指標作為執行緒引數
3.1傳遞類物件作為執行緒引數
要基於上面的臨時物件作為執行緒引數,子執行緒裡面改物件的值會改變主執行緒物件的值嗎?繼續寫程式碼:
#include <iostream>
#include <thread>
using namespace std;
class A {
public:
mutable int m_i; //記住!!!:帶有mutable關鍵字,m_i即使實在const中也可以被修改
A(int i) :m_i(i) {}
};
void myPrint(const A& pmybuf)
{
pmybuf.m_i = 199;
cout << "子執行緒myPrint的引數地址是" << &pmybuf << "thread = " << std::this_thread::get_id() << endl;
}
int main()
{
A myObj(10);
//myPrint(const A& pmybuf)中引用不能去掉,如果去掉會多建立一個物件
//const也不能去掉,去掉會出錯,可能出現無法接受右值情況(傳入臨時物件)
//即使是傳遞的const引用,但在子執行緒中還是會呼叫拷貝建構函式構造一個新的物件,
//因為主執行緒和子執行緒中物件不同,所以在子執行緒中修改m_i的值不會影響到主執行緒
thread myThread(myPrint, myObj);
myThread.join();
//myThread.detach();
cout << "Hello World!" << endl;
}
//如果希望子執行緒中修改m_i的值影響到主執行緒,可以用thread myThread(myPrint, std::ref(myObj));這樣const就是真的引用了,myPrint定義中的const就可以去掉了,類A定義中的mutable也可以去掉了,因為傳入的就是主執行緒的物件
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> up(new int(10));
//獨佔式指標只能通過std::move()才可以傳遞給另一個指標
//傳遞後up就指向空,新的ptn指向原來的記憶體
//所以這時就不能用detach了,因為如果用detach,主執行緒先執行完,new int(10)屬於主執行緒中的洩漏資源,ptn指向的物件就被釋放了!
thread myThread(myPrint, std::move(up));
myThread.join();
//myThread.detach();
return 0;
}
//這塊忘記了可以去看看智慧指標;
四、用成員函式指標做執行緒函式
#include <iostream>
using namespace std;
#include<thread>
class A
{
public:
int _ma;
A(int ma) : _ma(ma) { cout << "建構函式A()" << " 物件地址:" << this << " 執行緒PID:" << std::this_thread::get_id() << endl; }
~A() { cout << "解構函式~A()" << " 物件地址:" << this << " 執行緒PID:" << std::this_thread::get_id() << endl; }
A(const A& a) : _ma(a._ma) { cout << "拷貝建構函式A(const A &a)" << " 物件地址:" << this << " 執行緒PID:" << std::this_thread::get_id() << endl; }
void thread_work(int num) // 子執行緒任務
{
cout << "子執行緒執行" << " 子執行緒PID:" << std::this_thread::get_id() << endl;
}
};
int main()
{
cout << "主執行緒PID:" << std::this_thread::get_id() << endl;
A myobj(10);
thread mytobj(&A::thread_work, myobj, 15); //傳入 成員函式 + 類物件 + 成員函式的引數值;
//主執行緒等子執行緒
mytobj.join();
//主執行緒子執行緒分離
//mytobj.detach();
return 0;
}
join、detach都沒有問題:
可以看出:會在主執行緒中拷貝構造一個臨時物件傳給子執行緒,由子執行緒掌控該物件析構釋放