亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

C++11學(xué)習(xí)之多線程的支持詳解

 更新時(shí)間:2023年02月06日 10:34:24   作者:Shawn-Summer  
這篇文章主要為大家詳細(xì)介紹了C++11中多線程支持的相關(guān)資料,文中的示例代碼講解詳細(xì),對我們深入了解C++11有一定的幫助,需要的可以參考一下

C++11中的多線程的支持

千禧年以后,主流的芯片廠商都開始生產(chǎn)多核處理器,所以并行編程越來越重要了。在C++98中根本沒有自己的一套多線程編程庫,它采用的是C99中的POSIX標(biāo)準(zhǔn)的pthread庫中的互斥鎖,來完成多線程編程。

首先來簡單一個(gè)概念:原子操作,即多線程程序中"最小的且不可以并行化的操作"。通俗來說,如果對一個(gè)資源的操作是原子操作,就意味著一次性只有一個(gè)線程的一個(gè)原子操作可以對這個(gè)資源進(jìn)行操作。在C99中,我們一般都是采用互斥鎖來完成粗粒度的原子操作。

#include<pthread.h>
#include<iostream>
using namespace std;

static long long total =0;
pthread_mutex_t m=PTHREAD_MUTEX_INITIALIZER;//互斥鎖

void * func(void *)
{
    long long i;
    for(i=0;i<100000000LL;i++)
    {
        pthread_mutex_lock(&m);
        total +=i;
        pthread_mutex_unlock(&m);
    }
}

int main()
{
    pthread_t thread1,thread2;
    if(pthread_create(&thread1,nullptr,&func,nullptr))
    {
        throw;
    }
    if(pthread_create(&thread2,nullptr,&func,nullptr))
    {
        throw;
    }
    pthread_join(thread1,nullptr);
    pthread_join(thread2,nullptr);
    cout<<total<<endl;//9999999900000000
}

可以看出來,上書代碼中total +=i;就是原子操作。

1.C++11中的原子類型

我們發(fā)現(xiàn),在C99中的互斥鎖需要顯式聲明,要自己開關(guān)鎖,為了簡化代碼,C++11中定義了原子類型。這些原子類型是一個(gè)class,它們的接口都是原子操作。如下所示:

#include<atomic>
#include<thread>
#include<iostream>
using namespace std;

atomic_llong total {0};//原子數(shù)據(jù)類型

void func(int)
{
    for(long long i=0;i<100000000LL;i++)
    {
        total+=i;
    }
}
int main()
{
    thread t1(func,0);
    thread t2(func,0);

    t1.join();
    t2.join();
    cout<<total<<endl;//9999999900000000
}

上述代碼中total就是一個(gè)原子類對象,它的接口例如這里的重載operator+=()就是一個(gè)原子操作,所以我們不需要顯式調(diào)用互斥鎖了。

總共有多少原子類型呢?C++11的做法是,存在一個(gè)atomic類模板,我們可以通過這個(gè)類模板定義出想要的原子類型:

using atomic_llong = atomic<long long>;

所以我們想把什么類型搞成原子類型,只需要傳入不同的模板實(shí)參就行了。

總之,C++11中原子操作就是atomic模板類的成員函數(shù)。

1.1 原子類型的接口

我們知道原子類型的接口就是原子操作,但是我們現(xiàn)在關(guān)注一下,它們有哪些接口?

原子類型屬于資源類數(shù)據(jù),多個(gè)線程只能訪問單個(gè)預(yù)祝你類型的拷貝。所以C++11中的原子類型不支持移動(dòng)語義和拷貝語義,原子類型的操作都是對那個(gè)唯一的一份資源操作的,原子類型沒有拷貝構(gòu)造,拷貝賦值,移動(dòng)構(gòu)造和移動(dòng)賦值的。

atomic<float> af{1.2f};//正確
atomic<float> af1{af};//錯(cuò)誤,原子類型不支持拷貝語義
float f=af;//正確,調(diào)用了原子類型的接口
af=0.0;//正確,調(diào)用了原子類型的接口

