结论
给thread传递【基本类型数据】int... 建议值传递
给thread传递【临时类对象】,要避免隐式转换,应该在创建thread那一行传入临时对象(TestClazz(100)),线程函数用 const引用接收,避免主线程退出对内存的非法使用
给thread传递【引用的类对象】,必须使用std::ref,线程函数用const引用或非const引用接收,
detach
thread调用detach后,子线程与主线程分离,子线程传入参数要避免主线程提前退出导致对内存的非法使用;以及注意传入的临时对象的构造时机(如果传入临时对象); 使用ref时要注意
std::ref 不使用ref传参时,不论线程函数是const引用接收还是非引用接收,编译器默认拷贝接收(一定给你创建一个新的对象)
测试和结果
TestClazz.h
#include<iostream>
#include<thread>
#include<mutex>
#define COUT(_MESSAGE_) std::cout << _MESSAGE_ << std::endl
#define THIS_TD_COUT(_MESSAGE_) std::cout << "线程id: " << std::this_thread::get_id() << " " << _MESSAGE_ << std::endl
#define OBJECT_COUT(_MESSAGE_) std::cout << "对象地址: " << this << " ",THIS_TD_COUT(_MESSAGE_)
class TestClazz
{
public:
TestClazz() { OBJECT_COUT("调用TestClazz构造"); }
TestClazz(int _val) :val(_val) { OBJECT_COUT("调用TestClazz构造"); }
~TestClazz() { OBJECT_COUT("调用TestClazz析构"); }
TestClazz(const TestClazz& t) { OBJECT_COUT("调用TestClazz拷贝构造"); }
TestClazz(TestClazz&& t) { OBJECT_COUT("调用TestClazz移动构造"); }
TestClazz& operator=(const TestClazz& t) { OBJECT_COUT("调用TestClazz赋值运算符"); }
TestClazz& operator=(TestClazz&& t) { OBJECT_COUT("调用TestClazz移动赋值运算符"); }
public:
void operator()(int n) { OBJECT_COUT("调用TestClazz仿函数"); }
void fun(int a) { OBJECT_COUT("调用TestClazz成员函数"); }
void setValue(int _val) { val = _val; }
int getValue() { return val; }
private:
int val = 0;
};
main.cpp
int main()
{
THIS_TD_COUT("主线程id");
test1();
//test2();
//test3();
//test4();
//test5();
//test6();
//test7();
//test8();
return 0;
}
普通函数
void fun1(const TestClazz& t) test1
void fun1(const TestClazz& t) { THIS_TD_COUT("调用普通函数fun1"); }
void test1() {
int a = 1;
TestClazz t(a);
COUT("---td1---");
std::thread td1(fun1, TestClazz(a)); td1.join(); // 传临时对象推荐写法 保证a在主线程构造临时对象 不怕detach
COUT("---td1---");
COUT("---td2---");
std::thread td2(fun1, a); td2.join(); // 在子线程完成 线程函数参数 临时对象的创建,使用detach时可能导致未定义事件 //隐式类型转换 int -> TestClazz //
COUT("---td2---");
COUT("---td3---");
std::thread td3(fun1, t); td3.join(); // 主线程完成 线程函数参数 临时对象的创建 不怕detach
COUT("---td3---");
COUT("---td4---");
std::thread td4(fun1, std::ref(t)); td4.join(); // 小心detach
COUT("---td4---");
}
td1、td2、td3都是传临时对象
td1、td3在主线程完成所需参数对象的构造,即使主线程提前结束也不会导致未定义行为
td2在子线程完成所需参数对象的构造,使用detach时主线程提前结束可能导致使用释放的内存进行参数对象的构造(反正大概那么个意思,就是未定义行为)
td4使用std::ref进行引用传参,使用detach主线程提前结束可能导致未定义行为
detach测试
总之用detach要考虑内存提前释放导致的非法访问的未定义事件,要么直接不用也行
void fun2(TestClazz& t) test2
void fun2(TestClazz& t) { THIS_TD_COUT("调用普通函数fun2"); }
void test2() {
int a = 1;
TestClazz t(a);
COUT("---td4---");
std::thread td4(fun2, std::ref(t)); td4.join(); // 小心detach
COUT("---td4---");
}
void fun3(TestClazz t) test3
void fun3(TestClazz t) { THIS_TD_COUT("调用普通函数fun3"); }
void test3() {
int a = 1;
TestClazz t(a);
COUT("---td1---");
std::thread td1(fun3, TestClazz(a)); td1.join();
COUT("---td1---");
COUT("---td2---");
std::thread td2(fun3, a); td2.join();
COUT("---td2---");
COUT("---td3---");
std::thread td3(fun3, t); td3.join();
COUT("---td3---");
COUT("---td4---");
std::thread td4(fun3, std::ref(t)); td4.join();
COUT("---td4---");
}
test3和test1比较,值传递会多一层拷贝
void fun4(std::unique_ptr<TestClazz> ptr) test4
void fun4(std::unique_ptr<TestClazz> ptr) { THIS_TD_COUT("调用普通函数fun4"); }
void test4() {
int a = 1;
auto ptr = std::make_unique<TestClazz>(a);
COUT("---td5---");
std::thread td5(fun4, std::move(ptr)); td5.join(); // 使用detach时可能导致在move之前 ptr超出作用域被释放,导致未定义事件
COUT("---td5---");
}
void fun5(std::shared_ptr<TestClazz> ptr) test5
void fun5(std::shared_ptr<TestClazz> ptr) { THIS_TD_COUT("调用普通函数fun5"); }
void test5() {
int a = 1;
auto ptr = std::make_shared<TestClazz>(a);
COUT("---td5---");
std::thread td5(fun5, ptr); td5.join(); // 使用detach时可能导致在move之前 ptr超出作用域被释放,导致未定义事件
COUT("---td5---");
}
类public成员函数
private成员函数作为线程函数,可以用友元函数友元一下
test6
void test6() {
TestClazz t(99);
COUT("---td1---");
std::thread td1(&TestClazz::fun, t, 1); td1.join(); // 调用拷贝 传入一个临时对象
COUT("---td1---");
COUT("---td2---");
std::thread td2(&TestClazz::fun, &t, 1); td2.join(); // 小心detach
COUT("---td2---");
COUT("---td3---");
std::thread td3(&TestClazz::fun, std::ref(t), 1); td3.join(); // 小心detach
COUT("---td3---");
}
仿函数对象
test7
void test7() {
TestClazz t(99);
COUT("---td1---");
std::thread td1(t, 1); td1.join(); // 调用拷贝 传入一个临时对象
COUT("---td1---");
COUT("---td3---");
std::thread td3(std::ref(t), 1); td3.join(); // 小心detach
COUT("---td3---");
}
lambda表达式
test8
void test8() {
COUT("---td1---");
std::thread td1([](int a) {COUT("调用lambda表达式"); }, 1); td1.join();
COUT("---td1---");
}