一:传递临时对象作为线程参数
<1>要避免的陷阱(解释1)
void myprint(const int& i, char* pmybuf)
{
cout << i << endl; //分析认为(查看i的内存地址),i并不是mavary的引用,实际是值传递,即便主线程detach了子线程,那么子线程中用这个i值仍然是安全的。
cout << pmybuf << endl; //通过查看pmybuf的地址,指针在detach子线程时,绝对会有问题
return;
}
int main()
{
int mvar = 1;
int& mvary = mvar;
char mybuf[] = "this is test!";
thread mytobj(myprint, mvar, mybuf);
//mytobj.join();
mytobj.detach();
cout << "main主函数执行结束" << endl;
return 0;
}
这是有问题的。
//void myprint(const int& i, char* pmybuf)
//void myprint(const int i, char* pmybuf)
void myprint(const int& i, const string& pmybuf)
{
cout << i << endl; //分析认为(查看i的内存地址),i并不是mavary的引用,实际是值传递,即便主线程detach了子线程,那么子线程中用这个i值仍然是安全的。
cout << pmybuf.c_str() << endl; //查看pmybuf内存地址,与main函数中,mybuf内存地址不一致,感觉是线程安全了。
return;
}
int main()
{
int mvar = 1;
int& mvary = mvar;
char mybuf[] = "this is test!";
thread mytobj(myprint, mvar, mybuf); //但是mybuf到底是在什么时候转换成string
//事实上存在,mybuf都被回收了(main函数执行完了),系统才用mybuf去转string的可能性;
//mytobj.join();
mytobj.detach();
cout << "main主函数执行结束" << endl;
return 0;
}
这个char转string也是有问题的。
//void myprint(const int& i, char* pmybuf)
//void myprint(const int i, char* pmybuf)
void myprint(const int& i, const string& pmybuf)
{
cout << i << endl; //分析认为(查看i的内存地址),i并不是mavary的引用,实际是值传递,即便主线程detach了子线程,那么子线程中用这个i值仍然是安全的。
cout << pmybuf.c_str() << endl; //查看pmybuf内存地址,与main函数中,mybuf内存地址不一致,感觉是线程安全了。
return;
}
int main()
{
int mvar = 1;
int& mvary = mvar;
char mybuf[] = "this is test!";
//thread mytobj(myprint, mvar, mybuf); //但是mybuf到底是在什么时候转换成string
//事实上存在,mybuf都被回收了(main函数执行完了),系统才用mybuf去转string的可能性;
thread mytobj(myprint, mvar, string(mybuf)); //我们这里直接将mybuf转换成string对象,这是一个可以保证在线程中肯定有效的对象。
//mytobj.join();
mytobj.detach(); //子线程和主线程分别执行。
cout << "main主函数执行结束" << endl;
return 0;
}
这是线程安全的。
<2>要避免的陷阱(解释2)
class A
{
public:
int m_i;
//mutable int m_i;
//类型转换构造函数,可以把一个int转换成一个类A对象。
A(int a) :m_i(a)
{
cout << "[A::A(int a)构造函数执行]" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
A(const A& a) :m_i(a.m_i)
{
cout << "[A::A(cosnt A)]拷贝构造函数执行]" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
~A()
{
cout << "[A::~A()析构函数执行]" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
void thread_work(int num)
{
cout << "子线程thread_work执行了" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
void operator()(int num)
{
cout << "子线程()执行 " << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
};
void myprint(const int i, const A& pmybuf)
{
cout << "&pmybuf的地址: " << &pmybuf << endl; //这里打印的是pmybuf对象的地址
return;
}
int main()
{
int var = 1;
int secondvar = 12;
thread mytobj(myprint, var, A(secondvar)); //我们希望secondvar转成A类型对象传递给myprint的第二个参数
//thread mytobj(myprint, var, static_cast<A>(secondvar));
//在创建线程的同时构造临时对象的方法传递参数是可行的
//mytobj.join();
mytobj.detach();
//cout << "main主函数执行结束" << endl;
return 0;
}
事实1:只要用临时构造的类A对象作为参数传递给线程,那么就一定能够在主线程执行完毕前把线程的第二个参数构建出来,从而确保即便detach()子线程也安全运行。
class A
{
public:
int m_i;
//mutable int m_i;
//类型转换构造函数,可以把一个int转换成一个类A对象。
A(int a) :m_i(a)
{
cout << "[A::A(int a)构造函数执行]" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
A(const A& a) :m_i(a.m_i)
{
cout << "[A::A(cosnt A)]拷贝构造函数执行]" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
~A()
{
cout << "[A::~A()析构函数执行]" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
void thread_work(int num)
{
cout << "子线程thread_work执行了" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
void operator()(int num)
{
cout << "子线程()执行 " << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
};
void myprint(const int i, const A& pmybuf)
{
cout << "&pmybuf的地址: " << &pmybuf << endl; //这里打印的是pmybuf对象的地址
return;
}
void myprint2(A& pmybuf)
{
pmybuf.m_i = 199; //我们修改该值不会影响到main函数
cout << "子线程的参数地址是" << &pmybuf << " " << "this id = " << this_thread::get_id() << endl;
}
void myprint3(unique_ptr<int> p)
{
}
void printPA(const int i, const A& a)
{
cout << &a << endl;
}
int main()
{
int var = 1;
int secondvar = 12;
thread mytobj(myprint, var, A(secondvar)); //我们希望secondvar转成A类型对象传递给myprint的第二个参数
//thread mytobj(myprint, var, static_cast<A>(secondvar));
//在创建线程的同时构造临时对象的方法传递参数是可行的
//mytobj.join();
mytobj.detach();
//cout << "main主函数执行结束" << endl;
return 0;
}
A(int a) :m_i(a)构造函数执行00000047BC6FFB64
A(const A& a)拷贝构造函数执行000001ADA3B51F80
~A()析构函数执行00000047BC6FFB64
main主函数执行结束
ps:
发现void myprint(const int& i, const A& pmybuf),
A对象是否是引用,都会构建拷贝构造函数。书上说是由于thread引起的。暂时不解。
若A对象没有引用,则会调用两次拷贝构造函数,第二次调用感觉有问题。
<3>总结
1>如果传递int这种简单类型参数,建议都是值传递,不要用引用,防止节外生枝;
2>如果传递类对象,避免隐式类型转换。全部都在创建线程这一行就构建出临时对象来,然后在函数参数里,用引用来接,否则系统还会多构造一次对象,浪费;
终极结论:
3>建议不使用detach(),只使用join();这样就不存在局部变量失效导致线程对内存的非法引用问题。
二:临时对象作为线程参数继续讲
常用测试大法
<1>线程id概念
id是个数字,每个线程(不管是主线程还是子线程)实际上都对应着一个数字,而且每个线程对应的数字都不同;也就是说不同的线程,它的线程id(数字)必然是不同;
线程id可以用C++标准库里的函数来获取,std::this_thread::get_id()来获取;
<2>临时对象构造时机捕获
class A
{
public:
int m_i;
//mutable int m_i;
//类型转换构造函数,可以把一个int转换成一个类A对象。
A(int a) :m_i(a)
{
cout << "[A::A(int a)构造函数执行]" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
A(const A& a) :m_i(a.m_i)
{
cout << "[A::A(cosnt A)]拷贝构造函数执行]" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
~A()
{
cout << "[A::~A()析构函数执行]" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
void thread_work(int num)
{
cout << "子线程thread_work执行了" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
void operator()(int num)
{
cout << "子线程()执行 " << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
};
void myprint(const int i, const A& pmybuf)
{
cout << "&pmybuf的地址: " << &pmybuf << endl; //这里打印的是pmybuf对象的地址
return;
}
void myprint2(const A& pmybuf)
{
//pmybuf.m_i = 199; //我们修改该值不会影响到main函数
cout << "子线程void myprint2(A& pmybuf)的参数地址是 " << &pmybuf << " " << "this id = " << this_thread::get_id() << endl;
}
void myprint3(unique_ptr<int> p)
{
}
void printPA(const int i, const A& a)
{
cout << &a << endl;
}
int main()
{
cout << "主线程id: " << this_thread::get_id() << endl;
int mvar = 1;
thread mytobj2(myprint2, mvar);
mytobj2.join();
//mytobj2.detach();
cout << "main主函数执行结束" << endl;
return 0;
}
主线程id: 18960
[A::A(int a)构造函数执行] 00000009149FF664 threadid = 16412
子线程void myprint2(A& pmybuf)的参数地址是 00000009149FF664 this id = 16412
[A::~A()析构函数执行] 00000009149FF664 threadid = 16412
main主函数执行结束
注意:构造函数等都是在子线程中创造的,存在线程安全问题。
//将main()修改成如下:
int main()
{
cout << "主线程id: " << this_thread::get_id() << endl;
int mvar = 1;
thread mytobj2(myprint2, A(mvar));
//mytobj2.join();
mytobj2.detach();
cout << "main主函数执行结束" << endl;
return 0;
}
主线程id: 25528
[A::A(int a)构造函数执行] 000000C58113FC34 threadid = 25528
[A::A(cosnt A)]拷贝构造函数执行] 000001DC49D827A0 threadid = 25528
[A::~A()析构函数执行] 000000C58113FC34 threadid = 25528
main主函数执行结束
子线程void myprint2(A& pmybuf)的参数地址是
若将void myprint2(A& pmybuf)更改为void myprint2(A pmybuf);
主线程id: 11000
[A::A(int a)构造函数执行] 0000005CAA4FF8C4 threadid = 11000
[A::A(cosnt A)]拷贝构造函数执行] 000001A4653F29D0 threadid = 11000
[A::~A()析构函数执行] 0000005CAA4FF8C4 threadid = 11000[A::A(cosnt A)]拷贝构造函数执行]
0000005CAA5FFC24 threadid = 23544
main主函数执行结束
子线程void myprint2(A& pmybuf)的参数地址是
多生成一个拷贝构造函数,尽量不这样使用。
疑问点:这个在子线程中多生成的拷贝构造函数,是通过主线程的A来传递,还是已经创建好的A来传递,会不会有线程安全问题?
三:传递类对象与智能指针作为线程参数
class A
{
public:
mutable int m_i;
//类型转换构造函数,可以把一个int转换成一个类A对象。
A(int a) :m_i(a)
{
cout << "[A::A(int a)构造函数执行]" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
A(const A& a) :m_i(a.m_i)
{
cout << "[A::A(cosnt A)]拷贝构造函数执行]" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
~A()
{
cout << "[A::~A()析构函数执行]" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
void thread_work(int num)
{
cout << "子线程thread_work执行了" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
void operator()(int num)
{
cout << "子线程()执行 " << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
};
void myprint(const int i, const A& pmybuf)
{
cout << "&pmybuf的地址: " << &pmybuf << endl; //这里打印的是pmybuf对象的地址
return;
}
void myprint2(const A& pmybuf)
{
pmybuf.m_i = 199; //我们修改该值不会影响到main函数
cout << "子线程void myprint2(A& pmybuf)的参数地址是 " << &pmybuf << " " << "this id = " << this_thread::get_id() << endl;
}
void myprint3(unique_ptr<int> p)
{
}
void printPA(const int i, const A& a)
{
cout << &a << endl;
}
int main()
{
cout << "主线程id: " << this_thread::get_id() << endl;
A myobj(10); //生成一个类对象
thread mytobj(myprint2, myobj); //myobj将类对象作为线程参数
mytobj.join();
//mytobj.detach();
cout << "main主函数执行结束" << endl;
return 0;
}
void myprint2(const A& pmybuf)
如果使用引用&作为参数,前面必须加const,否则大多数编译器会报错。
std::ref()函数,修改main()函数如下:
int main()
{
cout << "主线程id: " << this_thread::get_id() << endl;
A myobj(10); //生成一个类对象
thread mytobj(myprint2, std::ref(myobj)); //myobj将类对象作为线程参数
mytobj.join();
cout << "myobj.m_i = " << myobj.m_i << endl;
//mytobj.detach();
cout << "main主函数执行结束" << endl;
return 0;
}
主线程id: 15708
[A::A(int a)构造函数执行] 0000004415F2F824 threadid = 15708
子线程void myprint2(A& pmybuf)的参数地址是 0000004415F2F824 this id = 20432
myobj.m_i = 199
main主函数执行结束
[A::~A()析构函数执行] 0000004415F2F824 threadid = 15708
若使用智能指针,main()修改为
void myprint3(unique_ptr<int> p)
{
}
int main()
{
cout << "主线程id: " << this_thread::get_id() << endl;
unique_ptr<int> myp(new int(100));
thread mytobj(myprint3, std::move(myp)); //myobj将类对象作为线程参数
mytobj.join();
//mytobj.detach();
cout << "main主函数执行结束" << endl;
return 0;
}
主线程id: 19736
main主函数执行结束
一定不能detach,存在线程安全问题。
四:用成员函数指针做线程函数
class A
{
public:
mutable int m_i;
//类型转换构造函数,可以把一个int转换成一个类A对象。
A(int a) :m_i(a)
{
cout << "[A::A(int a)构造函数执行]" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
A(const A& a) :m_i(a.m_i)
{
cout << "[A::A(cosnt A)]拷贝构造函数执行]" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
~A()
{
cout << "[A::~A()析构函数执行]" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
void thread_work(int num)
{
m_i = num;
cout << "子线程thread_work执行了" << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
void operator()(int num)
{
cout << "子线程()执行 " << " " << this << " " << "threadid = " << this_thread::get_id() << endl;
}
};
void myprint(const int i, const A& pmybuf)
{
cout << "&pmybuf的地址: " << &pmybuf << endl; //这里打印的是pmybuf对象的地址
return;
}
void myprint2(const A& pmybuf)
{
pmybuf.m_i = 199; //我们修改该值不会影响到main函数
cout << "子线程void myprint2(A& pmybuf)的参数地址是 " << &pmybuf << " " << "this id = " << this_thread::get_id() << endl;
}
void myprint3(unique_ptr<int> p)
{
}
void printPA(const int i, const A& a)
{
cout << &a << endl;
}
int main()
{
cout << "主线程id: " << this_thread::get_id() << endl;
A mobj(10); //生成一个类对象
thread mytobj(&A::thread_work, &mobj, 15); //&pa == std::ref()
mytobj.join();
//mytobj.detach();
cout << "mobj.m_i = " << mobj.m_i << endl;
cout << "main主函数执行结束" << endl;
return 0;
}
主线程id: 1960
[A::A(int a)构造函数执行] 000000A0B01BF574 threadid = 1960
子线程thread_work执行了 000000A0B01BF574 threadid = 7904
mobj.m_i = 15
main主函数执行结束
[A::~A()析构函数执行] 000000A0B01BF574 threadid = 1960
&pa == std::ref();均没有调用拷贝构造函数,若使用detach,均存在线程安全问题。
添加A类对象函数,修改main()函数如下:
int main()
{
cout << "主线程id: " << this_thread::get_id() << endl;
A mobj(10); //生成一个类对象
thread mytobj(std::ref(mobj), 15); //不再调用拷贝构造函数了,那么后续如果调用detach就不安全了
mytobj.join();
//mytobj.detach();
cout << "mobj.m_i = " << mobj.m_i << endl;
cout << "main主函数执行结束" << endl;
return 0;
}