除蟲筆記 – Thread 執行時機與 Closure

同事的 .NET 程式抓到一隻有趣的 Bug。以範例程式重現如下:

static void DoProcess(int idx)
{
    while (StartFlag)
    {
        Thread.Sleep(1000);
        Console.WriteLine(
            $"{DateTime.Now:mm:ss} Thread {idx} is running.");
    }
}
 
static void Main(string[] args)
{
 
    for (int i = 1; i <= 4; i++)
    {
        var thd = new Thread(() =>
        {
            DoProcess(i);
        });
        thd.Start();
    }
    StartFlag = true; 
    //三秒後關閉StartFlag
    Thread.Sleep(3000);
    StartFlag = false;
    Console.ReadLine();
}

程式跑迴圈啟動四個 Thread,各 Thread 以 while (StartFlag) { … } 持續執行,沒什麼事要做就每隔一秒 Console.WriteLine() 時間與序號充數。這段程式犯了一個錯,沒在 Thread.Start() 前把 StartFlag 設好,跑完迴圈才 StartFlag = true,導致 DoProcess 什麼都沒做就收工。但如果只是這樣,Bug 馬上會被掀出來,也不會有這篇筆記了。

有趣的現象是 4 條 Thread 中還是有一條 Thread 會跑,使人被「為什麼明明起了 4 條 Thread 卻只有一條執行?」所迷惑:

這個現象源自多執行緒平行執行的時機問題,Thread.Start() 後,主線程式碼會繼續跑下去,而另起 的 Thread 隨後啟動。故推敲實際狀況應為:跑迴圈啟動第一條 Thread,因 StartFlag 為 false 直接結束,啟動第二條 Thread、第三條 Thread 也直接結束,直到第四條 Thread.Start(),進入 DoProcess 之前,主執行緒結束迴圈繼續往下跑執行 StartFlag = true,接著第四條 Thread 才進入 DoProcess() 執行 while (StartFlag),此時 StartFlag 已是 true,因此只有最後一條 Thread 成功運作。

要修正問題,StartFlag = true 應移至 for 迴圈之前:

static void Main(string[] args)
{
    StartFlag = true; //Thread開始前應設定好
 
    for (int i = 1; i <= 4; i++)
    {
        var thd = new Thread(() =>
        {
            DoProcess(i);
        });
        thd.Start();
    }
 
    //三秒後關閉StartFlag
    Thread.Sleep(3000);
    StartFlag = false;
    Console.ReadLine();
}

修改後,四條 Thread 都起來了,但有個問題,編號怎麼是 3 3 4 5,不是 1 2 3 4?

多執行幾次,會發現數字非固定值,有時會是 3 3 5 5。

這一樣與各 Thread DoProcess() 執行時機有關,for 的過程 i 值會歷經 1 2 3 4 5 五種狀態,端看 DoProcess(i) 執行的當下 i 是多少而定。

static void Main(string[] args)
{
    StartFlag = true; //Thread開始前應設定好
 
    for (int i = 1; i <= 4; i++)
    {
        //另外宣告變數,形成Closure
        var idx = i;
        var thd = new Thread(() =>
        {
            DoProcess(idx);
        });
        thd.Start();
    }
 
    //三秒後關閉StartFlag
    Thread.Sleep(3000);
    StartFlag = false;
    Console.ReadLine();
}

要解決這個問題,我們可另外宣告一個變數 idx,透過 Closure 技巧讓四次迴圈中的匿名方法 () => { DoProcess(idx); } 擁有專屬變數,與 i 的變動脫鉤。(延伸閱讀:Closure in C#

抓一隻 Bug 溫習兩種觀念,很划算,呵~

歡迎推文分享:
Published 20 December 2017 08:39 PM 由 Jeffrey
Filed under:
Views: 4,088



意見

沒有意見

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<December 2017>
SunMonTueWedThuFriSat
262728293012
3456789
10111213141516
17181920212223
24252627282930
31123456
 
RSS
創用 CC 授權條款
【廣告】
twMVC

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication