如何產生一個C++區域性靜態物件的多執行緒問題

滕瑞發表於2015-03-15

本來是想重現以前工作中遇到的一個Segment Fault。但是據說原來的問題已經在新的C++版本中解決了,現在不會產生段錯誤。而且C++的實現版本眾多,也無從考究原來的版本是哪一個了。

原來的程式碼模型如下,在多執行緒環境中,重複進入這個函式,可能會有多個建構函式同時執行。從而會產生段錯誤。當然這個概率比較小,好像在效能比較差的機器上較容易重現。

void func()
{
    static string crash("It is none of my business!");
}

但這類問題現在仍然有效。可以參考《Effective C++(第三版)》中的條款4:確定物件被使用前已先被初始化。其中講到,任何一種non-const static物件,不論它是local或non-local,在多執行緒環境下“等待某事發生”都會有麻煩。

參考以下程式碼:

#include <iostream>
#include <string>
#include <windows.h>

using namespace std;        

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
    static string x(10000000, 'e');

    if (x.length() == 0) {
        cout << "Oh!" << endl;
    }
    else {
        cout << x.length() << endl;
    }

    return 0;
}

int main()
{
    CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
    CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
    Sleep(100);
    return 0;
}

在我的Visual Studio 2013 Express中的執行結果為:

Oh!
10000000
請按任意鍵繼續. . .

為了穩定重現這個問題,這裡使用了string(num,char)這個建構函式,好讓子彈飛得久一點。從結果看,確實有麻煩,當第二個執行緒訪問字串的時候,第一個執行緒還沒有把那個靜態字串初始化完成。所以不管是否利用了什麼Copy-On-Write或引用計數的技術與否,此時字串的長度為0,使用的時候可能會出現問題。

相關文章