簡體   English   中英

如何安全地從一個線程讀取變量並從另一個線程修改它?

[英]How do I safely read a variable from one thread and modify it from another?

我有一個在多個線程中使用的類實例。 我正在從一個線程更新多個成員變量並從一個線程讀取相同的成員變量。 保持線程安全的正確方法是什么?

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?

謝謝

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

也許最好做一些像這樣的事情:

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

這當然假定計算中使用的值不會在任何其他線程上修改

很難說你正在做什么導致問題。 互斥模式非常簡單。 您鎖定互斥鎖,訪問共享數據,解鎖互斥鎖。 這可以保護數據,因為互斥鎖只會讓一個線程一次獲得鎖定。 任何無法獲得鎖定的線程都必須等到互斥鎖解鎖。 解鎖喚醒了服務員。 然后他們將爭取獲得鎖定。 失敗者重新入睡。 從鎖定釋放開始,喚醒所需的時間可能是幾毫秒或更長。 確保始終解鎖互斥鎖。

確保您不要長時間鎖定鎖。 大多數時候,很長一段時間就像微秒。 我更喜歡把它放在“幾行代碼”之下。 這就是為什么人們建議你在鎖外進行長時間運行的計算。 不保持鎖定很長時間的原因是你增加了其他線程鎖定並且必須旋轉或睡眠的次數,這會降低性能。 您還增加了在擁有鎖定時線程可能被搶占的可能性,這意味着在該線程休眠時啟用了鎖定。 那性能更差。

失敗的線程不必睡覺。 旋轉意味着遇到鎖定的互斥鎖的線程不會休眠,但是在放棄和休眠之前循環反復測試鎖定預定義的時間段。 如果您有多個核心或核心能夠同時擁有多個線程,那么這是一個好主意。 多個活動線程意味着兩個線程可以同時執行代碼。 如果鎖是少量代碼,那么獲得鎖的線程將很快完成。 另一個線程需要等待幾納秒才能獲得鎖定。 請記住,睡眠你的線程是一個上下文切換和一些代碼將你的線程附加到互斥鎖上的服務員,所有都有成本。 另外,一旦你的線程休眠,你必須等待一段時間才能讓調度程序喚醒它。 這可能是多個ms。 查找螺旋鎖。

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

您應該假設線程可以在任何機器代碼指令中被搶占。 此外,您應該假設每行c代碼可能是許多機器代碼指令。 經典的例子是i ++。 這是c中的一個語句,但是機器代碼中的讀取,增量和存儲。

如果您真的關心性能,請先嘗試使用原子操作。 將互斥鎖視為最后的手段。 大多數並發問題很容易通過原子操作解決(谷歌gcc原子操作開始學習),很少有問題真正需要互斥。 互斥體比較慢。

保護您的共享數據,無論它在何處寫入以及在何處讀取 否則......為失敗做准備。 在只有一個線程處於活動狀態的時間段內,您不必保護共享數據。

它通常可用於運行您的應用程序與1個線程以及N個線程。 這樣您就可以更輕松地調試競爭條件。

最小化使用鎖保護的共享數據。 嘗試將數據組織到結構中,以便單個線程可以獲得對整個結構的獨占訪問(可能通過設置單個鎖定標志或版本號或兩者),之后不必擔心任何事情。 然后大多數代碼都沒有鎖和競爭條件。

最終寫入共享變量的函數應使用臨時變量,直到最后一刻,然后復制結果。 編譯器不僅會生成更好的代碼,而且訪問共享變量尤其是更改它們會導致L2和主ram之間的緩存行更新以及各種其他性能問題。 如果你不關心性能,請忽略這一點。 但是,如果你想了解更多,我建議你谷歌文件“程序員應該知道的關於記憶的一切”。

如果從共享數據中讀取單個變量,只要變量是整數類型而不是位域的成員(使用多個指令讀取/寫入位域成員),您可能不需要鎖定。 閱讀原子操作。 當您需要處理多個值時,需要鎖定以確保您沒有讀取一個值的版本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

也許,在上面的示例中,如果輸入已更改,您仍會存儲結果,但請返回並重新計算。 這取決於其他線程是否可以使用稍微過時的答案。 也許其他線程當他們看到一個線程已經在進行計算時,只需更改輸入數據並將其留給忙線程注意並重做計算(如果你這樣做,你需要處理競爭條件,並且容易一個)。 這樣,其他線程可以做其他工作,而不僅僅是睡覺。

干杯。

可能最好的辦法是:

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) 

我要做的是將計算與更新分開:

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

暫無
暫無

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

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