簡體   English   中英

修改靜態變量是否安全?

[英]Is it thread safe to modify a static variable?

從C ++ 11開始,保證靜態變量初始化是線程安全的。 但是如何在多個線程中修改靜態變量呢? 像下面

static int initialized = 0;
Initialize()
{
 if (initialized)
    return;
 initialized = 1; // Is this thread safe? 
}

我問這個問題的原因是,我正在閱讀Py_Initialize()的源代碼,正在嘗試將Python嵌入多線程C ++應用程序中,我想知道在幾個線程中多次調用Py_Initialize()是否安全? Py_Initialize()的實現歸結為函數_Py_InitializeEx_Private ,如下所示

// pylifecycle.c
static int initialized = 0;

_Py_InitializeEx_Private(int install_sigs, int install_importlib)
{
    if (initialized)
        return;
    initialized = 1;
 // a bunch of other stuff
 }

C的結論和C ++一樣嗎?

編輯所以所有答案都是好的,我選擇了最能使我頭腦清醒的答案。

不,在這種情況下,靜態僅與存儲時間有關(請參閱http://en.cppreference.com/w/c/language/static_storage_duration )。 與其他變量相比,該變量根本沒有額外的線程安全性。

嘗試為此使用std :: call_once,請參閱http://en.cppreference.com/w/cpp/thread/call_once

修改靜態變量不是線程安全的,但是初始化靜態變量是線程安全的。 因此,您可以執行以下操作:

void my_py_init() {
    static bool x = (Py_Initialize(), true);
}

而已。 現在,您可以從my_py_init多個線程中調用my_py_init ,而Py_Initialize將僅被調用一次。

Py_Initialize不是線程安全的。 僅當您知道Python解釋器已經初始化時,才可以從多個線程調用它,但是如果您可以證明調用該函數很愚蠢。

實際上,大多數Python C-API調用都不是線程安全的。 您需要獲取全局解釋器鎖(GIL)才能與Python解釋器進行交互。 (有關更多詳細信息,請參見Python C-API文檔 。請仔細閱讀。)

但是,據我所知,在解釋器初始化之前,您無法使用標准API來獲取GIL。 因此,如果您有多個線程,其中任何一個都可能初始化相同的Python解釋器,則需要使用自己的互斥鎖來保護對Py_Initialize的調用。 如果可以使用程序邏輯,那么最好在啟動任何線程之前進行一次初始化。


您引用的代碼:

static int initialized = 0;

void Initialize_If_Necessary() 
{
    if (initialized)
        return;
    initialized = 1;
    // Do the initialization only once
}

顯然,即使initialized為原子類型,也不是任何語言的線程安全。 假設有兩個線程在任何初始化發生之前同時執行此代碼:它們都被視為initialized為false,所以它們都繼續進行初始化。 (如果您沒有兩個核心,您可以想象第一個過程是在initialized測試和賦值測試之間進行任務切換。)

跨多個線程修改靜態變量並不安全,因為如果將變量放入寄存器中,則同一寄存器中其他內核的信息將有所不同(在另一個線程中修改該變量與嘗試訪問該內核的相同)版本的寄存器,其中包含完全不同的數據)。

第一個代碼樣本是所謂的“延遲初始化”的典型起點。 這對於保證僅對“昂貴對象”進行一次初始化很有用; 但是只有在需要使用該對象之前,才需要這樣做。

該特定示例沒有任何嚴重的問題,但是這過於簡單了。 當您更全面地看待延遲初始化時,您會發現多線程延遲初始化不是一個好主意。


線程安全 ”的概念遠遠超出了單個變量(靜態或其他)。 您需要退后一步,同時考慮在相同的1種資源(內存,對象,文件等)中發生的事情

1:同一類的不同實例不是同一件事; 但它們的靜態成員是。

請考慮第二個示例中的以下摘錄。

if (initialized)
    return;
initialized = 1;
// a bunch of other stuff

在前三行中,如果多個線程大約同時執行該代碼,則不會造成嚴重危害。 一些線程可能會提前返回; 其他人可能有點“太快”,並且所有人都執行設置initialized = 1;的任務initialized = 1; 但是,這不必擔心,因為無論有多少線程設置共享變量,最終效果總是相同的。

問題來自第四行。 一個幾乎毫不客氣地把它當作“ 一堆其他東西 ”拋在一邊。 那個“ 其他東西 ”才是真正關鍵的代碼,因為如果可能的話,可以將initialized = 1; 要多次調用,您需要考慮多次並發調用“其他內容”的影響。


現在,在極少數情況下,您使自己滿意,可以多次調用“其他內容”, 這還有另一個問題 ……

考慮可能使用Python的客戶端代碼。

Py_Initialize();
//use Python

如果有2個線程同時調用上述線程; 其中1個“早歸”,另一個實際執行了初始化。 然后,“早期返回線程”將完全初始化之前使用Python啟動(或嘗試啟動)

作為一點技巧,您可以嘗試在初始化過程中在if (initialized)行處進行阻塞。 但這是不可取的,原因有兩個:

  • 多個線程可能會在其處理的早期階段停留等待。
  • 即使在初始化完成之后,每次“懶惰初始化” Python框架時,檢查鎖的開銷也很小(但完全浪費)。

結論

延遲初始化有其用途。 但是,最好不要嘗試從多個線程執行延遲初始化。 寧可擁有一個“安全線程”(主線程通常足夠好),它甚至可以在創建任何嘗試使用已初始化內容的線程之前執行延遲初始化。 然后,您完全不必擔心線程安全性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM