最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++多線(xiàn)程std::call_once的使用

 更新時(shí)間:2022年03月11日 15:54:04   作者:Codemaxi  
本文主要介紹了C++多線(xiàn)程std::call_once的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

在多線(xiàn)程的環(huán)境下,有些時(shí)候我們不需要某個(gè)函數(shù)被調(diào)用多次或者某些變量被初始化多次,它們僅僅只需要被調(diào)用一次或者初始化一次即可。很多時(shí)候我們?yōu)榱顺跏蓟承?shù)據(jù)會(huì)寫(xiě)出如下代碼,這些代碼在單線(xiàn)程中是沒(méi)有任何問(wèn)題的,但是在多線(xiàn)程中就會(huì)出現(xiàn)不可預(yù)知的問(wèn)題。

bool initialized = false;
void foo() {
    if (!initialized) {
        do_initialize ();  //1
        initialized = true;
    }
}

為了解決上述多線(xiàn)程中出現(xiàn)的資源競(jìng)爭(zhēng)導(dǎo)致的數(shù)據(jù)不一致問(wèn)題,我們大多數(shù)的處理方法就是使用互斥鎖來(lái)處理。只要上面①處進(jìn)行保護(hù),這樣共享數(shù)據(jù)對(duì)于并發(fā)訪問(wèn)就是安全的。如下:

bool initialized = false;
std::mutex resource_mutex;

void foo() {
    std::unique_lock<std::mutex> lk(resource_mutex);  // 所有線(xiàn)程在此序列化 
    if(!initialized) {
        do_initialize ();  // 只有初始化過(guò)程需要保護(hù) 
    }
    initialized = true;
    lk.unlock();
    // do other;
}

但是,為了確保數(shù)據(jù)源已經(jīng)初始化,每個(gè)線(xiàn)程都必須等待互斥量。為此,還有人想到使用“雙重檢查鎖模式”的辦法來(lái)提高效率,如下:

bool initialized = false;
std::mutex resource_mutex;

void foo() {
    if(!initialized) {  // 1
        std::unique_lock<std::mutex> lk(resource_mutex);  // 2 所有線(xiàn)程在此序列化 
        if(!initialized) {
            do_initialize ();  // 3 只有初始化過(guò)程需要保護(hù) 
        }
        initialized = true;
    }
    // do other;  // 4
}

第一次讀取變量initialized時(shí)不需要獲取鎖①,并且只有在initialized為false時(shí)才需要獲取鎖。然后,當(dāng)獲取鎖之后,會(huì)再檢查一次initialized變量② (這就是雙重檢查的部分),避免另一線(xiàn)程在第一次檢查后再做初始化,并且讓當(dāng)前線(xiàn)程獲取鎖。

但是上面這種情況也存在一定的風(fēng)險(xiǎn),具體可以查閱著名的《C++和雙重檢查鎖定模式(DCLP)的風(fēng)險(xiǎn)》。

對(duì)此,C++標(biāo)準(zhǔn)委員會(huì)也認(rèn)為條件競(jìng)爭(zhēng)的處理很重要,所以C++標(biāo)準(zhǔn)庫(kù)提供了更好的處理方法:使用std::call_once函數(shù)來(lái)處理,其定義在頭文件#include<mutex>中。std::call_once函數(shù)配合std::once_flag可以實(shí)現(xiàn):多個(gè)線(xiàn)程同時(shí)調(diào)用某個(gè)函數(shù),它可以保證多個(gè)線(xiàn)程對(duì)該函數(shù)只調(diào)用一次。它的定義如下:

struct once_flag
{
    constexpr once_flag() noexcept;
    once_flag(const once_flag&) = delete;
    once_flag& operator=(const once_flag&) = delete;
};

template<class Callable, class ...Args>
void call_once(once_flag& flag, Callable&& func, Args&&... args);

