C++多线程入门(一)thread类介绍
一、thread类
1.class thread
class thread用于创建线程。
转自std::thread - cppreference.com
类 thread 表示单个执行线程。线程允许多个函数同时执行。
线程在构造关联的线程对象时立即开始执行(等待任何OS调度延迟),从提供给作为构造函数参数的顶层函数开始。顶层函数的返回值将被忽略,而且若它以抛异常终止,则调用 std::terminate。顶层函数可以通过 std::promise 或通过修改共享变量(可能需要同步,见 std::mutex 与 std::atomic )将其返回值或异常传递给调用方。
std::thread 对象也可能处于不表示任何线程的状态(默认构造、被移动、detach 或 join 后),并且执行线程可能与任何 thread 对象无关(detach 后)。
没有两个 std::thread 对象会表示同一执行线程;std::thread 不是可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) 的,尽管它可移动构造 (MoveConstructible) 且可移动赋值 (MoveAssignable) 。
下面是它最常用的构造函数:
1 | template< class F, class... Args > |
传入一个函数对象,后面的args代表调用这个函数所需要的参数。
下面写一个简单的程序:
1 |
|
如果我们去运行这个函数,会发现并没有输出Hello world!,而是产生了报错,如:terminate called without an active exception。这是因为线程还没有结束的情况下,主程序已经结束了,于是到整个程序结束时,线程还没有输出。如何解决这个问题?很简单,在调用线程后加一个join()成员函数即可,这时主函数就会等待线程结束后再运行,成功输出。
2.join()
1 | void join(); // C++11 |
附:
阻塞当前线程直至 *this 所标识的线程结束其执行。
此时,要有一个疑问:什么是线程?它和主进程的关系是什么?计算机最多可以开辟多少个线程?调用线程会产生什么开销?
这里我们暂不深入解释,留到以后章节细聊,目前先学会简单的使用线程即可。
带传入参数的函数使用thread的方法:
1 | std::thread func2([](int a, int b) |
func2接受两个参数,并输出两个参数的和。
附:
this 所标识的线程的完成同步于*对应的从join()成功返回。
*this 自身上不进行同步。同时从多个线程在同一thread对象上调用join()构成数据竞争,导致未定义行为。
3.detach()
detach()成员函数可以分离线程,使线程在后台运行,主线程不再等待它执行完毕,且不会产生报错。
1 | std::thread func3([]() |
观察到程序“正常”结束,没有报错,且没有输出"Hello, world3!。
附:
从 thread 对象分离执行线程,允许它独立地持续执行。当该线程退出时将释放其分配的任何资源。
调用 detach 后 *this 不再占有任何线程。
4.joinable()
如果一个线程被同时join多次,会产生错误,所以需要一个判断函数来检测该线程是否是活跃的,从而决定它是否可以被join:
1 | std::thread func4([]() |
同时对detach()也适用,可以被detach的线程一定是joinable的,否则会报错std::system_error。
附:
检查 std::jthread 对象是否标识活跃的执行线程。具体而言,若 get_id() != std::jthread::id() 则返回 true。故默认构造的 jthread 不可合并。
结束执行代码,但仍未合并的线程仍被当作活跃的执行线程,从而是可合并的。
二、jthread类
在C++20后,加入了可以自动合并和取消的jthread类:
1 | template< class F, class... Args > |
使用方法和thread一样,并且无需在手动调用join()函数。
附:
类 jthread 表示单个执行线程。它的一般行为和 std::thread 相同,除了 jthread 在析构时会自动再合并,而且能在特定情况下取消/停止。
线程在关联的线程对象被构造时(在任何操作系统调度延迟后),立即从作为构造函数实参提供的顶层函数开始执行。顶层函数的返回值将被忽略,而若它因抛异常退出,则调用 std::terminate。顶层函数可经由 std::promise,或通过修改共享变量(要求同步,见 std::mutex 与 std::atomic),向调用方交流其返回值或异常。
不同于 std::thread,jthread 逻辑上保有一个内部的 std::stop_source 类型私有成员,它维持共享的停止状态。jthread 的构造函数接受一个以 std::stop_token 为其首个实参的函数,jthread 将从其内部的 std::stop_source 传递它。这允许函数在其执行中检查是否已请求停止,而若已请求则返回。
std::jthread 对象亦可处于不表示任何线程的状态(在默认构造、被移动、detach 或 join 后),而执行线程可以不与任何 jthread 对象关联(detach 后)。
两个 std::jthread 对象不可以表示同一执行线程;std::jthread 非可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) ,尽管它为可移动构造 (MoveConstructible) 及可移动赋值 (MoveAssignable) 。
三、本小节最后,我们实现一个“大名鼎鼎”的睡眠排序法:
1 |
|

