文章目录
一、线程启动、结束、创建线程方法
1. 创建线程的一般方法
-
说明
- 主线程在从main开始执行,一旦主线程从main()返回,则整个程序(进程)结束
- 如果主线程结束了,其他子线程还没执行完,一般会被操作系统强行终止(除非子线程设置为
detach
) - 通常我们创建的子线程从一个函数开始运行,一旦此函数运行完毕,代表这个线程运行结束
- 如果想保持子线程一直运行,不要让主线程运行完毕(除非子线程设置为
detach
)
-
创建子线程的一般方法
- 包含头文件:
#include <thread>
- 编写一个子线程开始执行的函数(初始函数)
- 使用
thread()
创建子线程 - 设置主线程和子线程的关系,
join()
或detach()
- 包含头文件:
(1)thread()
1.先看一个示例,这里子线程myprint
和主线程main
并发运行
#include <iostream>
#include <thread>
using namespace std;
//子线程的初始函数
void myprint()
{
cout << "我的线程开始执行" << endl;
cout << "我的线程执行完毕" << endl;
}
//主线程在从main开始执行,一旦主线程从main()返回,则整个程序结束
int main()
{
//创建子线程(这两行在main函数里)
thread mytobj(myprint); //创建了线程,执行起点是myprint,同时让子线程开始执行
mytobj.join(); //主线程阻塞在这里,等子线程执行完
cout << "Hello World!\n";
return 0;
}
/*
-----运行结果--------
我的线程开始执行
我的线程执行完毕
Hello World!\n
*/
- 关于thread
thread
是一个类thread mytobj(myprint);
是利用构造函数创建了thread
对象,传入参数是一个可调用对象- 这行代码做了两件事
- 创建了线程,执行起点是
myprint
函数入口 myprint
线程开始运行
- 创建了线程,执行起点是
- C++11 functional对函数指针做了拓展,可调用对象包括函数指针和函数对象等
(2)join()
-
join
是thread
类中的一个方法 -
作用:阻塞主线程,让主线程等待子线程执行完毕,然后子线程和主线程汇合,再往下执行
-
对上面的程序进行逐语句debug,在8和17行打断点,单步执行顺序:17-8-18-9-20,可以看出进程的并发效果
-
如果把上面的
mytobj.join();
注释掉,运行时会看到输出混乱,而且弹出异常提示- 由于主线程和子线程交替执行,所以打印混乱
- 由于子线程没有结束主线程就结束了,子线程被操作系统强制结束,所以报异常
-
一旦把线程
join
了,就不能再detach
了(否则报异常),我们自己来控制子进程 -
设计良好的程序
- 主线程应等待子线程执行完毕后,进行一些收尾工作,然后才能推出
- 并发应在子线程之间发生,主线程用来控制子线程和执行收尾工作
(3)detach()
-
传统多线程程序,主线程要等待所有子线程执行完再退出,但C++11增加了
detach()
,可以不这样干了 -
detach
是thread
类中的一个方法 -
作用:一旦detach之后,与这个主线程关联的对象就会失去和主线程的关联,此时这个子线程就会驻留系统在后台运行(即主线程不必再和子线程汇合了,主线程和子线程的执行独立开来)
主线程可以不等子线程结束就先结束子线程不会被强制结束,而是可以继续运行,C++运行时库接管其运行,并在子线程执行完后为其清理资源(类似linux
中的 “守护线程”)- 订正 2023/7/7:关于 detach 后主线程和子线程关系,请参考:detach,主线程终止后子线程会结束吗
-
一旦把线程
detach
了,就不能再join
回来了(否则报异常),我们失去了对这个进程的控制 -
改写一下程序
#include <iostream>
#include <thread>
using namespace std;
//子线程的初始函数
void myprint()
{
cout << "子线程开始执行" << endl;
cout << "子线程执行中_1" << endl;
cout << "子线程执行中_2" << endl;
cout << "子线程执行中_3" << endl;
cout << "子线程执行中_4" << endl;
cout << "子线程执行完毕" << endl;
}
//主线程在从main开始执行,一旦主线程从main()返回,则整个程序结束
int main()
{
thread mytobj(myprint); //创建了线程,执行起点是myprint,同时让子线程开始执行
mytobj.detach();
cout << "main thread is running_1" << endl;
cout << "main thread is running_2" << endl;
cout << "main thread is running_3" << endl;
cout << "main thread is running_4" << endl;
cout << "main thread is running_5" << endl;
cout << "main thread is running_6" << endl;
cout << "main thread is running_7" << endl;
cout << "main thread is running_8" << endl;
cout << "main thread is running_9" << endl;
cout << "main thread is running_10" << endl;
return 0;
}
/*-------------------运行结果---------------------
子线程开始执行main thread is running_1
子线程执行中_1
main thread is running_2
子线程执行中_2
子线程执行中_3
子线程执行中_4
子线程执行完毕
main thread is running_3
main thread is running_4
main thread is running_5
main thread is running_6
main thread is running_7
main thread is running_8
main thread is running_9
main thread is running_10
运行结果每次都不太一样,如果主线程先结束,进程结束,可能部分子线程的输出看不到(子进程还在后台执行,只是窗口关了看不到)
*/
- 这种方式不推荐用
(4)joinable()
joinable
是thread
类中的一个方法- 作用:用来判断线程是否可以成功使用
join
和detch
,返回true
或false
- 在
join
和detach
调用之前,返回true
- 在
join
和detach
调用之前后,返回false
- 在
- 如果在
joinable
返回false
时调用join
或detch
,会报错
2. 其他创建线程的手法
(1)用类
-
前面说过:
thread mytobj(myprint);
是利用构造函数创建了thread
对象,传入参数是一个可调用对象,可调用对象也可以是类,这个类要重载()
,详见仿函数 -
示例代码1
class TA
{
public:
void operator()() //运算符重载,不能带参数(这里是子线程入口)
{
cout<<"子线程operator()启动"<<endl;
cout<<"子线程operator()结束"<<endl;
}
};
//在主函数中
TA ta;
thread mytobj(ta);
mytobj.join();
-
根据先前关于
detach
的分析,假设这个类中引用了主线程的变量,而且子线程创建后调用detach()
了,如果主线程先执行完,这个被引用的变量就会被回收,而此时子线程(没执行完)仍在引用这块内存空间,会导致不可预料的结果 -
但是有一个问题:如果主线程调用了
detach
了,假设主线程先结束,那ta
对象还在吗,如果ta
不再了,子线程是不是也被销毁了?-
实际上,
ta
对象确实被回收了,但是子线程还在 -
这是由于
thread mytobj(ta);
这句实际上创建了一个ta对象的副本放到子线程去了(ta对象被复制到子线程中去,调用拷贝构造函数)。虽然主线程的ta对象被销毁,但子线程的ta对象副本还在 -
所以:只要这个TA类对象中没有引用/指针,
detach
就不会出问题
-
- 示例代码2
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
class A
{
public:
int m_i;
A(int a) :m_i(a) { cout << "构造函数执行" << this << "thread id:" << std::this_thread::get_id() << endl; }
A(const A &a) :m_i(a.m_i) { cout << "拷贝构造函数执行" << this << "thread id:" << std::this_thread::get_id() << endl;}
~A() { cout << "析构函数执行" << this << "thread id:" << std::this_thread::get_id() << endl;}
//子线程入口
void operator()(int num)
{
cout << "子线程thread_work执行" << this << "thread id:" << std::this_thread::get_id() << endl;
}
};
int main()
{
A myobj(10);
//注意这个构造函数的调用方法
std::thread mytobj(myobj, 15);
mytobj.join();
return 0;
}
-
注意
std::thread mytobj(myobj, 15);
这样构造子线程,子线程是基于myobj
的对象副本的,放心用detach
std::thread mytobj(std::ref(myobj), 15);
这样构造子线程,子线程是基于myobj
自身的,用detach
可能出错,不过如果要在子线程修改myobj
对象的值,则必须这样写,此时应用join
方式- 这个构造函数没有用
&
传引用的形式
-
对比下面
用成员函数指针作为线程函数
的示例代码一起看
(2)用成员函数指针作为线程函数
- 示例程序
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
class A
{
public:
int m_i;
A(int a) :m_i(a) { cout << "构造函数执行" << this << "thread id:" << std::this_thread::get_id() << endl; }
A(const A &a) :m_i(a.m_i) { cout << "拷贝构造函数执行" << this << "thread id:" << std::this_thread::get_id() << endl;}
~A() { cout << "析构函数执行" << this << "thread id:" << std::this_thread::get_id() << endl;}
//子线程入口
void thread_work(int num)
{
cout << "子线程thread_work执行" << this << "thread id:" << std::this_thread::get_id() << endl;
}
};
int main()
{
A myobj(10);
//注意这个构造函数的调用方法
std::thread mytobj(&A::thread_work, myobj, 15); //类成员函数指针,类对象,子线程参数
mytobj.join();
return 0;
}
/*运行结果
构造函数执行 005BF7B8 thread id:8160
拷贝构造函数执行 00B6E89C thread id:8160
子线程thread_work执行 00B6E89C thread id:27864
析构函数执行 00B6E89C thread id:27864
析构函数执行 005BF7B8 thread id:8160
*/
-
从运行结果可以看出,在进入
thread_work
子线程前,在主线程中发生了一次拷贝构造,因此子线程是基于myobj
对象的副本的,可以放心使用detach
-
如果
std::thread mytobj(&A::thread_work, myobj, 15);
这句改成这样:std::thread mytobj(&A::thread_work, std::ref(myobj), 15);
std::thread mytobj(&A::thread_work, &myobj, 15);
(传引用)
运行结果会变成下面这样,可见这时子线程是基于
myobj
对象自身了,此时用detach
可能会出问题,不过如果要在子线程修改myobj
对象的值,则必须这样写,此时应用join
方式
/*运行结果
构造函数执行 005BF7B8 thread id:8160
子线程thread_work执行 00B6E89C thread id:27864
析构函数执行 005BF7B8 thread id:8160
*/
(3)用lambda表达式
- 示例
auto mylamthread = []{ //这里是子线程入口
cout<<"子线程mylambda启动"<<endl;
cout<<"子线程mylambda结束"<<endl;
}
//在主函数中
thread mytobj(mylamthread);
mytobj.join();