天天看點

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

一、編譯環境準備

        在正式進入c/c++多線程程式設計系列之前,先來搭建支援多線程編譯的編譯環境。

        1.1 MinGW(win)

        進入Downloads - MinGW-w64下載下傳頁面,選擇MinGW-w64-builds跳轉下載下傳,

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         再次進行跳轉:

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         然後進入下載下傳頁面(WinLibs - GCC+MinGW-w64 compiler for Windows),下拉到Download标題下面,按需下載下傳,本文選擇的是編譯好的WIN工具包

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         本文下載下傳的如下:

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         将該工具包解壓到無漢字、特殊字元的路徑上,例如本文:

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         進入環境變量配置頁面,在path環境變量中,添加mingw路徑:

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         運作指令工具,測試gcc -v和g++ -v是否生效,例如下圖

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         1.2 Cygwin(win)

        同樣還是(Downloads - MinGW-w64)下載下傳頁面,選擇Cygwin選項跳轉:

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         選擇setup.exe安裝工具包,跳轉,

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

        進入(Cygwin Installation)頁面,下載下傳

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         下載下傳完成後,得到直接安裝的.exe檔案

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         輕按兩下安裝,按安裝說明引導,路徑設定最好不包含漢字和特殊符号,本文安裝如下:

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         安裝完成後,同樣可以去配置環境變量,本文是先設定了變量,在引入path環境變量中的。

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲
C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         如果沒有建立Cygwin的桌面快捷方式,

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         可以自行進入路徑手動建立該快捷方式。

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         輕按兩下快捷方式啟動進入一個仿linux系統運作的win編譯工具指令視窗,通過gcc -v或g++ -v測試是否支援c/c++編譯.

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         那麼windows的磁盤檔案就在"/cygdrive"目錄下:

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         另外可以右鍵視窗選擇options項進入視窗設定,例如視窗大小、字型等,儲存後重新開機生效。

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         1.3、純粹的linux環境

        本文是采用VMware® Workstation 15 Pro+安裝centos7桌面版系統來實作的,具體安裝請參考其他博文資料,然後運作gcc -v或g++ -v測試編譯支援:

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         另外其他一些linux系統本人也測試了一下,都支援到c++11以上,但是部分可能支援到新的c++20、c++23标準會較麻煩

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         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++基于多線程并發程式設計的概念。所謂并發程式設計是指在一台處理器上“同時”處理多個任務。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,編譯及運作如下:

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         對比前面例子來說明一下:

        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,編譯及運作如下:

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

         在這次的代碼中,将變量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,編譯及運作如下:

C/C++開發,無可避免的多線程(篇一).跨平台并行程式設計姗姗來遲

 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 錯誤碼(枚舉)