2008-10-23 51 views
6

運行一些傳給我的舊代碼時遇到問題。它的工作時間爲99%,但有一段時間,我注意到它拋出了「違規閱讀位置」例外。我有可變數量的線程可能在整個生命週期中執行此代碼。發生頻率低可能表示競賽狀況,但我不知道爲什麼會在這種情況下導致例外。這是有問題的代碼:違規讀取std :: map運算符中的位置[]

MyClass::Dostuff() 
{ 
    static map<char, int> mappedChars; 
    if (mappedChars.empty()) 
    { 
     for (char c = '0'; c <= '9'; ++c) 
     { 
      mappedChars[c] = c - '0'; 
     } 
    } 
    // More code here, but mappedChars in not changed. 
} 

的異常被拋出在地圖上的operator []的實現,在第一個調用operator []的(使用STL的VS2005執行。)


mapped_type& operator[](const key_type& _Keyval) 
{ 
    iterator _Where = this->lower_bound(_Keyval); //exception thrown on the first line 
    // More code here 
} 

我已經嘗試凍結運算符[]中的線程,並試圖讓它們在同一時間運行它,但我無法使用該方法重現異常。

你能想到爲什麼會拋出什麼原因,只有一些時間?

(是的,我知道STL是不是線程安全的,我需要在這裏做出改變。我主要是好奇,爲什麼我看到我上面所述的行爲。)

按照要求,這裏有一些關於異常的更多細節:
app15-51-02-0944_2008-10-23.mdmp中的0x00639a1c(app.exe)未處理異常:0xC0000005:訪問衝突讀取位置0x00000004。

感謝大家提出多線程問題的解決方案,但這不是這個問題要解決的問題。是的,我知道所提供的代碼沒有得到正確保護,並且它試圖完成的任務過於龐大。我已經有了它的實施修復。我只是想更好地理解爲什麼這個異常是從一開始就拋出的。

+0

瞭解違規地址可能會有用。可能「this」爲空,它與地圖本身無關。 – 2008-10-23 23:07:42

回答

5

給定地址「4」,可能「this」指針爲空或迭代器不好。你應該能夠在調試器中看到它。如果這是空的,那麼問題不在於那個函數,而是誰曾經調用過這個函數。如果迭代器不好,那就是你提到的競爭條件。大多數迭代器不能容忍正在更新的列表。

好的等待 - 沒有FM在這裏。靜態的初次使用。這樣做的代碼不是多線程安全的。一個線程正在進行初始化,而第二個線程認爲它已經完成,但仍在進行中。結果是使用未初始化的變量。你可以在下面的程序集中看到這個:

static x y; 
004113ED mov   eax,dword ptr [$S1 (418164h)] 
004113F2 and   eax,1 
004113F5 jne   wmain+6Ch (41141Ch) 
004113F7 mov   eax,dword ptr [$S1 (418164h)] 
004113FC or   eax,1 
004113FF mov   dword ptr [$S1 (418164h)],eax 
00411404 mov   dword ptr [ebp-4],0 
0041140B mov   ecx,offset y (418160h) 
00411410 call  x::x (4111A4h) 
00411415 mov   dword ptr [ebp-4],0FFFFFFFFh 

當它初始化時,$ S1被設置爲1。如果設置了(004113F5),它將跳過init代碼 - 凍結fnc中的線程將無濟於事,因爲此檢查是在進入函數時完成的。這不是空的,但其中一個成員是。

通過將地圖移出方法並將其作爲靜態方法修復。然後它會在啓動時初始化。否則,你必須在調用DoStuff()的地方放置一個CR。您可以通過在地圖本身的使用周圍放置一個CR(例如,DoStuff使用運算符[])來保護剩餘的MT問題。

2

如果多個線程調用的函數DoStuff這將意味着初始化代碼

if (mappedChars.empty()) 

可以進入比賽狀態。這意味着線程1進入函數,找到空的地圖並開始填充它。線程2然後進入函數,並發現地圖非空(但未完全初始化),所以愉快地開始閱讀它。由於兩個線程現在都處於爭用狀態,但有一個正在修改映射結構(即插入節點),因此會導致未定義的行爲(崩潰)。

如果您在檢查地圖empty()之前使用同步原語,並在地圖保證完全初始化後發佈,則一切正常。

我看了一下Google,實際上靜態初始化是而不是線程安全。因此聲明static mappedChars立即成爲問題。正如其他人所提到的,如果只有一個線程保證在初始化的生命週期中處於活動狀態,那麼最好是初始化完成。

+0

請注意,寫入線程還沒有開始填充它。它拋出operator []的第一行。據我所知,lower_bound不會改變地圖的內容。 – Marcin 2008-10-23 23:19:51

+0

其他線程在做什麼,還有其他人訪問這個共享變量嗎?我不記得編譯器爲靜態變量插入的代碼本身是否是線程安全的。 – Henk 2008-10-23 23:25:53

3

mappedChars是靜態的,所以它被執行DoStuff()的所有線程共享。這可能是你的問題。

如果您必須使用靜態映射,那麼您可能需要使用互斥鎖或臨界區來保護它。

個人而言,我認爲使用地圖來達到這個目的是矯枉過正。我會寫一個輔助函數,它接受一個char並從中減去'0'。您不會遇到任何線程安全問題。

1

當你進入多線程的時候,通常會有太多的事情去找出事情進展不順的確切位置,因爲它會一直在變化。有很多地方在多線程情況下使用靜態地圖可能會變差。

請參閱this thread瞭解一些防範靜態變量的方法。你最好的選擇可能是在啓動多個線程來初始化之前調用該函數一次。要麼,要麼移動靜態地圖,並創建一個單獨的初始化方法。

0

你是否曾經打電話給operator[],其論點不在範圍0..9?如果是這樣,那麼你無意中修改了地圖,這可能會導致在其他線程中發生不良情況。如果您調用operator[]時地圖中沒有參數,則會將該鍵插入到地圖中,其值等於值類型的默認值(在int的情況下爲0)。