看一下上表中的一些原子類型的接口,load()是進(jìn)行讀取操作的,例如

atomic<int> a(2);
int b=a;
b=a.load();

上如代碼中的,b=a就是等價(jià)于b=a.load(),實(shí)際上,atomic<int>中存在operator int()接口,這個(gè)接口中:

operator __int_type() const noexcept
{ return load(); }

store()接口是用來寫數(shù)據(jù)的的:

atomic<int> a;
a=1;
a.store(1);

上述代碼中a=1相當(dāng)于a.load(1),atomic<int>中存在operator=(int)接口,它的實(shí)現(xiàn)如下:

__int_type operator=(__int_type __i) noexcept
{
store(__i);
return __i;
}

例如其他操作,比如exchange是做交換,compare_exchange_weak/strong()是比較并交換(CAS操作的),它們的實(shí)現(xiàn)會(huì)更復(fù)雜一些,還有一些符號的重載,這里就不一一介紹了,<<C++ Concurrency in Action>>的第5章和第7章會(huì)詳細(xì)介紹這部分內(nèi)容。

值得注意的是,這里有一個(gè)特殊的原子類型atomic_flag,這是一個(gè)原子類型是無鎖的,也就是說線程對這種類型的數(shù)據(jù)的訪問是無鎖的,所以它就不需要接口:load和store,即多個(gè)線程可以同時(shí)操作這個(gè)資源。我們可以用它來實(shí)現(xiàn)自旋鎖

1.2簡單自旋鎖的實(shí)現(xiàn)

互斥鎖是說,當(dāng)一個(gè)線程訪問一個(gè)資源的時(shí)候,他會(huì)給進(jìn)入臨界區(qū)代碼設(shè)置一把鎖,出來的時(shí)候就把鎖給打開,當(dāng)其他進(jìn)程要進(jìn)入臨界區(qū)的時(shí)候,就會(huì)實(shí)現(xiàn)看一下鎖,如果鎖是關(guān)的,那就阻塞自己,這樣core就會(huì)去執(zhí)行其他工作,導(dǎo)致上下文切換嗎,所以它的效率會(huì)比較低。原子類型中is_lock_free()就是說明的這個(gè)原子類型的訪問是否使用的互斥鎖。

與互斥鎖相反的是自旋鎖,區(qū)別是,當(dāng)其他進(jìn)程要進(jìn)入臨界區(qū)的時(shí)候,如果鎖是關(guān)的,它不會(huì)阻塞自己,而是不斷查看鎖是不是開的,這樣就不會(huì)引發(fā)上下文切換,但是同樣也會(huì)增加cpu利用率。

我們可以使用atomic_flag原子類型來實(shí)現(xiàn)自旋鎖,因?yàn)樵赼tomic_flag本身是無鎖的,所以多個(gè)線程可以同時(shí)訪問它,相當(dāng)于同時(shí)訪問這把自旋鎖,實(shí)現(xiàn)如下:

#include<thread>
#include<atomic>
#include<iostream>
#include<unistd.h>
using namespace std;
atomic_flag lock=ATOMIC_FLAG_INIT;//獲得自旋鎖
void f(int n)
{
    while(lock.test_and_set())
    {//嘗試獲得原子鎖
       cout<<"Waiting from thread "<<n<<endl;
    }
    cout<<"Thread "<<n<<" starts working"<<endl;
}
void g(int n)
{
    cout<<"Thread "<<n<<" is going to start"<<endl;
    lock.clear();//打開鎖
    cout<<"Thread "<<n<<" starts working"<<endl;
}
int main()
{
    lock.test_and_set();//關(guān)上鎖
    thread t1(f,1);
    thread t2(g,2);
    t1.join();
    usleep(100000);
    t2.join();
}

