簡體   English   中英

Thread.Yield是一種標准方法,用於確定您的多線程應用程序C#中是否存在錯誤

[英]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()會導致或破壞程序,那么幾乎肯定會有錯誤。

這里的關鍵是線程將處理器的權利放棄到另一個線程。

不要認為它是一個標准,但它確實是有用的,正如你所包含的引用中所提到的那樣......

多線程調試很難,沒有真正的標准方法

調試多線程代碼真的很難,原因有幾個

  1. 用於觀察程序的工具會修改它的執行方式,這意味着您並沒有真正調試將在生產中執行的操作。

  2. 暫時需要同步添加允許您觀察應用程序狀態的方法(即Console.WriteLine ,這意味着代碼與野外代碼不同)

  3. 根據您正在執行的環境,結果可能會有很大差異, 例如 ,帶有i5和8 GB RAM的 開發盒可能會正常工作,但是當您將程序上傳到具有16個內核和128 GB內存生產環境時 ,您很可能得到不同的結果

把所有東西扔在牆上,看看有什么東西

這不是一個很好的答案,但說實話是我最終調試很多時間的方式,你可以嘗試以下技術:

編譯為發布代碼,優化可能會更改代碼的執行順序,並導致Debug構建的指令與發布版本不同

  1. 多次運行多線程代碼(取決於1,000到1,000,000次之間的強度)
  2. 只打幾次電話
  3. 只打了一次電話(可能看起來很愚蠢,但這幾次搞砸了我)
  4. 長時間多次通話(每小時撥打一天電話)

這不是萬無一失的,但認為這是一種很好的方法,可以確保在釋放出一些東西之前捕獲盡可能多的問題。

使用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.

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