3.1 传递临时对象作为线程参数
3.1.1 要避免的陷阱–解释1
#include <iostream>
#include <thread>
using namespace std;
void myprint(const int &i, char* pmybuf)
{
cout << i << endl;
cout << pmybuf << endl;
return;
}
int main()
{
int mvar = 1;
int &mvary = mvar;
char mybuf[] = "this is a test!";
thread mytobj(myprint, mvar, mybuf);
//mytobj.join();
mytobj.detach();
cout << "主线程执行结束!!!" << endl;
return 0;
}
1、使用detach时,会不会因为主线程结束了就销毁了mvar变量,导致子线程访问不安全呢?
调试时,shift+f9查看变量的地址发现mvar的地址和i的地址不一样,分析认为i并不是mvar的引用,实际是值传递,那么我们认为,即便主线程detach了子线程,那么子线程中使用i值也是安全的。但是也不推荐使用引用,还是使用值传递
2、那么pmybuf是不是安全的呢?
查看地址发现地址一样,主线程执行完了,mybuf 指向的内存被系统回收,子线程再使用这个指针是不安全的,因此在detach子线程时,指针绝对会有问题
mybuf = 0x010ff9d0 "烫烫烫烫烫烫烫烫
pmybuf = 0x010ff9d0 “this is a test!”
3.1.2 要避免的陷阱–解释2
#include <iostream>
#include <thread>
using namespace std;
void myprint(const int i, const string& pmybuf)
{
cout << i << endl;
cout << pmybuf.c_str() << endl;
return;
}
int main()
{
int mvar = 1;
int &mvary = mvar;
char mybuf[] = "this is a test!";
thread mytobj(myprint, mvar, mybuf);
//mytobj.join();
mytobj.detach();
cout << "主线程执行结束!!!" << endl;
return 0;
}
1、那么使用const string& pmybuf去接受字符串数组安全吗?
内存地址并不同,看是好像安全,但是真的安全吗?需要清楚mybuf 是在什么时候被转化为string的,事实上存在mybuf都被回收了(main函数执行完了),系统才用mybuf去转string的可能性,那么如何修改呢?
我们直接将mybuf转换成string对象,这是一个可以保证在线程中用肯定有效的对象
#include <iostream>
#include <thread>
using namespace std;
void myprint(const int i, const string& pmybuf)
{
cout << i << endl;
cout << pmybuf.c_str() << endl;
return;
}
int main()
{
int mvar = 1;
int &mvary = mvar;
char mybuf[] = "this is a test!";
thread mytobj(myprint, mvar, string(mybuf));
//mytobj.join();
mytobj.detach();
cout << "主线程执行结束!!!" << endl;
return 0;
}
在创建线程的同时构造临时对象的方法传递参数是可行的
3.1.3 总结
1、若传递int这种简单类型参数,建议都是值传递,不要用引用。防止节外生枝。
2、如果传递类对象,避免隐式类型转换。全部都在创建线程这一行就构建出临时对象来,然后在函数参数里,用引用来接,否则系统还会构造一次对象,浪费
//终极结论:
3、建议不使用detach(),只使用join(), 这样就不存在局部变量失效导致线程对内存的非法引用问题
3.2 临时对象作为线程参数(续)
3.2.1 线程id概念
1、概念:id是个数字,每个线程(不管是主线程还是子线程)实际上都对应着一个数字,而且每个线程对应的这个数字都不同。也就是说,不同的线程,它的线程id数字)必然是不同,线程id可以用c++标准库里的函数来获取std::this_thread::get_id()
3.2.2 临时对象构造时机抓捕
#include <iostream>
#include <thread>
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(const A)拷贝构造函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
~A() { cout << "[A::~A()析构函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
};
void myprint(const A& pmybuf)
{
cout << "子线程myprint的参数地址是" << &pmybuf << "threadid = " <<std::this_thread::get_id() << endl;
}
int main()
{
cout << "主线程id是" << std::this_thread::get_id() << endl;
int mvar = 1;
thread mytobj(myprint, mvar);
mytobj.join();
return 0;
}
使用临时对象
#include <iostream>
#include <thread>
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(const A)拷贝构造函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
~A() { cout << "[A::~A()析构函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
};
void myprint(const A& pmybuf)
{
cout << "子线程myprint的参数地址是" << &pmybuf << "threadid = " <<std::this_thread::get_id() << endl;
}
int main()
{
cout << "主线程id是" << std::this_thread::get_id() << endl;
int mvar = 1;
thread mytobj(myprint, A(mvar));
mytobj.join();
return 0;
}
用了临时对象后,所有的A类对象都在main()函数中就已经构建完毕了
此时使用mytobj.detach()就不会有问题
注意:void myprint(const A& pmybuf)要使用引用,不用引用会调用一个构造函数,两个拷贝构造函数,造成浪费,此外第二个拷贝构造还是在主线程中执行的
3.3 传递类对象、智能指针作为线程参数
#include <iostream>
#include <thread>
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(const A)拷贝构造函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
~A() { cout << "[A::~A()析构函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
};
void myprint(const A& pmybuf)
{
pmybuf.m_i = 199; //我们修改该值不会影响到main函数
cout << "子线程myprint的参数地址是" << &pmybuf << "threadid = " <<std::this_thread::get_id() << endl;
}
int main()
{
cout << "主线程id是" << std::this_thread::get_id() << endl;
A myobj(10); //生成一个类对象
thread mytobj(myprint, myobj); //myobj将类对象作为一个线程参数
mytobj.join();
return 0;
}
shift + f9
通过分析发现,这都不是同一个地址,虽然是引用,但是在main函数传递的myobj并没有在线程中被修改,这种写法会被拷贝一份
但是用引用,就应该被修改,这个要怎么解决呢?
使用std::ref
#include <iostream>
#include <thread>
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(const A)拷贝构造函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
~A() { cout << "[A::~A()析构函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
};
void myprint(A& pmybuf)
{
pmybuf.m_i = 199; //我们修改该值不会影响到main函数
cout << "子线程myprint的参数地址是" << &pmybuf << "threadid = " <<std::this_thread::get_id() << endl;
}
int main()
{
cout << "主线程id是" << std::this_thread::get_id() << endl;
A myobj(10); //生成一个类对象
std::thread mytobj(myprint, std::ref(myobj)); //myobj将类对象作为一个线程参数
mytobj.join();
return 0;
}
此时地址一样,可以实现使用引用,在main函数传递的myobj可以在线程中被修改,这种写法不会再被拷贝一份
如果是智能指针该怎么传呢?
要使用std::move
#include <iostream>
#include <thread>
using namespace std;
void myprint(unique_ptr<int> pzn)
{
return 0;
}
int main()
{
cout << "主线程id是" << std::this_thread::get_id() << endl;
unique_ptr<int> myp(new int(100));
std::thread mytobj(myprint, std::move(myp));
mytobj.join();
return 0;
}
3.4 用成员函数指针作线程函数
#include <iostream>
#include <thread>
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(const 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()
{
cout << "主线程id是" << std::this_thread::get_id() << endl;
A myobj(10); //生成一个类对象
std::thread mytobj(&A::thread_work, myobj, 15);
mytobj.join();
return 0;
}
类对象做线程函数
#include <iostream>
#include <thread>
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(const 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;
}
void operator()(int num)
{
cout << "[子线程()执行]" << this << "threadid = " << std::this_thread::get_id() << endl;
}
};
int main()
{
cout << "主线程id是" << std::this_thread::get_id() << endl;
A myobj(10); //生成一个类对象
std::thread mytobj(myobj, 15);
mytobj.join();
return 0;
}
如果加std::ref就不会调用拷贝构造函数,但是此时要注意不要使用detach了