C/C++开发,无可避免的多线程(篇一).跨平台并行编程姗姗来迟

news2024/11/17 17:27:59

一、编译环境准备

        在正式进入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中的互斥锁是一种常用的同步工具,用于保护多线程应用程序中共享资源的访问。下面是使用互斥锁的要点。

  1. 创建互斥锁:使用pthread_mutex_init()函数初始化一个互斥锁变量。
  2. 锁定互斥锁:使用pthread_mutex_lock()函数锁定一个互斥锁。如果互斥锁已经被锁定,则调用线程将阻塞,直到互斥锁被释放。
  3. 解锁互斥锁:使用pthread_mutex_unlock()函数解锁一个互斥锁。
  4. 销毁互斥锁:使用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 错误码(枚举) 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/380954.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Fiddler抓包之Fiddler过滤器(Filters)调试

Filters&#xff1a;过滤器&#xff0c;帮助我们过滤请求。 如果需要过滤掉与测试项目无关的抓包请求&#xff0c;更加精准的展现抓到的请求&#xff0c;而不是杂乱的一堆&#xff0c;那功能强大的 Filters 过滤器能帮到你。 2、Filters界面说明 fiddler中的过滤 说明&#…

新增2000w播放、单月涨粉80w!13秒短视频竟成B站热门

知识区自从被设立为一级分区后&#xff0c;B站就成了大家口中的“互联网大学”&#xff0c;有什么不懂的知识就习惯上B站搜一搜、查一查。根据B站官方出具的创作者报告数据显示&#xff0c;除了众多自发原创知识作品的UP主以外&#xff0c;还有超过300位名师学者加入B站&#x…

【亲测可用】BEV Fusion (MIT) 环境配置

CUDA环境 首先我们需要打上对应版本的显卡驱动&#xff1a; 接下来下载CUDA包和CUDNN包&#xff1a; wget https://developer.download.nvidia.com/compute/cuda/11.6.2/local_installers/cuda_11.6.2_510.47.03_linux.run sudo sh cuda_11.6.2_510.47.03_linux.runwget htt…

pytorch-softmax解决分类问题,用fashion-mnist为例子,再走一遍数据获取到模型预测的流程。深度了解分类指标的递进关系

softmax回归 线性回归模型适用于输出为连续值的情景。在另一类情景中&#xff0c;模型输出可以是一个像图像类别这样的离散值。对于这样的离散值预测问题&#xff0c;我们可以使用诸如softmax回归在内的分类模型。和线性回归不同&#xff0c;softmax回归的输出单元从一个变成了…

当ChatGPT遇见Python

在如火如荼的 ChatGPT 大潮当中&#xff0c;已经衍生出了各种各样的周边产品。Python 作为著名的万金油工具&#xff0c;怎么能没有它的身影呢。今天我们就介绍两种通过 Python 调用 ChatGPT 的方法&#xff0c;一起来看看吧~chatgpt-wrapper这是一个开源在 GitHub 上的项目&am…

leetcode 236. 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一个节点也可以是它自己的祖…

华为机试题:HJ86 求最大连续bit数(python)

文章目录&#xff08;1&#xff09;题目描述&#xff08;2&#xff09;Python3实现&#xff08;3&#xff09;知识点详解1、input()&#xff1a;获取控制台&#xff08;任意形式&#xff09;的输入。输出均为字符串类型。1.1、input() 与 list(input()) 的区别、及其相互转换方…

Linux下 C/C++ NTP网络时间协议详解

NTP&#xff08;Network Time Protocol&#xff0c;网络时间协议&#xff09;是由RFC 1305定义的时间同步协议。它是通过网络在计算机系统之间进行时钟同步的网络协议。NTP 在公共互联网上通常能够保持时间延迟在几十毫秒以内的精度&#xff0c;并在理想条件下&#xff0c;它能…

Molecule:使用Jetpack Compose构建StateFlow流

Molecule:使用Jetpack Compose构建StateFlow流 看下面的jetpack compose片段&#xff1a; Composable fun MessageCard(message: Message) {Column {Text(text message.author)Text(text message.body)} }这段代码最有趣的部分是它实际上是reactive。其反应性为 通过Composa…

树链剖分(维护树上信息)