這里的test_and_set()是一個(gè)原子操作,它做的是,寫入新值并返回舊值。在main()中,我們首先給這個(gè)lock變量,寫入true值,即關(guān)上鎖,然后再線程t1中,它不斷嘗試獲得自旋鎖,再線程t2中,clear()接口,相當(dāng)于將lock變量值變成false,這時(shí)自旋鎖就打開了,這樣子,線程t1就可以執(zhí)行剩下的代碼了。

簡單的我們可以將lock封裝一下

void Lock(atomic_flag & lock)
{
    while(lock.test_and_set());
}
void Unlock(atomic_flag & lock)
{
    lock.clear();
}

上面操作中,我們就相當(dāng)于完成了一把鎖,可以用其實(shí)現(xiàn)互斥訪問臨界區(qū)的功能了。不過這個(gè)和C99中的pthread_mutex_lock()和pthread_mutex_unlock()不一樣,C99中的這兩個(gè)鎖是互斥鎖,而上面代碼中實(shí)現(xiàn)的是自旋鎖。

2.提高并行程度

#include <thread>
#include <atomic>
 
atomic<int> a;
atomic<int> b;
void threadHandle()
{
     int t = 1;
     a = t;
     b = 2; // b 的賦值不依賴 a
}

在上面代碼中,對a和b的賦值語句實(shí)際上可以不管先后的,如果允許編譯器或者硬件對其重排序或者并發(fā)執(zhí)行,那就會(huì)提高并行程度。

在單線程程序中,我們根部不關(guān)心它們的執(zhí)行順序,反正結(jié)果都是一樣的,但是多線程不一樣,如果執(zhí)行順序不一樣,結(jié)果就會(huì)不同。

#include <thread>
#include <atomic>
#include<iostream>
using namespace std;
atomic<int> a{0};
atomic<int> b{0};
void ValueSet(int )
{
     int t = 1;
     a = t;
     b = 2; // b 的賦值不依賴 a
}
int Observer(int)
{
    cout<<"("<<a<<","<<b<<")"<<endl;
}
int main()
{
    thread t1(ValueSet,0);
    thread t2(Observer,0);
    t1.join();
    t2.join();
    cout<<"Final: ("<<a<<","<<b<<")"<<endl;
}

上面代碼中,Observer()中的輸出結(jié)果,會(huì)和a和b的賦值順序有關(guān),它的輸出結(jié)果肯可能是:(0,0) (1,0) (0,2) (1,2)。這就說明了,多線程程序中,如果執(zhí)指令行順序不一樣,結(jié)果就會(huì)不同。

影響并行程度的兩個(gè)關(guān)鍵因素是:編譯器是否有權(quán)對指令進(jìn)行重排序和硬件是否有權(quán)對匯編代碼重排序。

C++11中,我們可以顯式得告訴編譯器和硬件它們的權(quán)限,進(jìn)而提高并發(fā)程度。通俗來說,如果我們要求并行程度最高,那么我們就授權(quán)給編譯器和硬件,允許它們重排序指令。

2.1 memory_order的參數(shù)

原子類型的成員函數(shù)中,大多數(shù)都可以接收一個(gè)類型為memory_order的參數(shù),它就是可以告訴編譯器和硬件,是否可以重排序。

typedef enum memory_order {
    memory_order_relaxed,    // 不對執(zhí)行順序做保證
    memory_order_acquire,    // 本線程中,所有后續(xù)的讀操作必須在本條原子操作完成后執(zhí)行
    memory_order_release,    // 本線程中,所有之前的寫操作完成后才能執(zhí)行本條原子操作
    memory_order_acq_rel,    // 同時(shí)包含 memory_order_acquire 和 memory_order_release
    memory_order_consume,    // 本線程中,所有后續(xù)的有關(guān)本原子類型的操作,必須在本條原子操作完成之后執(zhí)行
    memory_order_seq_cst    // 全部存取都按順序執(zhí)行
    } memory_order;

