日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

解開C++之call_once的神秘面紗:記一個有意思的問題筆記

來源: 責編: 時間:2023-11-22 09:14:50 408觀看
導讀引言最近因為項目要求用c++,之前一直很討厭c++,沒辦法只能短時間彌補c++的知識,項目中需要一個接口只調用一次,需要使用到c++的call_once機制,于是寫一個小demo來測試,就因為這個足夠小發現了一個非常有意思的問題。call_on

引言

最近因為項目要求用c++,之前一直很討厭c++,沒辦法只能短時間彌補c++的知識,項目中需要一個接口只調用一次,需要使用到c++的call_once機制,于是寫一個小demo來測試,就因為這個足夠小發現了一個非常有意思的問題。Wt328資訊網——每日最新資訊28at.com

Wt328資訊網——每日最新資訊28at.com

call_once,基本原理

std::call_once 的內部實現基于兩個重要的組件:std::once_flag 和 std::invoke。std::once_flag 是一個標志,用于表示某個函數是否已經被調用過。而 std::invoke 則負責實際調用該函數。Wt328資訊網——每日最新資訊28at.com

call_once的基本工作原理是:使用 std::once_flag 來標記函數是否被調用過。當有多個線程試圖調用 std::call_once 時,只有一個線程會執行函數,其他線程會被阻塞直至該函數執行完畢。Wt328資訊網——每日最新資訊28at.com

std::call_once 的使用步驟三步曲:Wt328資訊網——每日最新資訊28at.com

  • 創建 std::once_flag 對象:在需要保證函數只調用一次的地方創建一個 std::once_flag 對象。
  • 編寫需要執行一次的函數:編寫你想要確保只調用一次的函數。
  • 調用 std::call_once:在需要執行該函數的地方調用 std::call_once 并傳入 std::once_flag 和函數名稱。

demo問題引入

demo非常簡單,實現一個Init函數進行call_once調用,只調用一次的函數Initialize做一次打印處理,main中連續調用Init 4次,理論上來說我們執行結果只有一行打印,這也是我們的目的。Wt328資訊網——每日最新資訊28at.com

#include <iostream>#include <thread>#include <mutex>#include <atomic>std::once_flag flag;void Initialize(){        std::cout << "Run into Initialize.." << std::endl;}void Init(){        std::call_once(flag, Initialize);}int main(){    Init();    Init();    Init();    Init();    return 0;}

使用g++編譯,執行結果發現出錯了:Wt328資訊網——每日最新資訊28at.com

Wt328資訊網——每日最新資訊28at.com

拋出了個異常,從call_once上的理解來說代碼實現應該是沒問題的。于是使用調試大法gdb,編譯+g后使用gdb調試發現了個有意思的:Wt328資訊網——每日最新資訊28at.com

Wt328資訊網——每日最新資訊28at.com

Wt328資訊網——每日最新資訊28at.com

使用gdb調試發現__gthread_active_ptr指針是0,然后繼續執行發現___gthread_once返回的__e為0,于是繼續執行if就拋了異常。__gthread_active_ptr這又是什么呢?Wt328資訊網——每日最新資訊28at.com

深入研究研究

怎么看呢?既然不知道是什么我一般的操作是先看預處理部分代碼,使用gcc -E參數來編譯出call_once.i文件。Wt328資訊網——每日最新資訊28at.com

g++ -E call_once.cpp -o call_once.i

打開call_once.i文件我發現main函數部分沒有什么特別之處,我們搜索call_once可以看到它的實現。Wt328資訊網——每日最新資訊28at.com

Wt328資訊網——每日最新資訊28at.com

這段代碼中的 std::call_once 函數首先創建了一個可調用對象 __callable,這個對象會調用傳入的函數 __f,并傳入 __args 參數。然后,它將 __callable 的地址存儲到 __once_callable 變量中。Wt328資訊網——每日最新資訊28at.com

接下來,通過一個 lambda 表達式將 __callable 的調用封裝在 __once_call 中,這個 lambda 表達式會執行 __callable。Wt328資訊網——每日最新資訊28at.com

最后,使用底層線程庫的 __gthread_once 函數來確保 __once_call 只會執行一次,即保證傳入的函數 __f 只會被調用一次。Wt328資訊網——每日最新資訊28at.com

如果 __gthread_once 的返回值不為零,表示執行出現了錯誤,會通過 __throw_system_error 拋出系統錯誤。Wt328資訊網——每日最新資訊28at.com

既然是if(__e)后拋的異常,我們繼續看__gthread_once的實現,搜索__gthread_once關鍵字,找到其實現:Wt328資訊網——每日最新資訊28at.com

 11452 static inline int 11453 __gthread_once (__gthread_once_t *__once, void (*__func) (void)) 11454 { 11455   if (__gthread_active_p ()) 11456     return __gthrw_pthread_once (__once, __func); 11457   else 11458     return -1; 11459 }

這個函數可以看到執行了__gthread_active_p ,我們繼續找__gthread_active_p 的實現。Wt328資訊網——每日最新資訊28at.com