他接受的第一個(gè)參數(shù)類(lèi)型為std::once_flag,它只用默認(rèn)構(gòu)造函數(shù)構(gòu)造,不能拷貝不能移動(dòng),表示函數(shù)的一種內(nèi)在狀態(tài)。后面兩個(gè)參數(shù)很好理解,第一個(gè)傳入的是一個(gè)Callable。Callable簡(jiǎn)單來(lái)說(shuō)就是可調(diào)用的東西,大家熟悉的有函數(shù)、函數(shù)對(duì)象(重載了operator()的類(lèi))、std::function和函數(shù)指針,C++11新標(biāo)準(zhǔn)中還有std::bindlambda(可以查看我的上一篇文章)。最后一個(gè)參數(shù)就是你要傳入的參數(shù)。 在使用的時(shí)候我們只需要定義一個(gè)non-local的std::once_flag(非函數(shù)局部作用域內(nèi)的),在調(diào)用時(shí)傳入?yún)?shù)即可,如下所示:

#include <iostream>
#include <thread>
#include <mutex>
 
std::once_flag flag1;
void simple_do_once() {
    std::call_once(flag1, [](){ std::cout << "Simple example: called once\n"; });
}
 
int main() {
    std::thread st1(simple_do_once);
    std::thread st2(simple_do_once);
    std::thread st3(simple_do_once);
    std::thread st4(simple_do_once);
    st1.join();
    st2.join();
    st3.join();
    st4.join();
}

call_once保證函數(shù)func只被執(zhí)行一次,如果有多個(gè)線(xiàn)程同時(shí)執(zhí)行函數(shù)func調(diào)用,則只有一個(gè)活動(dòng)線(xiàn)程(active call)會(huì)執(zhí)行函數(shù),其他的線(xiàn)程在這個(gè)線(xiàn)程執(zhí)行返回之前會(huì)處于”passive execution”(被動(dòng)執(zhí)行狀態(tài))——不會(huì)直接返回,直到活動(dòng)線(xiàn)程對(duì)func調(diào)用結(jié)束才返回。對(duì)于所有調(diào)用函數(shù)func的并發(fā)線(xiàn)程,數(shù)據(jù)可見(jiàn)性都是同步的(一致的)。

但是,如果活動(dòng)線(xiàn)程在執(zhí)行func時(shí)拋出異常,則會(huì)從處于”passive execution”狀態(tài)的線(xiàn)程中挑一個(gè)線(xiàn)程成為活動(dòng)線(xiàn)程繼續(xù)執(zhí)行func,依此類(lèi)推。一旦活動(dòng)線(xiàn)程返回,所有”passive execution”狀態(tài)的線(xiàn)程也返回,不會(huì)成為活動(dòng)線(xiàn)程。(實(shí)際上once_flag相當(dāng)于一個(gè)鎖,使用它的線(xiàn)程都會(huì)在上面等待,只有一個(gè)線(xiàn)程允許執(zhí)行。如果該線(xiàn)程拋出異常,那么從等待中的線(xiàn)程中選擇一個(gè),重復(fù)上面的流程)。

std::call_once在簽名設(shè)計(jì)時(shí)也很好地考慮到了參數(shù)傳遞的開(kāi)銷(xiāo)問(wèn)題,可以看到,不管是Callable還是Args,都使用了&&作為形參。他使用了一個(gè)template中的reference fold(我前面的文章也有介紹過(guò)),簡(jiǎn)單分析:

  • 如果傳入的是一個(gè)右值,那么Args將會(huì)被推斷為Args;
  • 如果傳入的是一個(gè)const左值,那么Args將會(huì)被推斷為const Args&;
  • 如果傳入的是一個(gè)non-const的左值,那么Args將會(huì)被推斷為Args&。

也就是說(shuō),不管你傳入的參數(shù)是什么,最終到達(dá)std::call_once內(nèi)部時(shí),都會(huì)是參數(shù)的引用(右值引用或者左值引用),所以說(shuō)是零拷貝的。那么還有一步呢,我們還得把參數(shù)傳到可調(diào)用對(duì)象里面執(zhí)行我們要執(zhí)行的函數(shù),這一步同樣做到了零拷貝,這里用到了另一個(gè)標(biāo)準(zhǔn)庫(kù)的技術(shù)std::forward(我前面的文章也有介紹過(guò))。

