参考:https://www.runoob.com/cplusplus/cpp-tutorial.html
本教程旨在提取最精炼、实用的C++面试知识点,供读者快速学习及本人查阅复习所用。
目录
第九章 多线程
多线程是多任务处理的一种特殊形式,一般情况下,有基于进程和基于线程的两种类型的多任务处理方式。
- 基于进程的多任务处理是程序的并发执行。
- 基于线程的多任务处理是同一程序的片段的并发执行。
9.1 基本概念
9.1.1 进程与线程
进程是资源分配和调度的一个独立单位;而线程是进程的一个实体,是CPU调度和分配的基本单位。
同一个进程中的多个线程的内存资源是共享的,各线程都可以改变进程中的变量。因此在执行多线程运算的时候要注意执行顺序。
9.1.2 并行与并发
并行(parallellism)指的是多个任务在同一时刻同时在执行。
并发(concurrency)是指在一个时间段内,多个任务交替进行。虽然看起来像在同时执行,但其实是交替的。
9.2 C++线程管理
- C++11的标准库中提供了多线程库,使用时需要#include <thread>头文件,该头文件主要包含了对线程的管理类std::thread以及其他管理线程相关的类。
- 每个应用程序至少有一个进程,而每个进程至少有一个主线程,除了主线程外,在一个进程中还可以创建多个子线程。每个线程都需要一个入口函数,入口函数返回退出,该线程也会退出,主线程就是以main函数作为入口函数的线程。
9.2.1 启动线程
std::thread的构造函数需要的是可调用(callable)类型,除了函数外,还可以调用例如:lambda表达式、重载了()运算符的类的实例。
#include <iostream>
#include <thread>
using namespace std;
void output(int i)
{
cout << i << endl;
}
int main()
{
for (uint8_t i = 0; i < 4; i++)
{
//创建一个线程t,第一个参数为调用的函数,第二个参数为传递的参数
thread t(output, i);
//表示允许该线程在后台运行
t.detach();
}
return 0;
}
在多线程并行的条件下,其输出结果不一定是顺序呢的输出1234,可能如下:
注意:
- 把函数对象传入std::thread时,应传入函数名称(命名变量,如:output)而不加括号(临时变量,如:output())。
- 当启动一个线程后,一定要在该线程thread销毁前,调用t.join()或者t.detach(),确定以何种方式等待线程执行结束:
- detach方式,启动的线程自主在后台运行,当前的代码继续往下执行,不等待新线程结束。
- join方式,等待关联的线程完成,才会继续执行join()后的代码。
- 在以detach的方式执行线程时,要将线程访问的局部数据复制到线程的空间(使用按值传递),一定要确保线程没有使用局部变量的引用或者指针,除非你能肯定该线程会在局部作用域结束前执行结束。
9.2.2 向线程传递参数
向线程调用的函数只需要在构造thread的实例时,依次传入即可。
thread t(output, arg1, arg2, arg3, ...);
9.2.3 调用类成员函数
class foo
{
public:
void bar1(int n)
{
cout<<"n = "<<n<<endl;
}
static void bar2(int n)
{
cout<<"static function is running"<<endl;
cout<<"n = "<<n<<endl;
}
};
int main()
{
foo f;
thread t1(&foo::bar1, &f, 5); //注意在调用非静态类成员函数时,需要加上实例变量。
t1.join();
thread t2(&foo::bar2, 4);
t2.join();
}
9.2.4 转移线程的所有权
thread是可移动的(movable)的,但不可复制的(copyable)。可以通过move来改变线程的所有权,灵活的决定线程在什么时候join或者detach。
thread t1(f1);
thread t3(move(t1));
将线程从t1转移给t3,这时候t1就不再拥有线程的所有权,调用t1.join或t1.detach会出现异常,要使用t3来管理线程。这也就意味着thread可以作为函数的返回类型,或者作为参数传递给函数,能够更为方便的管理线程。
9.2.5 线程标识的获取
线程的标识类型为std::thread::id,有两种方式获得到线程的id:
- 通过thread的实例调用get_id()直接获取;
- 在当前线程上调用this_thread::get_id()获取。
9.2.6 线程暂停
如果让线程从外部暂停会引发很多并发问题,这也是为什么std::thread没有直接提供pause函数的原因。如果线程在运行过程中,确实需要停顿,就可以用this_thread::sleep_for。
void threadCaller()
{
this_thread::sleep_for(chrono::seconds(3)); //此处线程停顿3秒。
cout<<"thread pause for 3 seconds"<<endl;
}
int main()
{
thread t(threadCaller);
t.join();
}
9.2.7 异常情况下等待线程完成
为了避免主线程出现异常时将子线程终结,就要保证子线程在函数退出前完成,即在函数退出前调用join()。
方法一:异常捕获
void func() {
thread t([]{
cout << "hello C++ 11" << endl;
});
try
{
do_something_else();
}
catch (...)
{
t.join();
throw;
}
t.join();
}
方法二:资源获取即初始化(RAII)
class thread_guard
{
private:
thread &t;
public:
/*加入explicit防止隐式转换,explicit仅可加在带一个参数的构造方法上,如:Demo test; test = 12.2;
这样的调用就相当于把12.