Wt328資訊網——每日最新資訊28at.com

__gthread_active_p 是一個內聯函數,返回一個整數值。Wt328資訊網——每日最新資訊28at.com

static void const __gthread_active_ptr 是一個靜態指針常量,初始化為 __gthrw___pthread_key_create 函數的地址。Wt328資訊網——每日最新資訊28at.com

extension 是一個 GNU C 擴展,用于告知編譯器避免對某些表達式進行警告。在此處,它將地址轉換為 void類型,以避免類型不匹配的警告。Wt328資訊網——每日最新資訊28at.com

&__gthrw___pthread_key_create 可能是一個特定線程庫(如 POSIX 線程庫)內部的函數,用于創建線程特定數據的關鍵字。Wt328資訊網——每日最新資訊28at.com

函數返回 __gthread_active_ptr != 0,即如果該指針非空,則表明線程已激活(從指針命名上猜的)。Wt328資訊網——每日最新資訊28at.com

所以該函數用于指示線程是否被激活。不明白?我們繼續看__gthrw___pthread_key_create的定義。Wt328資訊網——每日最新資訊28at.com

11405 static __typeof(pthread_key_create) __gthrw___pthread_key_create __attribute__ ((__weakref__("__pthread_key_create")));

通過 attribute((weakref("__pthread_key_create"))),將 __gthrw___pthread_key_create 弱引用到 __pthread_key_create。Wt328資訊網——每日最新資訊28at.com

弱引用是一種機制,允許在鏈接過程中,如果存在 __pthread_key_create 的定義,則使用它。但如果找不到 __pthread_key_create,則允許 __gthrw___pthread_key_create 仍然存在,只不過它將保持為空或未定義狀態。Wt328資訊網——每日最新資訊28at.com

這里大致就明白了,總結一下,call_once內部實現中要找一個__pthread_key_create定義,如果不存在則返回空,即我們調試的時的指針給了0,從而引起異常。__pthread_key_create函數很明顯是線程函數。好,那我們在代碼中加上該函數調用試試看。在main開始增加如下:Wt328資訊網——每日最新資訊28at.com

int main(){    pthread_key_t key;    pthread_key_create(&key, NULL);    Init();

編譯運行:Wt328資訊網——每日最新資訊28at.com

Wt328資訊網——每日最新資訊28at.com

達到預期效果了!Wt328資訊網——每日最新資訊28at.com

所以整體分析一下,在 call_once 的內部實現中,檢測是否支持 __pthread_key_create 可能是為了確保在使用 call_once 進行線程同步時,能夠利用線程特定數據鍵來管理狀態或資源,確保其正確性和性能。如果當前環境不支持 __pthread_key_create,那么在多線程環境下可能無法有效地管理線程特定的狀態信息。Wt328資訊網——每日最新資訊28at.com

因此,檢測是否支持 __pthread_key_create 可能是 call_once 實現中的一種策略,用于在可能的情況下提供更好的線程安全性和性能。如果當前環境不支持這個特定的功能,可能會采用其他方式來實現 call_once,或者簡化其行為以確保程序在這樣的環境中仍能正確運行,盡管可能會犧牲一些特定的功能或性能。Wt328資訊網——每日最新資訊28at.com

我嘗試不使用__pthread_key_create,隨便調用一個pthread庫的api,比如pthread_create,或者pthread_mutex_init,調用可以全部傳遞空指針,結果依然是可以達到預期的。所以驗證也說明call_once內部通過弱引用庫函數來檢測當前是否支持多線程,如果不支持則拋出異常,所以使用前提必須是多線程環境。Wt328資訊網——每日最新資訊28at.com

總結

call_once 的魅力與注意事項:Wt328資訊網——每日最新資訊28at.com

std::call_once 提供了一種簡單而又強大的多線程同步方式,但在使用時也需注意一些細節。比如一定要確保程序是多線程調用,如果有多線程自然還要確保線程安全,避免潛在的死鎖和競態條件問題登。Wt328資訊網——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-33372-0.html解開C++之call_once的神秘面紗:記一個有意思的問題筆記

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: C++高級編程:構建高效穩定接口與深入對象設計技巧

下一篇: 使用單例模式管理全局音頻

標簽:
  • 熱門焦點
Top 主站蜘蛛池模板: 玉山县| 琼结县| 阜城县| 德钦县| 共和县| 黎川县| 甘孜| 光泽县| 西乌珠穆沁旗| 磴口县| 丰顺县| 保亭| 承德县| 栖霞市| 毕节市| 宿松县| 封开县| 乐亭县| 南和县| 都江堰市| 西乡县| 滦平县| 敖汉旗| 丹棱县| 新干县| 湟中县| 连云港市| 南陵县| 仁寿县| 莱阳市| 浮山县| 常州市| 池州市| 沽源县| 防城港市| 南京市| 信阳市| 泰顺县| 玉山县| 昭苏县| 象山县|