如下,如果在函數(shù)執(zhí)行中拋出了異常,那么會(huì)有另一個(gè)在once_flag上等待的線(xiàn)程會(huì)執(zhí)行。

#include <iostream>
#include <thread>
#include <mutex>
 
std::once_flag flag;
inline void may_throw_function(bool do_throw) {
    // only one instance of this function can be run simultaneously
    if (do_throw) {
        std::cout << "throw\n"; // this message may be printed from 0 to 3 times
        // if function exits via exception, another function selected
        throw std::exception();
    }

    std::cout << "once\n"; // printed exactly once, it's guaranteed that
    // there are no messages after it
}
 
inline void do_once(bool do_throw) {
    try {
        std::call_once(flag, may_throw_function, do_throw);
    } catch (...) {
    }
}
 
int main() {
    std::thread t1(do_once, true);
    std::thread t2(do_once, true);
    std::thread t3(do_once, false);
    std::thread t4(do_once, true);
 
    t1.join();
    t2.join();
    t3.join();
    t4.join();
}

std::call_once 也可以用在類(lèi)中:

#include <iostream>
#include <mutex>
#include <thread>

class A {
 public:
  void f() {
    std::call_once(flag_, &A::print, this);
    std::cout << 2;
  }

 private:
  void print() { std::cout << 1; }

 private:
  std::once_flag flag_;
};

int main() {
  A a;
  std::thread t1{&A::f, &a};
  std::thread t2{&A::f, &a};
  t1.join();
  t2.join();
}  // 122

還有一種初始化過(guò)程中潛存著條件競(jìng)爭(zhēng):static 局部變量在聲明后就完成了初始化,這存在潛在的 race condition,如果多線(xiàn)程的控制流同時(shí)到達(dá) static 局部變量的聲明處,即使變量已在一個(gè)線(xiàn)程中初始化,其他線(xiàn)程并不知曉,仍會(huì)對(duì)其嘗試初始化。很多在不支持C++11標(biāo)準(zhǔn)的編譯器上,在實(shí)踐過(guò)程中,這樣的條件競(jìng)爭(zhēng)是確實(shí)存在的,為此,C++11 規(guī)定,如果 static 局部變量正在初始化,線(xiàn)程到達(dá)此處時(shí),將等待其完成,從而避免了 race condition,只有一個(gè)全局實(shí)例時(shí),對(duì)于C++11,可以直接用 static 而不需要 std::call_once,也就是說(shuō),在只需要一個(gè)全局實(shí)例情況下,可以成為std::call_once的替代方案,典型的就是單例模式了:

template <typename T>
class Singleton {
 public:
  static T& Instance();
  Singleton(const Singleton&) = delete;
  Singleton& operator=(const Singleton&) = delete;

 private:
  Singleton() = default;
  ~Singleton() = default;
};

template <typename T>
T& Singleton<T>::Instance() {
  static T instance;
  return instance;
}

今天的內(nèi)容就到這里了。

參考:

std::call_once - C++中文 - API參考文檔 (apiref.com)

