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

C++11 call_once 和 once_flag的使用與區(qū)別

 更新時間:2023年06月02日 15:10:08   作者:小立愛學習  
本文主要介紹了C++11 call_once 和 once_flag的使用與區(qū)別,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

一、簡介

std::call_once 和 std::once_flag 是 C++11 中引入的線程安全的函數(shù)和類型,用于確保某個函數(shù)只被調用一次。

std::once_flag 是一個類型,用于標記一段代碼是否已經(jīng)被執(zhí)行過。它必須通過引用傳遞給 std::call_once 函數(shù),以確保在多線程環(huán)境下僅僅執(zhí)行一次。

std::call_once 函數(shù)接受兩個參數(shù):一個可調用對象(可以是函數(shù)、lambda 表達式等)和一個 std::once_flag 對象的引用。該函數(shù)會檢查 std::once_flag 對象是否被設置過,如果沒有,就調用可調用對象,并設置 std::once_flag 對象為已設置狀態(tài)。

使用 std::call_once 和 std::once_flag 可以避免在多線程環(huán)境下多次執(zhí)行同一個函數(shù),從而提高程序性能和正確性。

下面是一個簡單的示例:

#include <iostream>
#include <thread>
#include <mutex>
std::once_flag flag;
void do_something()
{
?? ?//call_once中的 lambda 表達式只執(zhí)行一次
? ? std::call_once(flag, []() {
? ? ? ? std::cout << "do_something() called once" << std::endl;
? ? });
? ? std::cout << "Thread id" << std::this_thread::get_id() << std::endl;
}
int main()
{
? ? std::thread t1(do_something);
? ? std::thread t2(do_something);
? ? t1.join();
? ? t2.join();
? ? return 0;
}

在這個例子中,我們定義了一個名為 do_something 的函數(shù),并將其作為參數(shù)傳遞給 std::call_once 函數(shù)。 std::once_flag 對象被聲明為全局變量,以便在多個線程之間共享。

當?shù)谝淮握{用 do_something 函數(shù)時,std::call_once 將檢查 std::once_flag 是否已經(jīng)被設置過。由于初始狀態(tài)為未設置,因此 std::call_once 將執(zhí)行提供的可調用對象——這里是一個 lambda 表達式,輸出一條消息表示函數(shù)被調用了一次。

當?shù)诙握{用 do_something 函數(shù)時,std::call_once 將不再執(zhí)行提供的可調用對象,因為 std::once_flag 已經(jīng)被設置過。

通過這種方式,我們可以確保 do_something 函數(shù)中std::call_once 提供的可調用對象被調用一次,無論有多少個線程同時調用它。

/modern_c++$ ./a.out 
do_something() called once
Thread id139891421738688
Thread id139891413345984

二、原理

2.1 示例

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
class Singleton
{
public:
? ? //使用了 std::call_once 函數(shù),因此在多個線程同時調用時,只有一個線程會創(chuàng)建單例對象 instance_,即只有一個線程執(zhí)行函數(shù)init()
? ? //其他線程會直接返回之前創(chuàng)建的單例對象 instance_,從而保證單例對象只被創(chuàng)建一次
? ? static Singleton& getInstance()
? ? {
? ? ? ? std::call_once(flag_, &Singleton::init);
? ? ? ? return *instance_;
? ? }
? ? Singleton(const Singleton&) = delete;
? ? Singleton& operator=(const Singleton&) = delete;
private:
? ? Singleton() { std::cout << "Singleton instance created.\n"; }
? ? static void init()
? ? {
? ? ? ? instance_ = new Singleton();
? ? }
? ? //在類的定義中,一個靜態(tài)成員變量必須由該類聲明為static
? ? //并且通常還需要在類外初始化,這意味著在類的定義中僅指定其類型和名稱
? ? static std::once_flag flag_;
? ? static Singleton* instance_;
};
//在 class 外初始化 static 成員變量
std::once_flag Singleton::flag_;
Singleton* Singleton::instance_ = nullptr;
void thread_func()
{
? ? //調用 Singleton::getInstance() 函數(shù)來獲取單例對象的引用
? ? Singleton& singleton = Singleton::getInstance();
? ? std::cout << "Singleton instance address: " << &singleton << "\n";
}
int main()
{
? ? std::vector<std::thread> threads;
? ? const int num_threads = 10;
? ? for (int i = 0; i < num_threads; ++i)
? ? {
? ? ? ? //threads將 `thread_func` 函數(shù)作為線程函數(shù),創(chuàng)建多個線程并啟動它們:
? ? ? ? threads.emplace_back(thread_func);
? ? }
? ? for (auto& t : threads)
? ? {
? ? ? ? t.join();
? ? }
? ? return 0;
}
modern_c++$ ./a.out 
Singleton instance created.
Singleton instance address: 0x7f1310000b70
Singleton instance address: 0x7f1310000b70
Singleton instance address: 0x7f1310000b70
Singleton instance address: 0x7f1310000b70
Singleton instance address: 0x7f1310000b70
Singleton instance address: 0x7f1310000b70
Singleton instance address: 0x7f1310000b70
Singleton instance address: 0x7f1310000b70
Singleton instance address: 0x7f1310000b70
Singleton instance address: 0x7f1310000b70

在上面的代碼中,我們使用 std::call_once 函數(shù)來保證單例模式在多線程環(huán)境中的正確性。當?shù)谝粋€線程調用 getInstance() 函數(shù)時,會執(zhí)行 init() 函數(shù)來創(chuàng)建單例對象,同時將 flag_ 標志位設置為“已調用”。在后續(xù)的調用中,std::call_once 函數(shù)會檢查 flag_ 標志位是否已經(jīng)被設置,如果已經(jīng)被設置,則直接返回之前創(chuàng)建的單例對象,不會再次執(zhí)行 init() 函數(shù),從而保證單例對象只被創(chuàng)建一次。

