[英]Is Thread.Yield a standard way to determine if you have a bug in your multi threaded application C#
我開始閱讀http://www.albahari.com/threading/上發布的信息。
提交人稱:
Sleep(0)
或Yield在生產代碼中偶爾會用於高級性能調整。 它也是一個很好的診斷工具,可以幫助發現線程安全問題: 如果在代碼中的任何地方插入Thread.Yield()
會導致或破壞程序,那么幾乎肯定會有錯誤。
根據MSDN on Thread.Yield()
的MSDN on Thread.Yield()
, Thread.Yield()
定義如下:
使調用線程執行到另一個准備在當前處理器上運行的線程。 操作系統選擇要生成的線程。
對我來說,這描述了一半的軟件開發,它說競爭條件無法解決。
這是線程中的標准調試實踐嗎?
這是一個很好的建議,但我通常使用Sleep(1)代替。
使並發錯誤如此難以修復的一個原因是它們很難重現 - 大多數問題都表現在你不幸的時候,操作系統會在最糟糕的時候暫停你的線程。
在調試這樣的問題時,你經常需要測試一個假設,比如“當我的線程在這里暫停時可能會發生,並且......”。 此時,您可以插入yield或sleep,這將暫停您的線程並大大增加再現錯誤的可能性。
Thread.Yield將幫助您找到一些錯誤
你實際上在這里回答你自己的問題:
Sleep(0)或Yield在生產代碼中偶爾會用於高級性能調整。 它也是一個很好的診斷工具,可以幫助發現線程安全問題: 如果在代碼中的任何地方插入Thread.Yield()會導致或破壞程序,那么幾乎肯定會有錯誤。
這里的關鍵是線程將處理器的權利放棄到另一個線程。
不要認為它是一個標准,但它確實是有用的,正如你所包含的引用中所提到的那樣......
多線程調試很難,沒有真正的標准方法
調試多線程代碼真的很難,原因有幾個
用於觀察程序的工具會修改它的執行方式,這意味着您並沒有真正調試將在生產中執行的操作。
暫時需要同步添加允許您觀察應用程序狀態的方法(即Console.WriteLine
,這意味着代碼與野外代碼不同)
根據您正在執行的環境,結果可能會有很大差異, 例如 ,帶有i5和8 GB RAM的 開發盒可能會正常工作,但是當您將程序上傳到具有16個內核和128 GB內存的生產環境時 ,您很可能得到不同的結果
把所有東西扔在牆上,看看有什么東西
這不是一個很好的答案,但說實話是我最終調試很多時間的方式,你可以嘗試以下技術:
編譯為發布代碼,優化可能會更改代碼的執行順序,並導致Debug構建的指令與發布版本不同
這不是萬無一失的,但認為這是一種很好的方法,可以確保在釋放出一些東西之前捕獲盡可能多的問題。
使用Thread.Sleep()
或Thread.Yield()
不會解決您的錯誤,但在某些情況下它們可能會隱藏它們。 雖然這似乎是件好事 - 阻止彈出錯誤比讓他們殺死你的程序更好 - 現實是你沒有解決潛在的問題。
是的,在多線程程序中踩踏錯誤可能會很難受。 這是你真正需要了解線程如何交互以及當線程在不同的CPU內核上同時運行時會發生什么等等。如果沒有這種理解,你可能永遠不會在程序邏輯中發現導致問題的錯誤。第一名。
編寫多線程程序時,必須確保共享數據上的每個操作都是原子的。 當您在共享值上執行此操作時,即使是簡單的增量操作也會成為問題,這就是我們使用Interlocked.Increment()
方法的原因。 對於其他所有東西,都有鎖等等,以幫助您管理線程交互。
檢查線程與共享數據之間的每次交互,並確保在使用數據時數據上存在鎖定。 例如,假設您正在為一組工作線程排隊工作:
public class WorkerThread
{
public static readonly Queue<Job> jobs = new Queue<Job>();
public void ThreadFunc()
{
while (true)
{
if (jobs.Count > 0)
{
Job myJob = jobs.Dequeue()
// do something with the job...
}
else
Thread.Yield();
}
}
}
看起來很簡單,只有在你檢查一份工作然后再去取它之間的幾個周期。 與此同時,另一個線程突然進入並抓住了你下面的等待工作。 您可以通過幾種方式解決此問題,但最簡單的方法是使用System.Collections.Concurrent
的線程安全版本的隊列:
public class WorkerThread
{
public static readonly ConcurrentQueue<Job> jobs = new ConcurrentQueue<Job>();
public void ThreadFunc()
{
Job myJob;
while (true)
{
if (jobs.TryDequeue(out myJob))
{
// do something with the job...
}
else
Thread.Yield();
}
}
}
如果您沒有線程安全版本,則必須依靠鎖定或其他一些機制來保護您對共享數據的訪問。 基於鎖定的上述解決方案可能如下所示:
public class WorkerThread
{
private static object _jobs_lock = new object();
private static readonly Queue<Job> _jobs = new Queue<Job>();
public void ThreadFunc()
{
Job myJob;
while (true)
{
if ((myJob = NextJob()) != null)
{
// do something with the job...
}
else
Thread.Yield();
}
}
public void AddJob(Job newJob)
{
lock(_jobs_lock)
_jobs.Enqueue(newJob);
}
private Job NextJob()
{
lock (_jobs_lock)
{
if (_jobs.Count > 0)
return _jobs.Dequeue();
}
return null;
}
}
如果存在作業並且實際從隊列中檢索作業,則這兩者中的任何一個都將確保在測試之間不修改集合。 確保盡可能快地釋放鎖定,因為否則您將遇到鎖定爭用問題,這可能會更加難以解決。 永遠不要將鎖定放置到比完成工作所必需的更長的時間 - 在這種情況下,測試並從隊列中檢索項目。 對所有共享資源執行此操作,您將不再擁有任何競爭條件。
當然還有很多其他的線程問題,包括本質上不安全的方法。 讓你的線程盡可能地自我依賴並鎖定對共享資源的訪問權限,你應該能夠避免大多數令人討厭的heisenbug 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.