在C++11中,memory_order的參數(shù)的默認(rèn)值是memory_order_seq_cst,即不允許編譯器和硬件進(jìn)行重排序,這樣一來,在上嗎代碼中的Observer()中輸出結(jié)果就不可能是(0,2),因?yàn)閷的賦值語句是先于b的。這實(shí)際上就是:順序一致性,準(zhǔn)確來說就是在同一個(gè)線程中,原子操作的順序和代碼的順序保持一致。

而如果我們改動(dòng)一下代碼:

#include <thread>
#include <atomic>
#include<iostream>
using namespace std;
atomic<int> a{0};
atomic<int> b{0};
void ValueSet(int )
{
     int t = 1;
     a.store(t,memory_order_relaxed);
     b.store(2,memory_order_relaxed); // b 的賦值不依賴 a
}
int Observer(int)
{
    cout<<"("<<a<<","<<b<<")"<<endl;
}
int main()
{
    thread t1(ValueSet,0);
    thread t2(Observer,0);
    t1.join();
    t2.join();
    
    
    cout<<"Final: ("<<a<<","<<b<<")"<<endl;
}

在上面代碼中的Observer()中輸出結(jié)果是有可能是:(0,2)的,因?yàn)檫@里的memory_order_relaxed不對原子操作的順序有嚴(yán)格要求,就有可能發(fā)生b先被賦值了,而此時(shí)a還沒被賦值的情況。

所以,為了進(jìn)一步開發(fā)原子操作的并行程度,我們的目標(biāo)是:保證程序既快又對。

2.2 release-acquire內(nèi)存順序

#include <thread>
#include <atomic>
#include<iostream>
using namespace std;
atomic<int> a;
atomic<int> b;
void Thread1(int )
{
     int t = 1;
     a.store(t,memory_order_relaxed);
     b.store(2,memory_order_release); // 本操作前的寫操作必須先完成,即保證a的賦值快于b
}
void Thread2(int )
{
    while(b.load(memory_order_acquire)!=2);//必須等該原子操作完成后,才執(zhí)行下面代碼
    cout<<a.load(memory_order_relaxed)<<endl;//1

}
int main()
{
    thread t1(Thread1,0);
    thread t2(Thread2,0);
    t2.join();
    t1.join();
}

上面代碼中,實(shí)際上也是實(shí)現(xiàn)了一種自旋鎖的操作,我們保證了a.store快于b.store,而b.load又一定快于a.load。而且,對于b的store和load就實(shí)現(xiàn)了一種release-acquire內(nèi)存順序.

2.3 release-consume內(nèi)存順序

#include<thread>
#include<atomic>
#include<cassert>
#include<string>
using namespace std;

atomic<string*> ptr;
atomic<int> date;
void Producer()
{
    string *p=new string("hello");
    date.store(42,memory_order_relaxed);
    ptr.store(p,memory_order_release);//date賦值快于ptr
}
void Consumer()
{
    string *p2;
    while(!(p2=ptr.load(memory_order_consume)));
    assert(*p2=="hello");//一定成立
    assert(date.load(memory_order_relaxed)==42);//可能斷言失敗,因?yàn)檫@個(gè)指令可能在本線程中首先執(zhí)行
}
int main()
{
    thread t1(Producer);
    thread t2(Consumer);
    t1.join();
    t2.join();
}

上面的內(nèi)存順序也叫生產(chǎn)者-消費(fèi)者順序。

實(shí)際上,總共的內(nèi)存模型就是4個(gè):順序一致性,松散的(relaxed),release-consume和release-acquire。

2.4 小結(jié)

實(shí)際上,對于并行編程來說,最根本的的在于,并行算法,而不是從硬件上搞內(nèi)存模型優(yōu)化啥的,如果你嫌麻煩的話,全部使用順序一致性內(nèi)存模型,對并行效率的影響也不是很大。

3.線程局部存儲(chǔ)

線程擁有自己的??臻g,但是堆空間,靜態(tài)數(shù)據(jù)區(qū)(文件data,bss段,全局/靜態(tài)變量)是共享的。線程之間互相共享,靜態(tài)數(shù)據(jù)當(dāng)然是很好的,但是我們也需要線程自己的局部變量