到此這篇關(guān)于C++多線(xiàn)程std::call_once的使用的文章就介紹到這了,更多相關(guān)C++ std::call_once內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • FFmpeg實(shí)現(xiàn)將編碼后數(shù)據(jù)保存成mp4

    FFmpeg實(shí)現(xiàn)將編碼后數(shù)據(jù)保存成mp4

    這篇文章主要為大家詳細(xì)介紹了FFmpeg如何實(shí)現(xiàn)將編碼后數(shù)據(jù)保存成mp4,即從內(nèi)存塊中獲取原始數(shù)據(jù),然后依次進(jìn)行解碼、編碼、最后保存成mp4視頻文件,感興趣的可以了解一下
    2023-08-08
  • C++中構(gòu)造函數(shù)詳解

    C++中構(gòu)造函數(shù)詳解

    大家好,本篇文章主要講的是C++中構(gòu)造函數(shù)詳解,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話(huà)記得收藏一下
    2022-02-02
  • 關(guān)于C語(yǔ)言和命令行之間的交互問(wèn)題

    關(guān)于C語(yǔ)言和命令行之間的交互問(wèn)題

    這篇文章主要介紹了C語(yǔ)言和命令行之間的交互,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-07-07
  • C語(yǔ)言與C++動(dòng)態(tài)通訊錄超詳細(xì)實(shí)現(xiàn)流程

    C語(yǔ)言與C++動(dòng)態(tài)通訊錄超詳細(xì)實(shí)現(xiàn)流程

    這篇文章主要為大家介紹了C語(yǔ)言與C++動(dòng)態(tài)實(shí)現(xiàn)通訊錄,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-05-05
  • c++中引用作為形參的使用方法以及作用

    c++中引用作為形參的使用方法以及作用

    這篇文章主要給大家介紹了關(guān)于c++中引用作為形參的使用方法以及作用的相關(guān)資料,引用是地址傳值,作為引用的形參數(shù)值被修改的同時(shí),也修改了對(duì)應(yīng)實(shí)參的值,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-11-11
  • 基于C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的掃雷小游戲

    基于C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的掃雷小游戲

    這篇文章主要為大家詳細(xì)介紹了基于C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的掃雷小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • Opencv?視頻讀取與寫(xiě)入的實(shí)現(xiàn)示例

    Opencv?視頻讀取與寫(xiě)入的實(shí)現(xiàn)示例

    本文將介紹如何使用OpenCV進(jìn)行視頻讀寫(xiě)。通過(guò)閱讀本文,您將了解如何讀取視頻文件、處理視頻幀、寫(xiě)入視頻文件等操作,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-08-08
  • C語(yǔ)言中volatile關(guān)鍵字的深入講解

    C語(yǔ)言中volatile關(guān)鍵字的深入講解

    在程序設(shè)計(jì)中,尤其是在C語(yǔ)言、C++、C#和Java語(yǔ)言中,使用volatile關(guān)鍵字聲明的變量或?qū)ο笸ǔ>哂信c優(yōu)化、多線(xiàn)程相關(guān)的特殊屬性,這篇文章主要給大家介紹了關(guān)于C語(yǔ)言volatile關(guān)鍵字的相關(guān)資料,需要的朋友可以參考下
    2021-07-07
  • C++入門(mén)到精通之循環(huán)語(yǔ)句的使用教程

    C++入門(mén)到精通之循環(huán)語(yǔ)句的使用教程

    這篇文章主要給大家介紹了關(guān)于C++中循環(huán)語(yǔ)句的用法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • 關(guān)于C++的強(qiáng)制類(lèi)型轉(zhuǎn)換淺析

    關(guān)于C++的強(qiáng)制類(lèi)型轉(zhuǎn)換淺析

    C++的強(qiáng)制類(lèi)型轉(zhuǎn)換是我們?cè)谌粘i_(kāi)發(fā)中經(jīng)常會(huì)遇到的,下面這篇文章主要給大家介紹了關(guān)于C++強(qiáng)制類(lèi)型轉(zhuǎn)換的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-09-09

最新評(píng)論

晋江市| 长宁区| 定日县| 鹤岗市| 巴彦县| 桂平市| 内乡县| 磐安县| 亳州市| 固原市| 稻城县| 自贡市| 伊宁市| 容城县| 黄浦区| 远安县| 汾西县| 阿拉善右旗| 广灵县| 迁西县| 繁昌县| 临桂县| 虹口区| 原平市| 乐昌市| 阿坝县| 德清县| 龙江县| 靖安县| 静宁县| 大冶市| 葵青区| 白山市| 吴旗县| 清徐县| 乐都县| 铁力市| 乌鲁木齐县| 四平市| 威宁| 宝山区|