thread() 函数传参分析
文章目录
类对象(可调用对象的一中)作为thread函数的参数
代码
class Test
{
public:
const int &ti;
const int &tw;
Test(int &i, int &w) : ti(i), tw(w) { cout << "调用构造函数" << endl; };
Test(const Test &test) : ti(test.ti), tw(test.tw)
{
cout << "调用拷贝构造函数" << endl;
}
~Test()
{
cout << "调用析构函数" << endl;
}
void operator()()
{
cout << "func 子线程开始" << endl;
cout << "func 子线程处理任务............ " << endl;
for (int i = 0; i < 5; i++) // 处理5s
{
std::this_thread::sleep_for(std::chrono::seconds(1));
cout << "Test::ti " << ti << "Test::tw " << tw << endl;
}
cout << "func 子线程结束" << endl;
}
};
int main(int argc, char **argv)
{
cout << "主线程开始" << endl;
int i = 5;
int w = 2;
Test test(i, w);
thread t(test);
t.detach();
for (int i = 0; i < 3; i++) // 处理3s
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
cout << "主线程3s后结束" << endl;
打印结果
// 主线程开始
// 调用构造函数
// 调用拷贝构造函数
// func 子线程开始
// func 子线程处理任务............
// Test::ti 5Test::tw 2
// Test::ti 5Test::tw 2
// 主线程3s后结束
// 调用析构函数
// Test::ti 340Test::tw 1170016112// 主线程开始
分析
- 可以看到对象test 是被拷贝进线程函数的,所以这里的析构函数应该有两次,但 子线程 晚于于主线程结束,所以(拷贝对象的析构函数)没有输出出来*
- 子线程在 主线程结束之后 i 和w 输出是 乱的 , 因为 子线程 晚于于主线程结束, 主线程的 i 和 w 在 结束后,被销毁,*
- 子线程的函数引用了一段被释放的空间。 数据自然是乱的*
进一步处理代码
- 现在将主线程的 代码修改为 执行8s ,使主线程 晚于子线程执行。
for (int i = 0; i < 8; i++) // 处理3s
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
cout << "主线程8s后结束" << endl;
输出结果
主线程开始
调用构造函数
调用拷贝构造函数
func 子线程开始
func 子线程处理任务............
Test::ti 5Test::tw 2
Test::ti 5Test::tw 2
Test::ti 5Test::tw 2
Test::ti 5Test::tw 2
Test::ti 5Test::tw 2
func 子线程结束
调用析构函数
主线程8s后结束
调用析构函数
分析
- 可见调用了两次构造和析构; 且没有发生乱码现象
总结
-
总结,在主线程,子线程分离的代码中,子线程函数不要 使用 引用和 指针 传递 变量
-
thread () 对传入的对象进行的拷贝。
函数对象和其参数 作为thread函数的参数
代码
void myPrint(const int &li, const char *buf)
// void myPrint(const int &li, const string &buf)
{
cout << "func 子线程开始" << endl;
cout << "func 子线程处理任务............ " << endl;
for (int i = 0; i < 5; i++) // 处理5s
{
std::this_thread::sleep_for(std::chrono::seconds(1));
cout << " myPrint::li " << li << " myPrint::&li " << &li << " myPrint::buf " << buf << " myPrint::&str " << (int *)buf << endl;
}
cout << "func 子线程结束" << endl;
}
int main(int argc, char **argv)
{
cout << "主线程开始" << endl;
int ri = 100;
int &li = ri;
char *buf = "hello world";
cout << " myPrint::li " << li << " main::&li " << &li << endl;
cout << " myPrint::buf " << buf << " main::&buf " << (int *)buf << endl;
thread t(myPrint, li, buf);
t.detach();
for (int i = 0; i < 3; i++) // 处理3s
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
cout << "主线程3s后结束" << endl;
}
执行结果
// 主线程开始
myPrint::li 100 main::&li 0x228cdffa0c
myPrint::buf hello world main::&buf 0x7ff6315b60cc
func 子线程开始
func 子线程处理任务............
myPrint::li 100 myPrint::&li 0x13497b76170 myPrint::buf hello world myPrint::&str 0x7ff6315b60cc
myPrint::li 100 myPrint::&li 0x13497b76170 myPrint::buf hello world myPrint::&str 0x7ff6315b60cc
主线程3s后结束
myPrint::li 100 myPrint::&li 0x13497b76170 myPrint::buf hello world myPrint::&str 0x7ff6315b60cc
分析
- 可以看到 在子线程和 主线程 中 li 的 地址是不同的,而 buf 的地址是相同的, 这意味这在 主线程结束后, li 结果是正确的(因为不会引用到销毁的地址空间),而 buf 的值会出错(因为子线程晚于主线程结束,却引用着已经销毁的变量buf)
- 可见 std::thread 的入口函数中传递简单类型数据,如 int 等,本质上是值传递,可以放心的 detach(不加std::ref的情况下)
- std::thread 的入口函数中传递指针时,是引用传递,引用的是和main 线程中指针变量 同一块内存
演示出现的问题
- 解决1 将线程函数的 const char *buf 参数 换为 const string & buf ,这样就会生成临时对象的转换, 就不会引用到主线程的buf
- 但这种临时对象的转换是发生在子线程的,一旦主线程结束,char* buf被回收了, 就不能被转换为 string &buf了。
- 演示一下 临时对象的转换发生在子线程中(转步到 thread05.cpp)
隐式类型转换出现在子线程中
// TAG 演示临时对象的转换发生在子线程中
class A
{
public:
int m_i;
A(int i) : m_i(i)
{
this_thread::sleep_for(std::chrono::seconds(2)); // 让子线程后于主线程结束。 如果转换发生在 主线程,那主线程一定会打印这句话,才会结束掉
cout << "A::A(int i) : 构造函数被执行 " << endl;
}
A(const A &other) : m_i(other.m_i) { cout << "A::A(const A& other) 拷贝构造函数被执行" << endl; }
~A() { cout << "A::~A() 析构函数被执行" << endl; }
};
void func(const int i, const A &a)
{
cout << &a << endl;
return;
}
int main()
{
int i = 25;
int a = 100;
cout << "主线程开始" << endl;
cout << "子线程开始:" << endl;
thread t(func, i, a); // 希望将整型的 a 转换为 A 类型的a 对象
t.detach();
cout << "主程序结束" << endl;
}
// 输出
// 主线程开始
// 子线程开始:
// 主程序结束
// 可见主程序结束前并未打印出A的构造函数,说明 主程序并未发生临时对象的转换。
解决方法
- 可以在thread 函数数中, 使用 string() 强制转换,让临时对象的转换发生在 主线程中, 就能避免在子线程中转换的时候,buf被回收了。
// TAG 演示临时对象的转换发生在子线程中
class A
{
public:
int m_i;
A(int i) : m_i(i)
{
this_thread::sleep_for(std::chrono::seconds(2)); // 让子线程后于主线程结束。 如果转换发生在 主线程,那主线程一定会打印这句话,才会结束掉
cout << "A::A(int i) : 构造函数被执行 " << endl;
}
A(const A &other) : m_i(other.m_i) { cout << "A::A(const A& other) 拷贝构造函数被执行" << endl; }
~A() { cout << "A::~A() 析构函数被执行" << endl; }
};
void func(const int i, const A &a)
{
cout << &a << endl;
return;
}
int main()
{
int i = 25;
int a = 100;
cout << "主线程开始" << endl;
cout << "子线程开始:" << endl;
thread t(func, i, A(a)); // 希望将整型的 a 转换为 A 类型的a 对象,
// todo 使用 强制类型转换后, 就能使得类型转换在主线程中进行
t.detach();
cout << "主程序结束" << endl;
}
// 输出结果
// 主线程开始
// 子线程开始:
// A::A(int i) : 构造函数被执行
// A::A(const A& other) 拷贝构造函数被执行
// A::~A() 析构函数被执行
// 主程序结束