#include<pthread.h>
#include<iostream>
using namespace std;

int thread_local errorCode=0;
void* MaySetErr(void *input)
{
    if(*(int*)input==1)
        errorCode=1;
    else if(*(int*)input==2)
        errorCode=2;
    else
        errorCode=0;
    cout<<errorCode<<endl;
}
int main()
{
    int input_a=1;
    int input_b=2;
    pthread_t thread1,thread2;
    pthread_create(&thread1,nullptr,&MaySetErr,&input_a);
    pthread_create(&thread2,nullptr,&MaySetErr,&input_b);
    pthread_join(thread1,nullptr);
    pthread_join(thread2,nullptr);
    cout<<errorCode<<endl;//0
}

上面代碼中的errorCode是一個(gè)thread_local變量,它意味著它是一個(gè)線程內(nèi)部的全局變量,線程開始時(shí),他會(huì)被初始化,然后線程結(jié)束時(shí),該值就不會(huì)有效。實(shí)際上兩個(gè)進(jìn)程中會(huì)有各自的errorCode,而main函數(shù)中也有自己的errorCode

4.快速退出

在C++98中,我們會(huì)見到3中終止函數(shù):terminate,abort,exit。而在C++11中我們增加了quick_exit終止函數(shù),這種終止函數(shù)主要用在線程種。

1.terminate函數(shù),它是C++種的異常機(jī)制有關(guān)的,通常沒有被捕獲的異常就會(huì)調(diào)用terminate

2.abort函數(shù)是底層的終止函數(shù),terminate就是調(diào)用它來終止進(jìn)程的,但是abort調(diào)用時(shí),不會(huì)調(diào)用任何析構(gòu)函數(shù),會(huì)引發(fā)內(nèi)存泄漏啥的,但是一般來說,他會(huì)給符合POSIX的操作系統(tǒng)拋出一個(gè)信號,此時(shí)signal handler就會(huì)默認(rèn)的釋放進(jìn)程種的所有資源,來避免內(nèi)存泄漏。

3.exit函數(shù)是正常退出,他會(huì)調(diào)用析構(gòu)函數(shù),但是有時(shí)候析構(gòu)函數(shù)狠復(fù)雜,那我們還不如直接調(diào)用absort函數(shù),將釋放資源的事情留給操作系統(tǒng)。

在多線程情況下,我們一般都是采用exit來退出的,但是這樣容易卡死,當(dāng)線程復(fù)雜的時(shí)候,exit這種正常退出方式,太過于保守了,但是abort這種退出方式又太激進(jìn)了,所以有一種新的退出函數(shù):quick_exit函數(shù)。

quick_exit

這個(gè)函數(shù)不執(zhí)行析構(gòu)函數(shù),而使得程序終止,但是和abort不同的是,abort一般都是異常退出,而quick_exit是正常退出。

#include<cstdlib>
#include<iostream>
using namespace std;

struct A{~A(){cout<<"Destruct A."<<endl;}};
void closeDevice(){cout<<"device is closed."<<endl;}
int main()
{
    A a;
    at_quick_exit(closeDevice);
    quick_exit(0);
}
//樣容易卡死,當(dāng)線程復(fù)雜的時(shí)候,`exit`這種正常退出方式,太過于保守了,但是`abort`這種退出方式又太激進(jìn)了,所以有一種新的退出函數(shù):`quick_exit`函數(shù)。
//這個(gè)函數(shù)不執(zhí)行析構(gòu)函數(shù),而使得程序終止,但是和`abort`不同的是,`abort`一般都是異常退出,而`quick_exit`是正常退出。
#include<cstdlib>
#include<iostream>
using namespace std;

struct A{~A(){cout<<"Destruct A."<<endl;}};
void closeDevice(){cout<<"device is closed."<<endl;}
int main()
{
    A a;
    at_quick_exit(closeDevice);
    quick_exit(0);
}