在 main() 函數(shù)中,我們創(chuàng)建了多個線程,并將 thread_func 函數(shù)作為線程函數(shù),分別啟動這些線程。在 thread_func 函數(shù)中,我們調用 Singleton::getInstance() 函數(shù)來獲取單例對象的引用,并輸出它的地址。由于 getInstance() 函數(shù)使用了 std::call_once 函數(shù),因此在多個線程同時調用時,只有一個線程會創(chuàng)建單例對象,其他線程會直接返回之前創(chuàng)建的單例對象,從而保證單例對象只被創(chuàng)建一次。

使用 std::call_once 函數(shù)可以非常方便地實現(xiàn)線程安全的單例模式,通過在多個線程同時調用時只創(chuàng)建一個對象來避免資源競爭和數(shù)據(jù)不一致的問題。在多線程環(huán)境中使用單例模式時,可以將 getInstance() 函數(shù)作為線程函數(shù),在多個線程中同時調用,以驗證單例對象的創(chuàng)建情況。

示例代碼中的析構函數(shù)只會執(zhí)行一次,因為 Singleton::init函數(shù)只執(zhí)行一次:

  static void init()
  {
      instance_ = new Singleton();
  }

在這個例子中,Singleton 的構造函數(shù)只會執(zhí)行一次,是因為在使用 std::call_once 函數(shù)時,該函數(shù)會使用一個 std::once_flag 類型的變量來標記是否已經(jīng)執(zhí)行過初始化函數(shù),從而保證初始化函數(shù)只會被執(zhí)行一次。

具體來說,當多個線程同時調用 Singleton::getInstance() 函數(shù)時,只有其中一個線程會執(zhí)行 std::call_once 函數(shù)指定的初始化函數(shù) &Singleton::init,其他線程會阻塞等待初始化函數(shù)執(zhí)行完畢。初始化函數(shù)執(zhí)行完畢之后,所有線程都會返回之前創(chuàng)建的單例對象 instance_ 的引用,從而保證單例對象只被創(chuàng)建一次。

在這個例子中,Singleton 的構造函數(shù)在初始化函數(shù) &Singleton::init 中被調用,因此只會被執(zhí)行一次。在其他線程中,由于 instance_ 已經(jīng)被創(chuàng)建,因此不會再次調用構造函數(shù)。