学习前请先掌握线段树&#xff1a;线段树&#xff08;维护区间信息&#xff09; 一&#xff0c;思想&#xff1a; 将一颗树拆成多条线性链以方便维护&#xff08;如线段树&#xff09;。 先给出以下定义&#xff08;通过这些定义我们就可以组成链&#xff09;&#xff1a; …

Docker概念介绍

目录 1、传统方式、虚拟化、容器部署方式的区别 2、为什么会有docker 3、什么是docker 4、docker的优势 5、Docker组成部分 6、docker镜像的原理介绍 7、 容器应用场景 8、Docker资源汇总 了解docker之前&#xff0c;我们要先了解部署方式有哪些&#xff0c;各有什么优缺点…

Windows 右键菜单扩展容器 [开源]

今天给大家分享一个我做的小工具&#xff0c;可以自定义扩展右键菜单的功能来提高工作效率&#xff0c;效果图如下&#xff1a; 如上图&#xff0c;右键菜单多了几个我自定义的菜单&#xff1a; 复制文件路径 复制文件夹路径 我的工具箱 <走配置文件动态创建子菜单&#x…

cesium封装实现配置格网及插值高程面实现

一、数据结构建模二、插值算法得到的插值结果三、图层配置primitiveGrid:{isRLayerPanel: true,primitives:[],url: /static/data/Grid.json,dataPath: ,dataIdField: code,options:{id:primitiveGrid,name:格网,type:grid,isShow: false},location: {"destination":…

Hive中的基础函数(一)

一、hive中的内置函数根据应用归类整体可以分为8大种类型。 1、 String Functions 字符串函数 主要针对字符串数据类型进行操作&#xff0c;比如下面这些&#xff1a; 字符串长度函数&#xff1a;length •字符串反转函数&#xff1a;reverse •字符串连接函数&#xff1a;…

Word处理控件Aspose.Words功能演示:使用 C++ 在 Word 文档 (DOC/DOCX) 中插入表格

Aspose.Words 是一种高级Word文档处理API&#xff0c;用于执行各种文档管理和操作任务。API支持生成&#xff0c;修改&#xff0c;转换&#xff0c;呈现和打印文档&#xff0c;而无需在跨平台应用程序中直接使用Microsoft Word。此外&#xff0c; Aspose API支持流行文件格式处…

基于python的多线程数据库数据录入

说明&#xff1a; 使用python编程结合多线程技术&#xff0c;将已经在python文件中的数据批量写入到数据库&#xff0c;便于数据关系结构化管理。 环境配置&#xff1a; certifi2019.6.16 chardet3.0.4 idna2.8 PyMySQL0.9.3 requests2.22.0 urllib31.25.3 将所需要的环境保…

vue模板语法和数据绑定和el、data的两种

vue模板语法有两大类&#xff1a; 1.插值语法&#xff1a; 功能&#xff1a;用于解拆标签体内容 写法&#xff1a;{{xxx}}&#xff0c;xxx是js表达式&#xff0c;且可以直接读取到data中的所有属性 2.指令语法&#xff1a; 功能&#xff1a;用于解拆标签&#xff08;包括&…

《商用密码应用与安全性评估》第一章密码基础知识1.1应用概念

密码的概念与作用 概念 密码&#xff1a;采用特定变换的方法对信息进行加密保护、安全认证的技术、产品和服务。 密码技术&#xff1a;密码编码、实现、协议、安全防护、分析破译、以及密钥产生、分发、传递、使 用、销毁等技术。 密码技术核心&#xff1a;密码算法…

家用洗地机什么品牌质量好耐用?最适合家用的洗地机

近些年&#xff0c;随着消费水平的不断升级&#xff0c;我们对家电产品的要求也在逐步提高&#xff0c;就以这几年非常流行的洗地机为例&#xff0c;如今的人们在选洗地机时&#xff0c;会综合考虑价位、技术、配置、颜值、功能等多个方面&#xff0c;那么市场上家用洗地机什么…

JAVACC

JavaCC全称为Java Compiler Compiler&#xff0c;它是一个生成器&#xff0c;用于生成词法分析器&#xff08;lexical analysers&#xff09;和语法分析器&#xff08;parsers&#xff09;&#xff1b;JavaCC本身并不是词法分析器和语法分析器&#xff0c;它是一个生成器&#…