C++併發實戰3:向thread傳遞引數
在建立thread object時可以向執行緒傳遞引數,預設情況下,引數會被拷貝到執行緒空間以供執行緒執行時存取,即使引數是引用也是這樣。
情形1:
void f(int i,std::string const& s);
boost::thread t(f,3,”hello”); //字串常量“hello”會被轉換為string,該string線上程中存在。
情形2:
void f(int i,std::string const& s); void oops(int some_param) { char buffer[1024]; sprintf(buffer, "%i",some_param); boost::thread t(f,3,buffer); //換成boost::thread t(f,3,string(buffer))保證安全 t.detach(); }
這裡會存在一個潛在的風險:當oops退出時,buffer會被銷燬,但是如果此時在子執行緒中buffer還沒有被轉化成string,將會出現dangle pointer。一個解決辦法是:boost::thread t(f,3,string(buffer));此時thread在構造時就會將buffer轉換為string,並將string拷貝至子執行緒地址空間。
情形3:thread構造會拷貝其引數
#include<iostream> #include<string> #include<boost/thread.hpp> #include<unistd.h> using namespace std; class test{ public: test(int i=0):data(i){} test(const test& one){ data=one.data; cout<<"test::copy constructor"<<endl; } test& operator=(const test& one){ data=one.data; cout<<"test::operator=()"<<endl; return *this; } ~test(){ cout<<"test::destructor"<<endl; } public: int data; }; void func(test& one){ cout<<"func get the data "<<one.data<<endl; } void oops(){ test one(10); boost::thread t(func,one); t.join(); } int main(){ oops(); return 0; }
程式輸出:
test::copy constructor
test::copy constructor
test::copy constructor
test::copy constructor
test::copy constructor
test::destructor
test::copy constructor
test::destructor
test::destructor
test::copy constructor
test::copy constructor
test::destructor
test::destructor
test::destructor
test::destructor
func get the data 10 //這裡是執行緒函式的輸出
test::destructor
test::destructor
說明:即使執行緒函式func採用引用方式,thread並不知道這一情形,所以在thread object在構造時會盲目的拷貝test one,然後thread將這個物件拷貝到執行緒地址空間作為internal copy,此後執行緒函式引用的是執行緒地址空間的那個internal copy,執行緒執行完畢,thread會銷燬internal copy。對於這一點和boost::bind也一樣,boost::bind也會拷貝引數。至於上面程式出現的那麼多拷貝和析構輸出已經我已無力解釋了sorry。
情形4: 有些時候需要向執行緒傳遞一個引用,由執行緒執行一些計算最後這個計算結果將在主執行緒中使用,可以用boost::ref傳遞一個引用到執行緒空間。
#include<iostream>
#include<string>
#include<boost/thread.hpp>
#include<boost/ref.hpp>
#include<unistd.h>
using namespace std;
class test{
public:
test(int i=0):data(i){}
test(const test& one){
data=one.data;
cout<<"test::copy constructor"<<endl;
}
test& operator=(const test& one){
data=one.data;
cout<<"test::operator=()"<<endl;
return *this;
}
~test(){
cout<<"test::destructor"<<endl;
}
public:
int data;
};
void func(test& one){
cout<<"func get the data "<<one.data++<<endl;
}
void oops(){
test one(10);
boost::thread t(func,boost::ref(one));
t.join();
cout<<"oops() get the data "<<one.data<<endl;
}
int main(){
oops();
return 0;
}
程式輸出:
func get the data 10 //子執行緒修改資料
oops() get the data 11 //主執行緒將得到子執行緒修改後的結果,因為boost::ref是傳遞引用,沒有拷貝構造之類的
test::destructor
情形5:thread傳遞函式物件
#include<iostream>
#include<boost/thread.hpp>
using namespace std;
class test{
public:
test(int i=0):data(i){}
~test(){
cout<<"test::destructor"<<endl;
}
test(const test& one){
data=one.data;
}
test& operator=(const test& one){
data=one.data;
return *this;
}
void operator()(){
cout<<"test::operator() "<<data<<endl;
}
void show(){
cout<<"test::show() "<<data<<endl;
}
public:
int data;
};
void oops(){
boost::thread t((test()));
t.join();
}
int main(){
oops();
return 0;
}
程式輸出:
test::destructor //函式物件沒有產生copy constructor
test::destructor
test::destructor
test::operator() 0
test::destructor
情形6:仿照boost::bind將物件的成員函式作為執行緒函式
#include<iostream>
#include<boost/thread.hpp>
using namespace std;
class test{
public:
test(int i=0):data(i){}
~test(){
cout<<"test::destructor"<<endl;
}
test(const test& one){
data=one.data;
}
test& operator=(const test& one){
data=one.data;
return *this;
}
void operator()(){
cout<<"test::operator() "<<data<<endl;
}
void show(){
cout<<"test::show() "<<data<<endl;
}
public:
int data;
};
void oops(){
test one(10);
boost::thread my_thread(&test::show,&one);
my_thread.join();
}
int main(){
oops();
return 0;
}
程式輸出:
test::show() 10
test::destructor
說明:此時thread的執行緒將呼叫one::show(),thread將&one當做物件地址傳遞到執行緒中。
情形7:向thread傳遞指標:普通指標和智慧指標。
#include<iostream>
#include<boost/thread.hpp>
#include<boost/shared_ptr.hpp>
using namespace std;
class test{
public:
test(int i=0):data(i){}
~test(){
cout<<"test::destructor"<<endl;
}
test(const test& one){
data=one.data;
}
test& operator=(const test& one){
data=one.data;
return *this;
}
public:
int data;
};
void fun(boost::shared_ptr<test> ptr){
ptr->data++;
}
void fun_(test* ptr){
ptr->data++;
}
void oops(){
boost::shared_ptr<test> ptr(new test(10));
boost::thread my_thread(fun,ptr);
my_thread.join();
cout<<"shared_ptr "<<ptr->data<<endl;
test* one=new test(10);
boost::thread t(fun_,one);
t.join();
cout<<"test* "<<one->data<<endl;
delete one;
}
int main(){
oops();
return 0;
}
程式輸出:
shared_ptr 11 //智慧指標
test* 11 //普通指標
test::destructor
test::destructor
說明:傳遞指標給執行緒函式,thread拷貝的是指標物件,故執行緒對指標所指物件的修改將會影響到主執行緒中的物件。注意的是智慧指標可能會引起物件生命週期的延長,若thread::detach那麼智慧指標肯定比普通裸指標安全,特別是detach後oops退出,智慧指標管理的物件由於引用計數不會被析構,而普通裸指標由於oops退出析構了局部物件導致dangle pointer。
情形8:std::unique_ptr的movable語義傳遞引數
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::move?std::unique_ptr是個左值物件,std::move的功能是將左值轉為右值使用
說明:std::unique_ptr不能共享所有權,但是可以轉移所有權,採用std::move()語義後原來的std::unique_ptr將為NULL。