備注:在C++中,類的靜態(tài)成員變量是與類相關聯(lián)的變量,而不是與對象相關聯(lián)的。它們被視為該類的所有對象共享的變量,并且只有一個副本存在于內存中。靜態(tài)成員變量通常用于跟蹤某些信息,例如,表示所有實例之間共享的計數(shù)器或全局配置設置等。

在類的定義中,一個靜態(tài)成員變量必須由該類聲明為static,并且通常還需要在類外初始化,這意味著在類的定義中僅指定其類型和名稱。

2.2 call_once源碼詳解

? template<typename _Callable, typename... _Args>
? ? void
? ? call_once(once_flag& __once, _Callable&& __f, _Args&&... __args)
? ? {
? ? ? // Closure type that runs the function
? ? ? auto __callable = [&] {
?? ? ?std::__invoke(std::forward<_Callable>(__f),
?? ??? ??? ?std::forward<_Args>(__args)...);
? ? ? };
? ? ? once_flag::_Prepare_execution __exec(__callable);
? ? ? // XXX pthread_once does not reset the flag if an exception is thrown.
? ? ? if (int __e = __gthread_once(&__once._M_once, &__once_proxy))
?? ?__throw_system_error(__e);
? ? }

std::call_once 函數(shù)是一個 C++ 標準庫函數(shù),它接受三個參數(shù):
(1)std::once_flag& flag:一個標志位對象的引用,用于記錄該函數(shù)是否已經(jīng)被調用過。
(2)Callable&& func:一個可調用對象,即函數(shù)或函數(shù)對象,用于執(zhí)行需要僅執(zhí)行一次的代碼。
(3)Args&&… args:可變模板參數(shù)包,用于傳遞給 func 函數(shù)的參數(shù)。

函數(shù)的實現(xiàn)分為以下步驟:
(1)創(chuàng)建一個 lambda 表達式 __callable,該表達式調用 std::__invoke 函數(shù)來執(zhí)行 __f 函數(shù)并傳遞參數(shù) __args…。
(2)創(chuàng)建一個 once_flag::_Prepare_execution 對象 __exec,該對象將在析構時執(zhí)行 __callable。
(3)調用 __gthread_once 函數(shù)來執(zhí)行一次性操作,如果操作已經(jīng)被執(zhí)行過,則不執(zhí)行。如果在執(zhí)行過程中發(fā)生異常,則不會重置 __once 標志位。
(4)如果 __gthread_once 函數(shù)返回一個非0 的值,則拋出一個系統(tǒng)錯誤異常。

下面是對代碼實現(xiàn)的詳細解釋:

template<typename _Callable, typename... _Args>
void call_once(once_flag& __once, _Callable&& __f, _Args&&... __args)
{
? // 創(chuàng)建一個可調用對象 __callable,該對象調用 __f 函數(shù)并傳遞參數(shù) __args...
? auto __callable = [&] {
? ? std::__invoke(std::forward<_Callable>(__f), std::forward<_Args>(__args)...);
? };
? // 創(chuàng)建一個 __exec 對象,并在其析構時調用 __callable
? once_flag::_Prepare_execution __exec(__callable);
? // 調用 __gthread_once 函數(shù)執(zhí)行一次性操作
? if (int __e = __gthread_once(&__once._M_once, &__once_proxy))
? ? __throw_system_error(__e);
}

在實現(xiàn)中,首先使用 lambda 表達式創(chuàng)建了一個可調用對象 __callable,該對象調用 std::__invoke 函數(shù)來執(zhí)行傳入的可調用對象 __f 并傳遞參數(shù) __args…。這個可調用對象將在后續(xù)的線程安全的執(zhí)行中使用。

接著,創(chuàng)建了一個 once_flag::_Prepare_execution 對象 __exec,該對象的構造函數(shù)接受一個可調用對象,并在其析構時調用該對象。這個對象的作用是確保在 std::call_once 函數(shù)執(zhí)行結束后,可調用對象 __callable 被正確地執(zhí)行。

然后,調用了 __gthread_once 函數(shù)來執(zhí)行一次性操作。該函數(shù)接受兩個參數(shù):一個指向 __once._M_once 變量的指針,以及一個指向 __once_proxy 函數(shù)的指針。__once._M_once 是一個原子類型的變量,用于記錄一次性操作是否已經(jīng)被執(zhí)行過。__once_proxy 函數(shù)是一個輔助函數(shù),其作用是調用 __exec 對象的可調用對象。

