一、多线程
问题:C++ 线程detach之后 程序退出后子线程还会执行嘛在linux上和windows上?
答案:C++线程detach之后,主程序退出子线程也会退出,不会继续执行。
但是注意:如果你是对main线程使用了 (pthread_exit(NULL))C语言的线程退出函数,那么主程序退出后,子线程还是会执行的。
如果在子线程调用了pthread_exit会对子线程做退出操作,不会对主线程做退出操作。
默认的main函数返回的return 0 其实相当于调用了exit(0)代表程序的正常退出。
C++中的线程默认是以拷贝/移动构造的方式进行传递的,要想传递原始数据,使用std::ref进行包装。
常见的多线程使用实例
值传递
cpp
#include <thread>
#include <iostream>
void threadFunction(int value) {
std::cout << "Value in thread: " << value << std::endl;
}
int main() {
int myValue = 42;
std::thread t(threadFunction, myValue); // 值传递
t.join();
return 0;
}
地址传递
cpp
#include <thread>
#include <iostream>
void threadFunction(int* value) {
std::cout << "Value in thread: " << *value << std::endl;
*value = 24; // 修改原始数据
}
int main() {
int myValue = 42;
std::thread t(threadFunction, &myValue); // 地址传递
t.join();
std::cout << "Value in main: " << myValue << std::endl; // 输出修改后的值
return 0;
}
在这个例子中,myValue
的地址被传递给 threadFunction
,线程函数接收的是指向 myValue
的指针,并可以修改它。
注意事项
-
线程安全:如果你使用地址传递并允许线程修改数据,确保考虑线程安全问题,使用互斥锁或其他同步机制来避免数据竞争。
-
生命周期:确保传递给线程的参数在其生命周期内是有效的。对于值传递,这通常不是问题,因为数据被复制。但对于地址传递,如果数据在线程结束前被销毁,将会导致未定义行为。(本文重点讨论这个问题)
-
性能考虑:对于大型数据结构,值传递可能会因为复制而产生不必要的性能开销。在这种情况下,地址传递可能是更好的选择。
-
std::ref
和std::cref
:对于引用传递,你可以使用std::ref
来传递非常量引用,或使用std::cref
来传递常量引用。这允许你在线程函数中修改(或安全地观察)原始数据。
引用传递
cpp
#include <thread>
#include <functional>
#include <iostream>
void threadFunction(int& value) {
value = 24; // 修改原始数据
}
int main() {
int myValue = 42;
std::thread t(threadFunction, std::ref(myValue)); // 引用传递
t.join();
std::cout << "Value in main: " << myValue << std::endl; // 输出修改后的值
return 0;
}
在这个例子中,std::ref
用于传递 myValue
的引用,允许线程函数修改原始数据。
使用仿函数调用多线程程序
仿函数:就是一个类重载了operator() 的函数就称为仿函数。 先引入一个简单的例子来看下怎么使用
cpp
// ConsoleApplication10.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include <mutex>
#include <cstdint>
using namespace std;
class TA
{
public:
void operator()()
{
cout << "my functor start " << endl;
cout << "my functor end " << endl;
}
};
int main()
{
std::cout << "Hello World! " << sizeof(char*) << endl;
TA ta;
thread t(ta);
t.join();
cout << "============================================" << endl;
return 0;
}
使用detach要注意,如果子线程传递参数使用的是主线程中的引入变量,主线程退出 子线程会产生不可以预料的效果。
deatch调用后,主线程结束后,TA对象应该会销毁。还能调用TA对象嘛?
(对象不在了,实际上被复制到了线程中去) 线程执行完毕TA销毁,但是复制的TA对象还存在。
结论:thread 在创建时候,会调用拷贝构造函数(默认是先浅拷贝哦),如果这里我们使用join就会发现,会调用两次析构函数。
cpp
// ConsoleApplication10.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include <mutex>
#include <cstdint>
using namespace std;
class TA
{
public:
int& m_i;
TA(int& i) :m_i(i) // 列表初始化来初始化对象。
{
cout << "TA()构造函数被执行"<<this << endl;
}
TA(const TA& ta):m_i(ta.m_i)
{
cout << "TA拷贝构造函数" << endl;
}
~TA()
{
cout << "TA析构函数"<<this << endl;
}
void operator()()
{
//cout << "my functor start " << endl;
//cout << "my functor end " << endl;
for (int i = 0; i < 100; i++)
{
cout << "mi" << i<< "的值为:" << m_i << endl;
}
/*cout << "mi1的值为:" << m_i << endl;
cout << "mi2的值为:" << m_i << endl;
cout << "mi3的值为:" << m_i << endl;
cout << "mi4的值为:" << m_i << endl;*/
}
};
int main()
{
//std::cout << "Hello World! " << sizeof(char*) << endl;
int num = 10;
TA ta(num);
thread t(ta);
//deatch调用后,主线程结束后,TA对象应该会销毁。还能调用TA对象嘛?
// (对象不在了,实际上被复制到了线程中去) 线程执行完毕TA销毁,但是复制的TA对象还存在。
//t.detach(); //使用detach要注意,如果子线程传递参数使用的是主线程中的引入变量,主线程退出 子线程会产生不可以预料的效果。
t.join();
//std::this_thread::sleep_for(std::chrono::milliseconds(1));
cout << "============================================" << endl;
return 0;
}
使用lambda表达式调用多线程程序
cpp
// ConsoleApplication10.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include <mutex>
#include <cstdint>
using namespace std;
class TA
{
public:
int& m_i;
TA(int& i) :m_i(i) // 列表初始化来初始化对象。
{
cout << "TA()构造函数被执行"<<this << endl;
}
TA(const TA& ta):m_i(ta.m_i)
{
cout << "TA拷贝构造函数" << endl;
}
~TA()
{
cout << "TA析构函数"<<this << endl;
}
void operator()()
{
//cout << "my functor start " << endl;
//cout << "my functor end " << endl;
for (int i = 0; i < 100; i++)
{
cout << "mi" << i<< "的值为:" << m_i << endl;
}
/*cout << "mi1的值为:" << m_i << endl;
cout << "mi2的值为:" << m_i << endl;
cout << "mi3的值为:" << m_i << endl;
cout << "mi4的值为:" << m_i << endl;*/
}
};
int main()
{
auto mylambda = []() {
cout << "我的lambda线程开始执行了" << endl;
cout << "我的lambda线程执行结束了" << endl;
};
thread myobj(mylambda);
cout << "main 线程开始执行" << endl;
myobj.join();
cout << "============================================" << endl;
return 0;
}
传递临时对象作为线程参数
示例一
cpp
// ConsoleApplication10.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include <mutex>
#include <cstdint>
using namespace std;
void myprint( const int i, const string& ptr)
//void myprint(const int i, char* ptr)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
cout << i << "|"<<&i << endl;
cout << ptr.c_str() <<"|"<<&ptr << endl;
}
int main()
{
int mvar = 1;
int& myvary = mvar; //这里使用的是引用,所以mvar和myvary的地址是一样的。
char mybuf[] = "this is tesst";
cout << mvar << "|" << &mvar << endl;
cout << myvary << "|" << &myvary << endl;
cout << mybuf << "|" << &mybuf << endl;
// mvar和mubuf 两个都是不同地址传递。
thread myobj(myprint,mvar,mybuf); //这里虽然用引用了,但是内部实现没有用引用,是使用的值传递,地址是不一样的。
//要避免的陷阱,使用detach
myobj.join();
//myobj.detach();
cout << "=========================================== " << endl;
cout << "myvar = " << mvar << endl;
return 0;
}
示例二
cpp
// ConsoleApplication10.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include <mutex>
#include <cstdint>
using namespace std;
void myprint( const int i, const string& ptr)
//void myprint(const int i, char* ptr)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
cout << i << "|"<<&i << endl;
cout << ptr.c_str() <<"|"<<&ptr << endl;
}
int main()
{
int mvar = 1;
int& myvary = mvar; //这里使用的是引用,所以mvar和myvary的地址是一样的。
char mybuf[] = "this is tesst";
cout << mvar << "|" << &mvar << endl;
cout << myvary << "|" << &myvary << endl;
cout << mybuf << "|" << &mybuf << endl;
// 但是mybuf到底是在什么时候转为string的
//thread myobj(myprint,mvar,mybuf); //此时,如果程序执行完毕后,系统才将mybuf转为string,会有BUG
thread myobj(myprint,mvar,(string)mybuf); //这样提前转换为一个临时对象,会保证没有问题。
//要避免的陷阱,使用detach
myobj.join();
//myobj.detach();
cout << "=========================================== " << endl;
cout << "myvar = " << mvar << endl;
return 0;
}
求证示例二
cpp
// ConsoleApplication10.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include <mutex>
#include <cstdint>
using namespace std;
class TA
{
public:
int m_i;
TA(int i) :m_i(i) // 列表初始化来初始化对象。 explicit可以禁止隐式转换
{
cout << "TA()构造函数被执行" << this << endl;
}
TA(const TA& ta) :m_i(ta.m_i)
{
cout << "TA拷贝构造函数" <<this << endl;
}
~TA()
{
cout << "TA析构函数" << this << endl;
}
};
void myprint1( const int& i, const TA& ptr) //要用引用去接收,不然会拷贝构造两次对象
//void myprint(const int i, char* ptr)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
cout << "func = " << i << "|" << &i << endl;
cout <<"func = " << ptr.m_i << "|" << &ptr << endl;
}
void myprint( const int i, const string& ptr)
//void myprint(const int i, char* ptr)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
cout << i << "|"<<&i << endl;
cout << ptr.c_str() <<"|"<<&ptr << endl;
}
int main()
{
int mvar = 1;
int& myvary = mvar; //这里使用的是引用,所以mvar和myvary的地址是一样的。
int mysecondpar = 12;
cout << mvar << "|" << &mvar << endl;
cout << myvary << "|" << &myvary << endl;
cout << mysecondpar << "|" << &mysecondpar << endl;
//thread myobj(myprint1,mvar, mysecondpar);//我们希望将mysecondpar转为TA对象传递给myprint1第二个参数,使用了隐式转换。
//thread myobj(myprint1, mvar, (TA)mysecondpar);//我们希望将mysecondpar转为TA对象传递给myprint1第二个参数,使用了隐式转换。
thread myobj(myprint1, ( mvar) , TA(mysecondpar) );//我们希望将mysecondpar转为TA对象传递给myprint1第二个参数,使用了隐式转换。
//上述两种方式,方式一,在执行完毕后不会调用构造函数,证明main线程结束之后,才进行构造,导致子线程使用了析构完的变量会有问题
// 方式二,进行了临时构造,会先执行构造函数
// 方式三,会多一个拷贝构造
//要避免的陷阱,使用detach
myobj.join();
//myobj.detach();
cout << "=========================================== " << endl;
cout << mvar << endl;
return 0;
}
临时对象构造时机捕获
cpp
// ConsoleApplication10.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include <mutex>
#include <cstdint>
using namespace std;
class TA
{
public:
mutable int m_i; //mutable可以修改变量的值
TA(int i) :m_i(i) // 列表初始化来初始化对象。 explicit可以禁止隐式转换
{
cout << "TA()构造函数被执行" << this<< " threadid = "<< this_thread::get_id() << endl;
}
TA(const TA& ta) :m_i(ta.m_i)
{
cout << "TA拷贝构造函数" <<this << " threadid = " << this_thread::get_id() << endl;
}
TA(const TA&& ta) :m_i(ta.m_i)
{
//m_i = 0;
ta.m_i = -100;
cout << "TA移动构造函数" << this << " threadid = " << this_thread::get_id() << endl;
}
~TA()
{
cout << "TA析构函数" << this << " threadid = " << this_thread::get_id() << endl;
}
};
void myprint1( const int& i, const TA& ptr) //要用引用去接收,不然会拷贝构造两次对象,第二次的拷贝构造函数会在子线程中执行
//void myprint(const int i, char* ptr)
{
ptr.m_i += 199;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
cout << "func = " << i << "|" << &i << endl;
cout <<"func = " << ptr.m_i << "|" << &ptr << endl;
}
void myprint( const int i, const string& ptr)
//void myprint(const int i, char* ptr)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
cout << i << "|"<<&i << endl;
cout << ptr.c_str() <<"|"<<&ptr << endl;
}
int main()
{
int mvar = 1;
int& myvary = mvar; //这里使用的是引用,所以mvar和myvary的地址是一样的。
int mysecondpar = 12;
cout << mvar << "|" << &mvar << endl;
cout << myvary << "|" << &myvary << endl;
cout << mysecondpar << "|" << &mysecondpar << endl;
//thread myobj(myprint1,mvar, mysecondpar);//这种方式对象在子线程被构造出来了
thread myobj(myprint1, mvar, (TA)mysecondpar);//这样是在主线程被构造,拷贝构造的析构函数是在子线程
//thread myobj(myprint1,mvar , TA(mysecondpar) );//这样也是在主线程被构造
//
//
//上述两种方式,方式一,在执行完毕后不会调用构造函数,证明main线程结束之后,才进行构造,导致子线程使用了析构完的变量会有问题
// 方式二,进行了临时构造,会先执行构造函数
// 方式三,会多一个拷贝构造
//要避免的陷阱,使用detach
myobj.join();
//myobj.detach();
cout << "=========================================== " << " threadid = " << this_thread::get_id() << endl;
cout << "mysecondpar= "<< mysecondpar << endl;
return 0;
}
std::ref函数可以传递真正的数据
cpp
// ConsoleApplication10.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include <mutex>
#include <cstdint>
using namespace std;
class TA
{
public:
mutable int m_i; //mutable可以修改变量的值
TA(int i) :m_i(i) // 列表初始化来初始化对象。 explicit可以禁止隐式转换
{
cout << "TA()构造函数被执行" << this<< " threadid = "<< this_thread::get_id() << endl;
}
TA(const TA& ta) :m_i(ta.m_i)
{
cout << "TA拷贝构造函数" <<this << " threadid = " << this_thread::get_id() << endl;
}
TA(const TA&& ta) :m_i(ta.m_i)
{
//m_i = 0;
//ta.m_i = -100;
cout << "TA移动构造函数" << this << " threadid = " << this_thread::get_id() << endl;
}
~TA()
{
cout << "TA析构函数" << this << " threadid = " << this_thread::get_id() << endl;
}
};
void myprint1( const int& i, const TA& ptr) //要用引用去接收,不然会拷贝构造两次对象,第二次的拷贝构造函数会在子线程中执行
//void myprint(const int i, char* ptr)
{
ptr.m_i += 199;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
cout << "func = " << i << "|" << &i << endl;
cout <<"func = " << ptr.m_i << "|" << &ptr << endl;
}
void myprint( const int i, const string& ptr)
//void myprint(const int i, char* ptr)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
cout << i << "|"<<&i << endl;
cout << ptr.c_str() <<"|"<<&ptr << endl;
}
int main()
{
int mvar = 1;
cout << mvar << "|" << &mvar << endl;
TA obj(12);
//thread myobj(myprint1,mvar, obj);
thread myobj(myprint1, mvar, std::ref(obj) ); //使用ref之后可以看到是同一块内容
//
myobj.join();
//myobj.detach();
cout << "=========================================== " << " threadid = " << this_thread::get_id() << endl;
cout << "obj.m_i = "<< obj.m_i << endl;
return 0;
}
使用智能指针进行参数传递
cpp
// ConsoleApplication10.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include <mutex>
#include <cstdint>
using namespace std;
class TA
{
public:
mutable int m_i; //mutable可以修改变量的值
TA(int i) :m_i(i) // 列表初始化来初始化对象。 explicit可以禁止隐式转换
{
cout << "TA()构造函数被执行" << this<< " threadid = "<< this_thread::get_id() << endl;
}
TA(const TA& ta) :m_i(ta.m_i)
{
cout << "TA拷贝构造函数" <<this << " threadid = " << this_thread::get_id() << endl;
}
TA(const TA&& ta) :m_i(ta.m_i)
{
//m_i = 0;
//ta.m_i = -100;
cout << "TA移动构造函数" << this << " threadid = " << this_thread::get_id() << endl;
}
~TA()
{
cout << "TA析构函数" << this << " threadid = " << this_thread::get_id() << endl;
}
};
void myprint2(unique_ptr<int>ptr )
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
cout << "func = " << ptr << "|" << *ptr << endl;
cout <<"func = " << ptr.get() << "|" << *(ptr.get() ) << endl;
}
int main()
{
unique_ptr<int> myptr(new int(100));
//thread myobj(myprint2,myptr); //不能直接把智能指针传递过去
thread myobj(myprint2, std::move(myptr) ); //使用move传递,此时myptr已经空了
myobj.join();
//myobj.detach();
cout << "=========================================== " << " threadid = " << this_thread::get_id() << endl;
cout << "myptr = "<< myptr << endl;
return 0;
}
用成员函数指针做线程函数
cpp
// ConsoleApplication10.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include <mutex>
#include <cstdint>
using namespace std;
class TA
{
public:
mutable int m_i; //mutable可以修改变量的值
TA(int i) :m_i(i) // 列表初始化来初始化对象。 explicit可以禁止隐式转换
{
cout << "TA()构造函数被执行" << this<< " threadid = "<< this_thread::get_id() << endl;
}
TA(const TA& ta) :m_i(ta.m_i)
{
cout << "TA拷贝构造函数" <<this << " threadid = " << this_thread::get_id() << endl;
}
TA(const TA&& ta) :m_i(ta.m_i)
{
//m_i = 0;
//ta.m_i = -100;
cout << "TA移动构造函数" << this << " threadid = " << this_thread::get_id() << endl;
}
void thread_work(int num)
{
cout << num << "TA thread_work " << this << " threadid = " << this_thread::get_id() << endl;
}
void operator()(int num)
{
cout << num << "TA operator " << this << " threadid = " << this_thread::get_id() << endl;
}
~TA()
{
cout << "TA析构函数" << this << " threadid = " << this_thread::get_id() << endl;
}
};
void myprint2(unique_ptr<int>ptr )
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
cout << "func = " << ptr << "|" << *ptr << endl;
cout <<"func = " << ptr.get() << "|" << *(ptr.get() ) << endl;
}
int main()
{
TA ta(10);
//thread myobj(ta, 100); //函数地址 ,类对象,实际参数
thread myobj(&TA::thread_work, (ta), 15); //函数地址 ,类对象,实际参数
//thread myobj(&TA::thread_work, (&ta), 15); //等价于 &ta == ref(ta)
//thread myobj(&TA::thread_work,std::ref(ta),15); //等价于 &ta == ref(ta),因为属于this指针可以取地址
//myobj.join();
myobj.detach();
cout << "=========================================== " << " threadid = " << this_thread::get_id() << endl;
cout << "ta = " << ta.m_i << endl;
return 0;
}
回顾使用仿函数调用多线程
cpp
// ConsoleApplication10.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include <mutex>
#include <cstdint>
using namespace std;
class TA
{
public:
mutable int m_i; //mutable可以修改变量的值
TA(int i) :m_i(i) // 列表初始化来初始化对象。 explicit可以禁止隐式转换
{
cout << "TA()构造函数被执行" << this<< " threadid = "<< this_thread::get_id() << endl;
}
TA(const TA& ta) :m_i(ta.m_i)
{
cout << "TA拷贝构造函数" <<this << " threadid = " << this_thread::get_id() << endl;
}
TA(const TA&& ta) :m_i(ta.m_i)
{
//m_i = 0;
//ta.m_i = -100;
cout << "TA移动构造函数" << this << " threadid = " << this_thread::get_id() << endl;
}
void thread_work(int num)
{
cout << num << "TA thread_work " << this << " threadid = " << this_thread::get_id() << endl;
}
void operator()(int num)
{
cout << num << "TA operator " << this << " threadid = " << this_thread::get_id() << endl;
}
~TA()
{
cout << "TA析构函数" << this << " threadid = " << this_thread::get_id() << endl;
}
};
void myprint2(unique_ptr<int>ptr )
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
cout << "func = " << ptr << "|" << *ptr << endl;
cout <<"func = " << ptr.get() << "|" << *(ptr.get() ) << endl;
}
int main()
{
TA ta(10);
//thread myobj(ta, 100); //函数地址 ,类对象,实际参数
thread myobj(std::ref(ta), 100); //不会调用拷贝构造函数
//thread myobj(&ta, 100); //不可用,因为ta已经是一个地址了
//myobj.join();
myobj.detach();
cout << "=========================================== " << " threadid = " << this_thread::get_id() << endl;
cout << "ta = " << ta.m_i << endl;
return 0;
}