C/C++开发,无可避免的多线程(篇一).跨平台并行编程姗姗来迟
一、编译环境准备
在正式进入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 错误码(枚举)
相关文章:

C/C++开发,无可避免的多线程(篇一).跨平台并行编程姗姗来迟
一、编译环境准备 在正式进入c/c多线程编程系列之前,先来搭建支持多线程编译的编译环境。 1.1 MinGW(win) 进入Downloads - MinGW-w64下载页面,选择MinGW-w64-builds跳转下载, 再次进行跳转: 然后进入下载页…...

如何把照片的底色修改为想要的颜色
如何给照片更换底色?其实有可以一键给照片更换底色的 APP ,但是几乎都要收费。如果想要免费的给照片更换底色的话,分享两种简单便捷的方法给你。掌握了这项技能,以后就不用店花钱处理啦!1、免费!线上快速 给…...

【高效办公】批量生成固定模板的文件夹名称
老师让你按照他的要求生成每位学生的文件夹,你是学委,让你马上完成该任务,但你又不想是手动一个一个码字,因此聪明的你就看到了本篇文章啦!!! 虽说一个人懒惰,并不是好的事情。 但这个似乎合情合理啊~ 然后,就动手想办法,一开始就真的打算码字了。。 思路 在实际开…...

redis的集群方式
1.主从复制 主从复制原理: 从服务器连接主服务器,发送SYNC命令; 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令; 主服务器BGSAVE执行完后,向所有从服务…...

温控负荷的需求响应潜力评估及其协同优化管理研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

模电学习9. MOS管使用入门
模电学习9. MOS管使用入门一、mos管理简介1. 简介2. mos管理的特点3. MOS管的工作状态(1)放大功能(2)截止区(3)饱和区3. Mos管的分类(1)按照工作模式分类:(2&…...
【算法】【数组与矩阵模块】正数组中累加和为给定值的最长子数组长度,空间复杂度O(1)解法
目录前言问题介绍解决方案代码编写java语言版本c语言版本c语言版本思考感悟写在最后前言 当前所有算法都使用测试用例运行过,但是不保证100%的测试用例,如果存在问题务必联系批评指正~ 在此感谢左大神让我对算法有了新的感悟认识! 问题介绍 …...
3.1.2 创建表
文章目录1.创建表2.表创建基础3.表的主键4.使用null值5.使用AUTO_INCREMENT6.指定默认值7. 字段备注8.引擎类型9.外键1.创建表 表的创建一般有俩种方式,一种是使用交互式创建和管理表的工具,比如我们安装的MariaDB,另一种是使用MySQL 语句进…...

使用netlify实现自动化部署前端项目(无服务器版本)
介绍 本文以 github仓库进行介绍关联netlify的无服务前端自动化部署。用途:个人网站设计、小游戏等当然这只是让你入门~具体细节等待你自己去探索 实现 打开官方网站 如果没有注册过的账户,你需要使用 github 去进行登录。注册完成后会自动给你提示填…...
MATLAB点云数据处理(二十九):可视化点云之pcshow参数详解与快捷键操作
文章目录 1 pcshow简述2 最简单的pcshow3 带参数的pcshow3.1 点大小参数----MakerSize3.2 背景色参数----Background3.3 指定竖直轴参数----VerticalAxis3.4 指定垂直轴方向参数----VerticalAxisDir3.5 投影参数----Projection3.6 指定可视化平面参数----ViewPlane3.7 颜色渲染…...

顺序表——重置版
本期我们来实现数据结构的顺序表(这个之前写过一次,不过本期和之前可能会略有不同,但大体相同),大家可以看一下我们之前完成的顺序表 (6条消息) 顺序表及其多种接口的实现_顺序表类中实现接口方法_KLZUQ的博客-CSDN博客…...
PyQt5自然语言处理入门案例笔记
前言 最近想将自然语言处理的项目进行可视化,尽量还是使用回Python语言,因此打算用PyQt来实现相应的功能。 入门案例 一个简单的自然语言处理的demo,使用PyQt框架,该demo可以读取文本文件,对文件中的文本进行情感分…...
使用 CSS 替换表行颜色?
跳到主内容 我正在使用一个带有交替行颜色的表格。 tr.d0 td {background-color: #CC9999;color: black; } tr.d1 td {background-color: #9999CC;color: black; }<table><tr class"d0"><td>One</td><td>one</td></tr>&…...

智能家居控制系统
🥁作者: 华丞臧. 📕专栏:【项目经验】 各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞收藏关注)。如果有错误的地方,欢迎在评论区指出。 推荐一款刷题网站 👉 LeetCode刷题网站…...

Linux 进程:fork()与vfork()的对比
目录一、fork函数二、vfork函数1.函数的原理2.函数的隐患3.解决函数隐患的方法在Linux的进程学习中,常使用fork函数来创建子进程,但其实还有一个vfork函数也可以创建子进程。但是这两个函数的实现机制不同,fork函数使用了写实拷贝技术&#x…...

环境搭建02-Ubuntu16.04 安装CUDA和CUDNN、CUDA多版本替换
1、CUDA安装 (1)下载需要的CUDA版本 https://developer.nvidia.com/cuda-toolkit-archive (2)安装 sudo sh cuda_8.0.61_375.26_linux.run(3)添加环境 gedit ~/.bashrc在文件末尾添加: ex…...

HOT100--(3)无重复字符的最长子串
点击查看题目详情 大思路: 创建哈希表,元素类型为<char, int>,分别是字符与其对应下标 用哈希表来存储未重复的子串,若有重复则记录下当前子串最大值maxhashsize 并且开始以相同方法记录下一子串 遍历完成以后,…...

vue keep-alive多层级路由支持
keep-alive使用 属性值 1.include - 字符串或正则表达式。只有名称匹配的组件会被缓存。 2.exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。 3.max - 数字。最多可以缓存多少组件实例。 注:匹配首先检查组件自身的 name 选项,如果 nam…...

从源码角度看React-Hydrate原理
React 渲染过程,即ReactDOM.render执行过程分为两个大的阶段:render 阶段以及 commit 阶段。React.hydrate渲染过程和ReactDOM.render差不多,两者之间最大的区别就是,ReactDOM.hydrate 在 render 阶段,会尝试复用(hydr…...
ARM基础 -- 2
文章目录一、可编程器件的编程原理1.1 电子器件的发展方向1.2 可编程器件的特点1.3 整个编程及运行过程二、指令集对CPU的意义2.1 汇编语言与C等高级语言的差异2.2 汇编语言的本质2.2.1 编程语言的发展过程2.2.2 汇编语言的特点和用途三、RISC和CISC的区别3.1 复杂指令集CPU --…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...

基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...