如果 __gthread_once 函數(shù)返回一個非0 的值,則說明執(zhí)行失敗,此時會拋出一個系統(tǒng)錯誤異常。

需要注意的是,std::call_once 函數(shù)的實現(xiàn)依賴于操作系統(tǒng)和編譯器提供的線程庫。在不同的平臺和編譯器下,__gthread_once 函數(shù)的實現(xiàn)可能有所不同。但是,無論在哪個平臺和編譯器下,std::call_once 函數(shù)都會保證傳入的可調用對象只會被執(zhí)行一次。

2.3 once_flag源碼詳解

? /// Flag type used by std::call_once
? struct once_flag
? {
? ? constexpr once_flag() noexcept = default;
? ? /// Deleted copy constructor
? ? once_flag(const once_flag&) = delete;
? ? /// Deleted assignment operator
? ? once_flag& operator=(const once_flag&) = delete;
? private:
? ? // For gthreads targets a pthread_once_t is used with pthread_once, but
? ? // for most targets this doesn't work correctly for exceptional executions.
? ? __gthread_once_t _M_once = __GTHREAD_ONCE_INIT;
? ? struct _Prepare_execution;
? ? template<typename _Callable, typename... _Args>
? ? ? friend void
? ? ? call_once(once_flag& __once, _Callable&& __f, _Args&&... __args);
? };

once_flag 結構體通過提供同步機制來確保特定任務僅在首次執(zhí)行時被執(zhí)行一次,無論有多少個線程嘗試執(zhí)行它。它通過提供一個同步機制,允許線程等待特定任務被執(zhí)行,并在任務被第一次執(zhí)行后將其標記為已完成。

once_flag 結構體具有刪除的拷貝構造函數(shù)和賦值運算符,這意味著它不能從另一個 once_flag 實例中拷貝或賦值。

在內部,once_flag 結構體包含一個 _M_once 成員變量,類型為 __gthread_once_t,它由底層線程庫(在本例中為 gthreads)用于處理目標任務的同步和執(zhí)行。

call_once 函數(shù)是 once_flag 的友元函數(shù),它接受一個 once_flag 實例以及一個目標函數(shù)和其參數(shù)。它確保目標函數(shù)僅被執(zhí)行一次,并對調用它的所有線程進行同步訪問。

其中:

std::call_once 函數(shù)需要訪問 std::once_flag 類的私有成員 _M_once,以確保可調用對象只被執(zhí)行一次。但是,將 _M_once 成員聲明為公共成員會破壞 std::once_flag 類的封裝性,而將其聲明為私有成員則無法從 std::call_once 函數(shù)中訪問。

因此,為了解決這個問題,C++ 標準庫將 std::call_once 函數(shù)聲明為 std::once_flag 類的友元函數(shù)。這樣,std::call_once 函數(shù)就可以訪問 std::once_flag 類的私有成員 _M_once,而不會破壞 std::once_flag 類的封裝性。

通過將 std::call_once 聲明為 std::once_flag 類的友元函數(shù),可以保證 std::call_once 函數(shù)與 std::once_flag 類緊密地結合在一起,形成一個可靠的只執(zhí)行一次的函數(shù)機制。同時,它也使得使用 std::call_once 函數(shù)更加方便,用戶只需要提供一個 std::once_flag 對象和一個可調用對象作為參數(shù),即可實現(xiàn)只執(zhí)行一次的函數(shù)調用。

C++ 中友元函數(shù):
在 C++ 中,友元函數(shù)是一種特殊的函數(shù),它可以訪問類的私有成員和保護成員。友元函數(shù)可以作為類的非成員函數(shù)或其他類的成員函數(shù)來聲明。在函數(shù)聲明前加上 friend 關鍵字即可將其聲明為友元函數(shù)。

友元函數(shù)對于實現(xiàn)一些特殊的功能非常有用,例如操作符重載、單例模式、只執(zhí)行一次函數(shù)等等。友元函數(shù)可以訪問類的私有成員和保護成員,這使得它們可以直接操作類的內部數(shù)據(jù),而不需要通過類的公共接口來訪問。這樣可以提高程序的效率和靈活性,同時也可以保證類的封裝性不被破壞。

