一、编译环境准备
在正式进入c/c++多线程编程系列之前,先来搭建支持多线程编译的编译环境。
1.1 MinGW(win)
进入Downloads - MinGW-w64下载页面,选择MinGW-w64-builds跳转下载,
再次进行跳转:
然后进入下载页面(WinLibs - GCC+MinGW-w64 compiler for Windows),下拉到Download标题下面,按需下载,本文选择的是编译好的WIN工具包
本文下载的如下:
将该工具包解压到无汉字、特殊字符的路径上,例如本文:
进入环境变量配置页面,在path环境变量中,添加mingw路径:
运行命令工具,测试gcc -v和g++ -v是否生效,例如下图
1.2 Cygwin(win)
同样还是(Downloads - MinGW-w64)下载页面,选择Cygwin选项跳转:
选择setup.exe安装工具包,跳转,
进入(Cygwin Installation)页面,下载
下载完成后,得到直接安装的.exe文件
双击安装,按安装说明引导,路径设置最好不包含汉字和特殊符号,本文安装如下:
安装完成后,同样可以去配置环境变量,本文是先设置了变量,在引入path环境变量中的。
如果没有创建Cygwin的桌面快捷方式,
可以自行进入路径手动创建该快捷方式。
双击快捷方式启动进入一个仿linux系统运行的win编译工具命令窗口,通过gcc -v或g++ -v测试是否支持c/c++编译.
那么windows的磁盘文件就在"/cygdrive"目录下:
另外可以右键窗口选择options项进入窗口设置,例如窗口大小、字体等,保存后重启生效。
1.3、纯粹的linux环境
本文是采用VMware® Workstation 15 Pro+安装centos7桌面版系统来实现的,具体安装请参考其他博文资料,然后运行gcc -v或g++ -v测试编译支持:
另外其他一些linux系统本人也测试了一下,都支持到c++11以上,但是部分可能支持到新的c++20、c++23标准会较麻烦
1.4 先睹为快的c++11多线程编程示例
建立test.h、test.cpp、main.cpp三个文件,内容如下:
test.h
#ifndef _THREAD_TEST_H_
#define _THREAD_TEST_H_
void func2(void);
#endif //_THREAD_TEST_H_
test.cpp
#include "test.h"
#include <iostream>
#include <thread>
void fun1(void)
{
std::cout << "A new thread!" << std::endl;
};
void func2(void)
{
std::thread t(fun1);
t.join();
std::cout << "Main thread!" << std::endl;
};
main.cpp
#include "test.h"
int main(int argc, char* argv[])
{
func2();
return 0;
}
编译g++ main.cpp test.cpp -o test.exe -pthread -std=c++11,注意因为程序输出名一致,那个编译完就那个直接运行,否则其他就会覆盖它。各个工具对c++11的支持后,win/linux对于多线程的编程就一致了,不像c++11之前那样跨平台编译c++的多线程需要做一堆平台适应性编码。
二、c/c++并发编程支持
简单阐述一下c++基于多线程并发编程的概念。所谓并发编程是指在一台处理器上“同时”处理多个任务。C/C++并发编程主要是程序在进程内通过多线程pthrea或thread(c++11)同时并发处理多任务。
2.1 进程与线程
进程和线程是计算机编程领域的两个重要概念。进程可以看作是正在运行的程序的一个实例。它拥有自己的地址空间、文件句柄、系统资源等,是一个独立的执行环境。而线程则是进程内的一个执行单元,它共享进程的地址空间和系统资源,但拥有自己的栈和程序计数器。
进程和线程之间有着紧密的关系。首先,一个进程可以包含多个线程,这些线程共享进程的资源,可以并发地执行不同的任务。同时,线程的创建、销毁、调度等都是由进程控制的,进程可以为不同的线程分配不同的资源,如CPU时间、优先级等。
进程和线程的关系还体现在多核CPU上。多核CPU通常具有多个处理器核心,这使得一个进程可以同时在多个核心上运行多个线程,从而充分利用系统资源,提高程序的执行效率。
2.2 c/c++多线程的并发编程
并发编程是指在同一时间内执行多个独立任务的能力。常见的并行编程有多种模型,如共享内存、多线程、消息传递等。不过从实用性上讲,多线程模型往往具有较大的优势。多线程编程就是在同一进程中同时运行多个线程以达到并发执行的目的。
在c/c++中,使用线程库来创建和管理线程。多线程模型允许同一时间有多个处理器单元执行统一进程中的代码部分,而通过分离的栈空间和共享的数据区及堆栈空间,线程可以拥有独立的执行状态以及进行快速的数据共享。
在多线程编程中,需要考虑许多问题,包括线程同步、竞争条件和死锁等问题。为了避免这些问题,可以使用锁和条件变量来同步线程,并使用互斥锁来避免竞争条件。另外,使用信号量和读写锁也可以提高线程的效率和可靠性。
多线程编程可以提高程序的性能和响应速度,但也需要小心处理,以避免一些常见的问题,例如死锁和竞争条件。c/c++多线程编程在编写多线程应用程序时,需要仔细考虑各种因素,包括线程同步、竞争条件和死锁等问题。
2.3 c++11以前的多线程支持
在C++11之前,C/C++一直是一种顺序的编程语言。顺序是指所有指令都是串行执行的,即在相同的时刻,有且仅有单个CPU的程序计数器指向可执行代码的代码段,并运行代码段中的指令。而C/C++代码也总是对应地拥有一份操作系统赋予进程的包括堆、栈、可执行的(代码)及不可执行的(数据)在内的各种内存区域。
不过随着处理器的发展,多核处理器的发展崛起。因此在2000年以后,主流的芯片厂商以及编译器开发厂商或组织都开始推广适用于多核处理器的多线程编程模型。而编程语言,也逐渐地将线程模型纳入语言特性或者语言库中。相应地,各个编程语言也逐渐也开始向并行化的编程方式发展。以顺序执行编程模型为基础的单核处理器的 c/c++语言,直到c++11标准前,也无集成于C/C++语言特性中的线程特性或者线程库。
在C++11之前,在C/C++中程序中主要使用POSLX 线程(pthread)和OpenMP编译器指令两种编程模型来完成程序的线程化。其中,POSLX 线程是POSIX标准中关于线程的部分,程序员可以通过一些pthread线程的API来完成线程的创建、数据的共享、同步等功能。pthread主要用于C语言,在类UNIX系统上,如FreeBSD、NetBSD、OpenBSD、GNU/Linux、Mac OS X,甚至 是Windows上都有实现(Windows上pthread的实现并非“原生”,主要还是包装为Windows的线程库)。不过在使用的便利性上,pthread不如后来者OpenMP。OpenMP的编译器指令将大部分的线程化的工作交给了编译器完成,而将识别需要线程化的区域的工作交给了程序员,这样的使用方式非常简单,也易于推广。因此,OpenMP得到了业界大多数主流软硬件厂商,如 AMD、IBM、Intel、Cray、HP、Fujitsu、Nvidia、NEC、Microsoft、Texas Instruments、Oracle Corporation 等的支持。除去C/C++语言外,OpenMP还可以用于Fortran语言,是现行的一种非常有影响力的使用线程程序优化的编程模型。
来看下c++11前调用的pthread.h实现跨线程事务例子:
创建test1.h、test1.cpp源文件,创建两个线程,通过函数指针加载两个事务(即函数)对一个原型类型进行累加操作,为了线程安全使用互斥锁:
test1.h
#ifndef _TEST_1_H_
#define _TEST_1_H_
void func3(void);
#endif //_TEST_1_H_
test1.cpp
#include "test1.h"
#include <pthread.h>
#include <iostream>
using namespace std;
static long long total = 0;
pthread_mutex_t mutex_ = PTHREAD_MUTEX_INITIALIZER;
void* func(void *)
{
long long i;
for(i = 0;i< 100000000LL;i++)
{
pthread_mutex_lock(&mutex_);
total += i;
pthread_mutex_unlock(&mutex_);
}
cout << "pthread_quit" << "\n";
return 0;
};
void func3(void)
{
pthread_t thread1,thread2;
if(pthread_create(&thread1,NULL,&func,NULL))
{
throw;
}
if(pthread_create(&thread2,NULL, &func, NULL))
{
throw;
}
pthread_join(thread1, NULL);
pthread_join(thread2,NULL);
cout << total << endl;// 9999999900000000
};
在main.cpp调用:
// #include "test.h"
#include "test1.h"
int main(int argc, char* argv[])
{
// func2();
func3();
return 0;
}
我们要注释掉前面的例子,因为这次是指定c++98编译,g++ main.cpp test1.cpp -o test.exe -pthread -std=c++98,编译及运行如下:
对比前面例子来说明一下:
pthread是C++98接口且只支持Linux,使用时需要包含头文件#include <pthread.h>,编译时需要链接pthread库,主要是通过一组函数模板集实现多线程。
std::thread是C++11接口,是跨平台支持的,使用时需要包含头文件#include <thread>,编译时需要支持c++11标准。thread中封装了pthread的方法,所以也需要链接pthread库,std::thread是类模板方式来实现多线程。
2.4 c++11以后的多线程支持
在C++11中,标准的一个相当大的变化就是引入了多线程的支持。这使得C/C++语言在进行线程编程时,不必依赖第三方库和标准。而C/C++对线程的支持,一个最为重要的部分,就是在原子操作中引入了原子类型的概念。
上述代码中,基于pthread的方法虽然可行,但代码编写却很麻烦。程序员需要为共享变量创建互斥锁,并在进入临界区前后进行加锁和解锁的操作。对于习惯了在单线程情况下编程的程序员而言,互斥锁的管理无疑是种负担。不过在C++11中,通过对并行编程更为良好的抽象,要实现同样的功能就简单了很多。再看看下面代码。
创建test2.h、test2.cpp源文件
test2.h
#ifndef _TEST_2_H_
#define _TEST_2_H_
void func4(void);
#endif //_TEST_2_H_
test2.cpp
#include "test2.h"
#include <atomic>
#include <thread>
#include <iostream>
using namespace std;
// 原子数据类型
atomic_llong total {0};
void func(int)
{
for(long long i = 0; i< 100000000LL; ++i)
{
total += i;
}
cout << "pthread_quit" << "\n";
};
void func4(void)
{
thread t1(func, 0);
thread t2(func, 0);
t1.join();
t2.join();
cout << total << endl;// 9999999900000000
};
在main.cpp调用:
// #include "test.h"
// #include "test1.h"
#include "test2.h"
int main(int argc, char* argv[])
{
// func2();
// func3();
func4();
return 0;
}
我们再次注释掉前面的例子,因为这次是指定c++11编译,g++ main.cpp test2.cpp -o test.exe -std=c++11,编译及运行如下:
在这次的代码中,将变量total定义为一个"原子数据类型":atomic_llong,该类型长度等同于C++11中的内置类型long long。在C++11中,开发者不需要为原子数据类型显式地声明互斥锁或调用加锁、解锁的API,线程就能够对变量total互斥地进行访问。这里仅定义了C++11的线程std::thread变量t1及t2,它们都执行同样的函数func,并类似于pthread t,调用了std::thread成员函数join加入程序的执行。由于原子数据类型的原子性得到了可靠的保障,程序最后打印出的total的值和前面是一致的效果。
相比于基于pthread"原子操作API"而言,C++11对于"原子操作"概念的抽象遵从了面向对象的思想,C++11标准定义的都是所谓的“原子类型”。而传统意义上所谓的“原子操作”,则抽象为针对于这些原子类型的操作。直观地看,编译器可以保证原子类型在线程间被互斥地访问。这样设计,从并行编程的角度看,是由于需要同步的总是数据而不是代码,因此C++11对数据进行抽象,会有利于产生行为更为良好的并行代码。而进一步地,一些琐碎的概念,比如互斥锁、临界区则可以被C++11的抽象所掩盖,因而并行代码的编写也会变得更加简单。
2.5 线程与互斥锁
上述代码中,用到了互斥锁的概念,它主要在多线程应用程序中,是一种有效的方式来保护共享资源的访问,避免竞态条件和数据损坏的问题的常用手段。
C++98中的互斥锁是一种常用的同步工具,用于保护多线程应用程序中共享资源的访问。下面是使用互斥锁的要点。
- 创建互斥锁:使用pthread_mutex_init()函数初始化一个互斥锁变量。
- 锁定互斥锁:使用pthread_mutex_lock()函数锁定一个互斥锁。如果互斥锁已经被锁定,则调用线程将阻塞,直到互斥锁被释放。
- 解锁互斥锁:使用pthread_mutex_unlock()函数解锁一个互斥锁。
- 销毁互斥锁:使用pthread_mutex_destroy()函数销毁一个互斥锁。
在上述程序中,创建了一个互斥锁变量mutex_,并定义了一个共享变量total。然后,我们创建了2个线程,每个线程都会对total变量进行100000000LL次+=i增量操作。在这个操作中,我们使用互斥锁来保护total变量的访问,确保每个线程每次+=i增量操作都是原子的。最后,我们输出count变量的值,验证程序的正确性。
2.6 跨线程安全问题
c/c++开发中的线程安全问题是指在多线程程序中,同时有多个线程访问同一段代码或同一份数据时,可能会导致数据的不一致性或程序崩溃等问题。为了避免这些问题,可以采取以下措施:
- 使用互斥锁或信号量等同步机制来保护共享数据,确保同一时间只有一个线程能够访问它。互斥锁是一种最基本的同步机制,它能够保证同一时间只有一个线程能够进入临界区。
- 对于非常小的临界区,可以使用原子操作来保证数据的一致性。原子操作是指一组操作,能够保证在同一时间只有一个线程能够访问某个共享资源,从而避免了竞争条件。
- 避免死锁问题。死锁是指在多个线程互相等待对方释放锁的情况下发生的一种死循环。为了避免死锁,可以使用避免嵌套锁,按照相同的顺序获取锁等方法。
- 避免竞争条件。竞争条件是指在多线程环境下,由于访问共享资源的顺序不同,导致程序出现错误的情况。为了避免竞争条件,可以使用同步机制、原子操作等。
- 尽可能减少临界区。减少临界区的长度能够减小线程争夺锁的时间,从而提高程序的并发性能。
- 对于全局变量等共享数据,可以使用volatile关键字来保证它们的可见性。volatile关键字能够确保每个线程都能够看到共享变量的最新值。
- 使用线程安全的库函数、类和数据结构。标准库提供了许多线程安全的函数、类和数据结构,如线程安全的随机数生成函数rand_r()等。
综上所述,c/c++线程安全问题可以通过使用同步机制、避免死锁和竞争条件、减少临界区长度、使用volatile关键字等方法来解决。同时,使用线程安全的库函数、类和数据结构也能够有效地避免线程安全问题。
三、认识c/c++的thread
3.1 std::thread类
c++11标准引入了std::thread类,定义于头文件 <thread>:
//C++11起
class thread;
类 thread 表示单个执行线程。线程允许多个函数同时执行。线程在构造关联的线程对象时立即开始执行(等待任何OS调度延迟),从提供给作为构造函数参数的顶层函数开始。顶层函数的返回值将被忽略,而且若它以抛异常终止,则调用 std::terminate 。顶层函数可以通过std::promise 或通过修改共享变量(可能需要同步, std::mutex 与 std::atomic )将其返回值或异常传递给调用方。
std::thread 对象也可能处于不表示任何线程的状态(默认构造、被移动、 detach 或 join 后),并且执行线程可能与任何 thread 对象无关(在 detach 后)。没有两个 std::thread 对象会表示同一执行线程; std::thread 不是可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) 的,尽管它可移动构造 (MoveConstructible) 且可移动赋值 (MoveAssignable) 。
std::thread类提供了如下功能:
成员类型 定义
native_handle_type (可选) 实现定义
成员类
id 表示线程的 id(公开成员类)
成员函数
(构造函数) 构造新的 thread 对象(公开成员函数)
(析构函数) 析构 thread 对象,必须合并或分离底层线程(公开成员函数)
operator= 移动 thread 对象(公开成员函数)
观察器
joinable 检查线程是否可合并,即潜在地运行于平行环境中(公开成员函数)
get_id 返回线程的 id(公开成员函数)
native_handle 返回底层实现定义的线程句柄(公开成员函数)
hardware_concurrency[静态] 返回实现支持的并发线程数(公开静态成员函数)
操作
join 等待线程完成其执行(公开成员函数)
detach 容许线程从线程句柄独立开来执行(公开成员函数)
swap 交换二个 thread 对象(公开成员函数)
非成员函数
std::swap(std::thread) 特化 std::swap 算法(函数)
其在<thread>头文件中,声明如下:
namespace std {
class thread {
public:
// 类型
class id;
using native_handle_type = /* 由实现定义 */;
// 构造/复制/销毁
thread() noexcept;
template<class F, class... Args> explicit thread(F&& f, Args&&... args);
~thread();
thread(const thread&) = delete;
thread(thread&&) noexcept;
thread& operator=(const thread&) = delete;
thread& operator=(thread&&) noexcept;
// 成员
void swap(thread&) noexcept;
bool joinable() const noexcept;
void join();
void detach();
id get_id() const noexcept;
native_handle_type native_handle();
// 静态成员
static unsigned int hardware_concurrency() noexcept;
};
}
3.2 std::jthread类
值得注意的是,在c++20标准中,引入了一个新的线程类std::jthread类。类 jthread 表示单个执行线程。它拥有通常同 std::thread 的行为,除了 jthread 在析构时自动再结合,而且能在具体情况下取消/停止。
线程在构造关联的线程对象时(在任何操作系统调度延迟后)立即开始执行,始于作为构造函数参数提供的顶层函数。忽略顶层函数的返回值,而若它因抛异常退出,则调用 std::terminate 。顶层函数可经由 std::promise 向调用方交流其返回值或异常,或通过修改共享变量(要求同步,见 std::mutex 与 std::atomic )。
使用std::thread时,让主线程等待该子线程完成,然后主线程再继续执行,对于不会停止的线程,不要使用join(),防止阻塞其他线程,或调用detach()(进行线程分离,使其不影响其他线程运行)。如果join()和detach()都没有被调用,析构函数将立即导致程序异常终止。C++20引入的std::jthread得以解决这个问题,std::jthread对象被析构时,会自动调用join(),等待执行流结束。
std::jthread的j实际上是joining的缩写,不同于 std::thread 在其生命周期结束时调用join(),std::jthread 逻辑上保有一个内部的 std::stop_source 类型私有成员,它维持共享停止状态。std:: jthread 的构造函数接受一个 std::stop_token 作为其首参数, std::jthread 将从其内部的 stop_source 传递它。这允许函数在其执行中检查是否已请求停止,而若已请求则返回。
std::jthread 对象亦可在不表示任何线程的状态(在默认构造、被移动、 detach 或 join 后),而执行线程可以与任何 std::jthread 对象关联( detach 后)。没有二个 std::jthread 对象可表示同一执行线程; std::jthread 非可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) ,尽管它为可移动构造 (MoveConstructible) 及可移动赋值 (MoveAssignable) 。
//(C++20)
成员类型 定义
id std::thread::id
native_handle_type (可选) std::thread::native_handle_type
成员函数
(构造函数) 创建新的 jthread 对象(公开成员函数)
(析构函数) 若 joinable() 为 true ,则调用 request_stop() 然后 join() ;不论如何都会销毁 jthread 对象。(公开成员函数)
operator= 移动 jthread 对象(公开成员函数)
观察器
joinable 检查线程是否可合并,即潜在地运行于平行环境中(公开成员函数)
get_id 返回线程的 id(公开成员函数)
native_handle 返回底层实现定义的线程句柄(公开成员函数)
hardware_concurrency[静态] 返回实现支持的并发线程数(公开静态成员函数)
操作
join 等待线程完成其执行(公开成员函数)
detach 容许线程从线程句柄独立开来执行(公开成员函数)
swap 交换二个 jthread 对象(公开成员函数)
停止记号处理(有别于std::thread)
get_stop_source 返回与线程的停止状态关联的 stop_source 对象(公开成员函数)
get_stop_token 返回与线程的共享停止状态关联的 stop_token(公开成员函数)
request_stop 请求执行经由线程的共享停止状态停止(公开成员函数)
非成员函数
swap(std::jthread) 特化 std::swap 算法(函数)
其在<thread>头文件中,声明如下:
namespace std {
class jthread {
public:
// 类型
using id = thread::id;
using native_handle_type = thread::native_handle_type;
// 构造函数、移动与赋值
jthread() noexcept;
template<class F, class... Args> explicit jthread(F&& f, Args&&... args);
~jthread();
jthread(const jthread&) = delete;
jthread(jthread&&) noexcept;
jthread& operator=(const jthread&) = delete;
jthread& operator=(jthread&&) noexcept;
// 成员
void swap(jthread&) noexcept;
[[nodiscard]] bool joinable() const noexcept;
void join();
void detach();
[[nodiscard]] id get_id() const noexcept;
[[nodiscard]] native_handle_type native_handle();
// 停止记号处理
[[nodiscard]] stop_source get_stop_source() noexcept;
[[nodiscard]] stop_token get_stop_token() const noexcept;
bool request_stop() noexcept;
// 特化的算法
friend void swap(jthread& lhs, jthread& rhs) noexcept;
// 静态成员
[[nodiscard]] static unsigned int hardware_concurrency() noexcept;
private:
stop_source ssource; // 仅用于阐释
};
}
下面来看下std::jthread的使用示例(参考了网上代码),创建test3.h和test3.cpp:
tes3.h
#ifndef _TEST_3_H_
#define _TEST_3_H_
void func5(void);
#endif //_TEST_3_H_
test3.cpp
#include "test3.h"
#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
using namespace std;
void f1(int n)
{
for (int i = 0; i < 5; ++i) {
cout << "Thread 1 executing\n";
++n;
this_thread::sleep_for(chrono::milliseconds(10));
}
}
void f2(int& n)
{
for (int i = 0; i < 5; ++i) {
cout << "Thread 2 executing\n";
++n;
this_thread::sleep_for(chrono::milliseconds(10));
}
}
class foo
{
public:
void bar()
{
for (int i = 0; i < 5; ++i) {
cout << "Thread 3 executing\n";
++n;
this_thread::sleep_for(chrono::milliseconds(10));
}
}
int n = 0;
};
class baz
{
public:
void operator()()
{
for (int i = 0; i < 5; ++i) {
cout << "Thread 4 executing\n";
++n;
this_thread::sleep_for(chrono::milliseconds(10));
}
}
int n = 0;
};
void func5(void)
{
int n = 0;
foo f;
baz b;
jthread jt0; // t0 不是线程
jthread jt1(f1, n + 1); // 按值传递
jthread jt2a(f2, ref(n)); // 按引用传递
jthread jt2b(move(jt2a)); // t2b 现在运行 f2() 。 t2a 不再是线程
jthread jt3(&foo::bar, &f); // t3 在对象 f 上运行 foo::bar()
jthread jt4(b); // t4 在对象 b 上运行 baz::operator()
jt1.join();
jt2b.join();
jt3.join();
cout << "Final value of n is " << n << '\n';
cout << "Final value of foo::n is " << f.n << '\n';
// t4 在析构时join
};
在main.cpp调用:
// #include "test.h"
// #include "test1.h"
// #include "test2.h"
#include "test3.h"
int main(int argc, char* argv[])
{
// func2();
// func3();
// func4();
func5();
return 0;
}
我们再次注释掉前面的例子,因为这次是指定c++20编译,g++ main.cpp test3.cpp -o test.exe -std=c++20,编译及运行如下:
3.3 独木难支-线程关联函数集及类集
除了thread和jthread类外,c/c++标准库为了支持到多线程的同步、安全、死锁等多线程问题及跨线程应用场景,在<thread>库中配套了众多相关的函数和类,先睹为快,它们的详细说明及案例实践见后面篇章。
管理当前线程的函数,定义于命名空间 this_thread
yield,(C++11), 建议实现重新调度各执行线程(函数)
get_id,(C++11), 返回当前线程的线程 id(函数)
sleep_for,(C++11), 使当前线程的执行停止指定的时间段(函数)
sleep_until,(C++11), 使当前线程的执行停止直到指定的时间点(函数)
线程取消,定义于头文件 <stop_token>
stop_token,(C++20), 查询是否已经做出 std::jthread 取消请求的接口(类)
stop_source,(C++20), 表示请求停止一个或多个 std::jthread 的类(类)
stop_callback,(C++20), 在 std::jthread 取消上注册回调的接口(类模板) (C++20 起)
缓存大小访问,定义于头文件 <new>
hardware_destructive_interference_size,(C++17),避免假共享的最小偏移(常量)
hardware_constructive_interference_size,(C++17),促使真共享的最大偏移(常量)
互斥,义于头文件 <mutex>
互斥算法避免多个线程同时访问共享资源。这会避免数据竞争,并提供线程间的同步支持。定
mutex,(C++11),提供基本互斥设施(类)
timed_mutex,(C++11), 提供互斥设施,实现有时限锁定(类)
recursive_mutex,(C++11), 提供能被同一线程递归锁定的互斥设施(类)
recursive_timed_mutex,(C++11),提供能被同一线程递归锁定的互斥设施,并实现有时限锁定(类) ,定义于头文件 <shared_mutex>
shared_mutex,(C++17), 提供共享互斥设施(类)
shared_timed_mutex,(C++14), 提供共享互斥设施并实现有时限锁定(类)
通用互斥管理,定义于头文件 <mutex>
lock_guard,(C++11), 实现严格基于作用域的互斥体所有权包装器(类模板)
scoped_lock,(C++17), 用于多个互斥体的免死锁 RAII 封装器(类模板)
unique_lock,(C++11), 实现可移动的互斥体所有权包装器(类模板)
shared_lock,(C++14), 实现可移动的共享互斥体所有权封装器(类模板)
defer_lock_t,(C++11),用于指定锁定策略的标签类型(类)
try_to_lock_t,(C++11),用于指定锁定策略的标签类型(类)
adopt_lock_t,(C++11), 用于指定锁定策略的标签类型(类)
defer_lock,(C++11),用于指定锁定策略的标签常量(常量)
try_to_lock,(C++11),用于指定锁定策略的标签常量(常量)
adopt_lock,(C++11),用于指定锁定策略的标签常量(常量)
通用锁定算法
try_lock,(C++11),试图通过重复调用 try_lock 获得互斥体的所有权(函数模板)
lock,(C++11), 锁定指定的互斥体,若任何一个不可用则阻塞(函数模板)
单次调用
once_flag,(C++11), 确保 call_once 只调用函数一次的帮助对象(类)
call_once,(C++11), 仅调用函数一次,即使从多个线程调用(函数模板)
条件变量,定义于头文件 <condition_variable>
/*条件变量是允许多个线程相互交流的同步原语。它允许一定量的线程等待(可以定时)另一线程的提醒,然后再继续。条件变量始终关联到一个互斥。*/
condition_variable,(C++11), 提供与 std::unique_lock 关联的条件变量(类)
condition_variable_any,(C++11), 提供与任何锁类型关联的条件变量(类)
notify_all_at_thread_exit,(C++11), 安排到在此线程完全结束时对 notify_all 的调用(函数)
cv_status,(C++11), 列出条件变量上定时等待的可能结果(枚举)
信号量,定义于头文件 <semaphore>
/*信号量 (semaphore) 是一种轻量的同步原件,用于制约对共享资源的并发访问。在可以使用两者时,信号量能比条件变量更有效率。*/
counting_semaphore,(C++20), 实现非负资源计数的信号量(类模板)
binary_semaphore,(C++20), 仅拥有二个状态的信号量(typedef)
闩与屏障,定义于头文件 <latch>
/*闩 (latch) 与屏障 (barrier) 是线程协调机制,允许任何数量的线程阻塞直至期待数量的线程到达该屏障。闩不能复用,屏障能重复使用。*/
latch,(C++20), 单次使用的线程屏障(类) .定义于头文件 <barrier>
barrier,(C++20), 可复用的线程屏障(类模板) , (C++20 起)
Future,定义于头文件 <future>
/*标准库提供了一些工具来获取异步任务(即在单独的线程中启动的函数)的返回值,并捕捉其所抛出的异常。这些值在共享状态中传递,其中异步任务可以写入其返回值或存储异常,而且可以由持有该引用该共享态的 std::future 或 std::shared_future 实例的线程检验、等待或是操作这个状态。*/
promise,(C++11),存储一个值以进行异步获取(类模板)
packaged_task,(C++11), 打包一个函数,存储其返回值以进行异步获取(类模板)
future,(C++11), 等待被异步设置的值(类模板)
shared_future,(C++11), 等待被异步设置的值(可能为其他 future 所引用)(类模板)
async,(C++11), 异步运行一个函数(有可能在新线程中执行),并返回保有其结果的 std::future(函数模板)
launch,(C++11),指定 std::async 所用的运行策略(枚举)
future_status,(C++11),指定在 std::future 和 std::shared_future 上的定时等待的结果(枚举)
Future 错误
future_error,(C++11),报告与 future 或 promise 有关的错误(类)
future_category,(C++11),鉴别 future 错误类别(函数)
future_errc,(C++11),鉴别 future 错误码(枚举)