以上就是C++11學(xué)習(xí)之多線程的支持詳解的詳細(xì)內(nèi)容,更多關(guān)于C++11多線程的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C語言 二叉查找樹性質(zhì)詳解及實(shí)例代碼

    C語言 二叉查找樹性質(zhì)詳解及實(shí)例代碼

    這篇文章主要介紹了C語言 二叉查找樹性質(zhì)詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下
    2017-03-03
  • Qt專欄之模態(tài)與非模態(tài)對話框的實(shí)現(xiàn)

    Qt專欄之模態(tài)與非模態(tài)對話框的實(shí)現(xiàn)

    這篇文章主要介紹了Qt專欄之模態(tài)與非模態(tài)對話框的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • C語言編程實(shí)例之輸出指定圖形問題

    C語言編程實(shí)例之輸出指定圖形問題

    這篇文章主要介紹了C語言編程實(shí)例之輸出指定圖形問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • C語言字符串旋轉(zhuǎn)問題的深入講解

    C語言字符串旋轉(zhuǎn)問題的深入講解

    這篇文章主要給大家介紹了關(guān)于C語言字符串旋轉(zhuǎn)問題的相關(guān)資料,文中給出了詳細(xì)的實(shí)現(xiàn)方法,并對每種方法進(jìn)行了分析和示例代碼,需要的朋友可以參考下
    2021-09-09
  • Qt實(shí)現(xiàn)手動(dòng)切換多種布局的完美方案

    Qt實(shí)現(xiàn)手動(dòng)切換多種布局的完美方案

    通過點(diǎn)擊程序界面上不同的布局按鈕,使主工作區(qū)呈現(xiàn)出不同的頁面布局,多個(gè)布局之間可以通過點(diǎn)擊不同布局按鈕切換,支持的最多的窗口為9個(gè),不同布局下窗口數(shù)隨之變化,這篇文章主要介紹了Qt實(shí)現(xiàn)手動(dòng)切換多種布局的完美方案,需要的朋友可以參考下
    2024-07-07
  • C++11 線程同步接口std::condition_variable和std::future的簡單使用示例詳解

    C++11 線程同步接口std::condition_variable和std::future的簡單使用示例詳

    本文介紹了std::condition_variable和std::future在C++中的應(yīng)用,用于線程間的同步和異步執(zhí)行,通過示例代碼,展示了如何使用std::condition_variable的wait和notify接口進(jìn)行線程間同步
    2024-09-09
  • C語言編程C++旋轉(zhuǎn)字符操作串示例詳解

    C語言編程C++旋轉(zhuǎn)字符操作串示例詳解

    這篇文章主要為大家介紹了C語言編程中C++旋轉(zhuǎn)字符操作串示例詳解,文中附含詳細(xì)圖文示例代碼,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2021-09-09
  • 淺談使用C++多級指針存儲(chǔ)海量qq號和密碼

    淺談使用C++多級指針存儲(chǔ)海量qq號和密碼

    這篇文章主要介紹了淺談使用C++多級指針存儲(chǔ)海量qq號和密碼,分享了相關(guān)實(shí)例代碼,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-01-01
  • C++中回調(diào)函數(shù)及函數(shù)指針的實(shí)例詳解

    C++中回調(diào)函數(shù)及函數(shù)指針的實(shí)例詳解

    這篇文章主要介紹了C++中回調(diào)函數(shù)及函數(shù)指針的實(shí)例詳解的相關(guān)資料,希望通過本文能幫助到大家,讓大家理解掌握這部分內(nèi)容,需要的朋友可以參考下
    2017-10-10
  • C++ OpenCV制作黑客帝國風(fēng)格的照片

    C++ OpenCV制作黑客帝國風(fēng)格的照片

    這篇文章主要介紹了如何通過C++ OpenCV制作出黑客帝國風(fēng)格的照片,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)OpenCV有一定幫助,需要的可以參考一下
    2022-01-01

最新評論