2011-01-11 142 views
4

我有一個在多個線程中使用的類實例。我正從一個線程更新多個成員變量,並從一個線程讀取相同的成員變量。什麼是維護線程安全的正確方法?如何安全地從一個線程讀取變量並從另一個線程修改它?

eg: 
    phthread_mutex_lock(&mutex1) 
    obj1.memberV1 = 1; 
    //unlock here? 

我應該在這裏解鎖互斥鎖嗎? (如果另一個線程現在訪問obj1成員變量1和2,則訪問的數據可能不正確,因爲memberV2尚未更新。但是,如果我沒有釋放鎖定,則另一個線程可能會因爲耗時操作而被阻塞。下面

//perform some time consuming operation which must be done before the assignment to memberV2 and after the assignment to memberV1 
    obj1.memberV2 = update field 2 from some calculation 
pthread_mutex_unlock(&mutex1) //should I only unlock here? 

感謝

回答

0

可能是做的最好的事情是:

temp = //perform some time consuming operation which must be done before the assignment to memberV2 

pthread_mutex_lock(&mutex1) 
obj1.memberV1 = 1; 
obj1.memberV2 = temp; //result from previous calculation 
pthread_mutex_unlock(&mutex1) 
+0

我認爲這可能會導致更新丟失,如果線程1執行您的示例代碼和線程2更新`memberV1`在線程1之間分配`temp`並進入互斥體。 – finnw 2011-01-11 03:11:20

1

你鎖定是正確的,您應該解除鎖定早期只是爲了讓另一個線程。繼續(因爲這將允許其他線程看到不一致的狀態的對象。)

0

我會做的是分開的,更新的計算:

temp = some calculation 
pthread_mutex_lock(&mutex1); 
obj.memberV1 = 1; 
obj.memberV2 = temp; 
pthread_mutex_unlock(&mutex1); 
1

也許會更好做是這樣的:

//perform time consuming calculation 
pthread_mutex_lock(&mutex1) 
    obj1.memberV1 = 1; 
    obj1.memberV2 = result; 
pthread_mutex_unlock(&mutex1) 

當然,這是假設在計算中使用的值將不能任何其他線程修改

1

很難說出你在做什麼導致問題。互斥模式非常簡單。您鎖定互斥鎖,訪問共享數據,解鎖互斥鎖。這可以保護數據,因爲互斥鎖一次只能讓一個線程獲得鎖定。任何未能獲得鎖定的線程都必須等待,直到互斥鎖被解鎖。解鎖讓服務員醒來。他們然後將爭取獲得鎖定。失敗者回去睡覺。從鎖定釋放的時間起,喚醒所需的時間可能是多個ms或更多。確保你總是最終解鎖互斥鎖。

請確保您不要鎖定很長一段時間的鎖。大多數時候,很長一段時間就像微秒。我更喜歡圍繞「幾行代碼」。這就是爲什麼人們建議你在鎖定之外進行長時間運算。不鎖定很長時間的原因是您增加了其他線程將鎖定並且不得不旋轉或休眠的次數,這會降低性能。在擁有鎖的同時,您還會增加您的線程可能被搶佔的可能性,這意味着鎖在該線程休眠時啓用。這更糟糕的表現。

失敗的線程不必睡覺。紡紗意味着遇到鎖定的互斥體的線程不會休眠,但會在放棄和休眠之前循環重複測試鎖定預定義週期。如果您有多個核心或多個併發線程的核心,這是一個好主意。多個活動線程意味着兩個線程可以同時執行代碼。如果鎖是在少量代碼周圍,那麼獲得鎖的線程將很快完成。另一個線程在獲得鎖定之前只需要等待幾秒鐘。請記住,休眠線程是一個上下文切換和一些將線程附加到互斥量服務器上的代碼,所有代碼都有成本。另外,一旦你的線程休眠,你必須等待一段時間,然後調度程序纔會喚醒它。這可能是多個毫秒。查找螺旋鎖。

如果你只有一個核心,那麼如果一個線程遇到一個鎖定,這意味着另一個睡眠線程擁有該鎖定,無論你旋轉多長時間,它都不會解鎖。所以你會使用一個鎖,立即睡一個服務員,希望擁有鎖的線程能夠喚醒並完成。

你應該假設一個線程可以在任何機器代碼指令中被搶佔。你也應該假設每行c代碼可能是很多機器代碼指令。經典的例子是i ++。這是c中的一個陳述,但是是機器代碼域中的讀取,增量和存儲。

如果您真的關心性能,請嘗試先使用原子操作。作爲最後的手段來看待互斥體。大多數併發問題很容易通過原子操作解決(谷歌gcc原子操作開始學習),很少有問題需要互斥體。互斥體的速度較慢。

保護您的共享數據,無論它在哪裏書面,無論它在哪裏閱讀。其他...準備失敗。只有一個線程處於活動狀態時,您不必保護共享數據。

通常可以使用1個線程以及N個線程運行您的應用程序。這樣你可以更容易地調試競爭條件。

最小化使用鎖保護的共享數據。嘗試將數據組織到結構中,以便單個線程可以獲得對整個結構的獨佔訪問權限(可能通過設置單個鎖定標誌或版本號或兩者),而不必擔心後面的任何事情。然後,大部分代碼不會與鎖和競爭條件混雜在一起。

最終寫入共享變量的函數應該使用臨時變量直到最後一刻,然後複製結果。編譯器不僅會生成更好的代碼,而且會訪問共享變量,尤其是更改它們會導致L2和主內存之間的緩存行更新以及各種其他性能問題。再一次,如果你不關心性能,不考慮這一點。不過,如果你想知道更多,我建議你谷歌文檔「程序員應該知道關於內存的一切」。

如果您正在從共享數據中讀取單個變量,則只要該變量是整數類型而不是位域成員(可以使用多條指令讀取/寫入位域成員),則可能不需要鎖定該變量, 。閱讀原子操作。當你需要處理多個值時,你需要一個鎖來確保你沒有讀取一個值的版本A,被搶佔,然後讀取下一個值的版本B.寫作也是如此。

你會發現數據的副本,甚至整個結構的副本都派上用場。您可以在構建數據的新副本,然後通過使用一個原子操作更改指針來交換它。您可以製作一份數據副本,然後對其進行計算,而不用擔心它是否發生變化。

因此,也許你想要做的是:

lock the mutex 
    Make a copy of the input data to the long running calculation. 
unlock the mutex 

L1: Do the calculation 

Lock the mutex 
    if the input data has changed and this matters 
    read the input data, unlock the mutex and go to L1 
    updata data 
unlock mutex 

也許,在上面的例子中,你還是把結果存儲在輸入變化,但回去重新計算。這取決於其他線程是否可以使用稍微過時的答案。當其他線程看到一個線程已經在進行計算時,只需更改輸入數據並將其保留到繁忙線程中即可注意到並重新計算(如果你這樣做,將會遇到需要處理的競態條件,並且容易)。這樣,其他線程可以完成其他工作,而不僅僅是睡眠。

歡呼聲。

相關問題