需要注意的是,友元函數(shù)不是類的成員函數(shù),因此它沒有 this 指針,也不能直接訪問類的成員變量和成員函數(shù)。友元函數(shù)可以通過類的對象、指針或引用來訪問類的成員變量和成員函數(shù),或者將類的成員變量和成員函數(shù)作為參數(shù)傳遞給友元函數(shù)。

總之,友元函數(shù)是一種特殊的函數(shù),它可以訪問類的私有成員和保護成員,但不是類的成員函數(shù)。友元函數(shù)可以通過類的對象、指針或引用來訪問類的成員變量和成員函數(shù),或者將類的成員變量和成員函數(shù)作為參數(shù)傳遞給友元函數(shù)。友元函數(shù)對于實現(xiàn)一些特殊的功能非常有用,但需要謹慎使用,以避免破壞類的封裝性。

三、Linux內核中的 DO_ONCE 機制

在Linux 內核中也有對應的機制:DO_ONCE 宏。

DO_ONCE 宏是 Linux 內核中實現(xiàn)一次性代碼執(zhí)行的一種機制,可以保證多個線程同時調用宏時只有一個線程會執(zhí)行代碼,從而避免了重復執(zhí)行的問題,并且可以確保代碼的正確性和可靠性。

到此這篇關于C++11 call_once 和 once_flag的使用與區(qū)別的文章就介紹到這了,更多相關C++11 call_once once_flag內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:

相關文章

  • C語言源碼實現(xiàn)俄羅斯方塊

    C語言源碼實現(xiàn)俄羅斯方塊

    這篇文章主要為大家詳細介紹了C語言源碼實現(xiàn)俄羅斯方塊,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-06-06
  • C++之std命名空間

    C++之std命名空間

    這篇文章主要介紹了C++之std命名空間使用,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • 從c++標準庫指針萃取器談一下traits技法(推薦)

    從c++標準庫指針萃取器談一下traits技法(推薦)

    本篇文章基于gcc中標準庫源碼剖析一下標準庫中的模板類pointer_traits,并且以此為例理解一下traits技法,對c++ traits技法源碼分析感興趣的朋友跟隨小編一起看看吧
    2021-07-07
  • C語言遞歸之漢諾塔和青蛙跳臺階問題

    C語言遞歸之漢諾塔和青蛙跳臺階問題

    這篇文章主要介紹了C語言遞歸之漢諾塔問題和青蛙跳臺階問題,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • C++中的字符串編碼處理方法

    C++中的字符串編碼處理方法

    這篇文章主要介紹了C++中的字符串編碼處理,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-05-05
  • c++11新特性多線程操作實戰(zhàn)

    c++11新特性多線程操作實戰(zhàn)

    這篇文章主要介紹了c++11新特性多線程操作實戰(zhàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-09-09
  • C++超詳細講解拷貝構造函數(shù)

    C++超詳細講解拷貝構造函數(shù)

    我們經(jīng)常會用一個變量去初始化一個同類型的變量,那么對于自定義的類型也應該有類似的操作,那么創(chuàng)建對象時如何使用一個已經(jīng)存在的對象去創(chuàng)建另一個與之相同的對象呢
    2022-06-06
  • 輸出1000以內的素數(shù)的算法(實例代碼)

    輸出1000以內的素數(shù)的算法(實例代碼)

    本篇文章是對輸出1000以內的素數(shù)的算法進行了詳細的分析介紹,需要的朋友參考下
    2013-05-05
  • C語言中strcmp的實現(xiàn)原型

    C語言中strcmp的實現(xiàn)原型

    這篇文章主要介紹了C語言中strcmp的實現(xiàn)原型的相關資料,這里提供實例幫助大家理解這部分內容,希望能幫助到大家,需要的朋友可以參考下
    2017-08-08
  • 深入解析C++中的mutable關鍵字

    深入解析C++中的mutable關鍵字

    在C++中,mutable也是為了突破const的限制而設置的。被mutable修飾的變量,將永遠處于可變的狀態(tài),即使在一個const函數(shù)中
    2013-10-10

最新評論