c#線程初探(二)

來源:互聯網
上載者:User

繼續上一篇c#線程初探(一),這裡介紹線程同步的常見概念和注意事項。
3、同步
使用線程的一個重要方面是同步訪問多個線程訪問的任何變數。
(1)、“同步”:所謂同步,是指在某一時刻只有一個線程可以訪問變數。
同步問題只會發生在下述情境:至少有一個線程要寫入一個變數,而與此同時,其他線程正在讀取或者寫入同一個變數。這和大學課程《作業系統》教的線程同步是一個道理。
c#為同步訪問變數提供了一種非常簡單的方式,即使用關鍵字lock。Code is cheap.舉例來說,在“保證一個類僅有一個執行個體:單例模式”就已經用了這種方式:

Code
using System;
using System.Threading;
/// <summary>
/// Singleton 
/// </summary>
public class WindowsTaskManager
{
    private static WindowsTaskManager wtm;
    private static readonly object syncRoot = new object();// 程式運行時建立一個靜態唯讀進程輔助對象
    private WindowsTaskManager()
    {
    }
    public static WindowsTaskManager CreateSingleWtm()
    {
        lock (syncRoot) //lock 同步訪問變數
        {
            if (wtm == null)
            {
                wtm = new WindowsTaskManager();
            }
        }
        return wtm;
    }
}

lock語句把變數放在括弧中,以封裝對象,被稱為獨佔鎖或者排它鎖。當執行帶有lock關鍵字的複合陳述式時,獨佔鎖會保留下來。當變數被封裝在獨佔鎖中時,其他線程就不能訪問該變數。如果在上面代碼中使用獨佔鎖,在執行複合陳述式時,這個線程就會失去其“時間片”。如果下一個獲得時間片的線程試圖訪問變數syncRoot,就會被拒絕。Windows會讓其他線程處於睡眠狀態,直到解除了獨佔鎖為止。
PS:上述代碼中,我們lock的是一個object對象,對於string這個特殊類型的對象,我們一定要慎用。比如下面的代碼:

Code
   class LockTestOne
    {
        private string strSync = "";
        public void DoSomething()
        {
            lock (strSync)
            {
                //do something
            }
        }
    }

    class LockTestTwo
    {
        private string strSync2 = "";
        public void DoSomething2()
        {
            lock (strSync2)
            {
                //do something
            }
        }
    }

本來,LockTestOne和LockTestOne2類中各自的方法一點關係沒有,但在這裡,DoSomething2方法執行時,若另一個線程在執行DoSomething方法,那它得等待!兩個變數(strSync和strSync2)都是在編譯時間賦值為""(Null 字元串),.NET會讓這兩個變數指向同一個拘留池的對象(strSync和strSync2在拘留池中。)。於是,兩個lock看似lock兩個毫不相干的對象,但其實是在lock同一個對象。所以,準確的講,我們不要lock拘留池中的字串。
(2)同步引起的問題:死結(dead lock)和競態條件(race condition)
線程同步非常重要,但是要慎用,因為這會降低效能。原因有兩個,首先,在對象上放置和解開鎖會帶來某些系統開銷。第二個原因更重要,線程使用的越多,等待釋放對象的線程就越多。如果一個線程在對象上放置了一個鎖,需要訪問該對象的其他線程就只能暫停執行,直到該鎖被解開才能繼續執行。因此,在lock塊內編寫的代碼越少越好,以免出現線程同步錯誤。lock語句某種意義上就是臨時禁用應用程式的多線程功能,也就刪除了多線程的各種優勢。
使用線程同步有潛在的危險,主要表現就是死結和競態條件。
a、死結:死結是一個錯誤,在兩個線程都需要訪問該被互鎖的資源時發生。比如下面的代碼:

Code
        /* 線程1運行如下代碼 */
        //a和b是兩個線程都可以訪問的對象引用
        lock (a)
        {
            //do something
            lock (b)
            {
                //do something
            }
        }

        /* 線程2運行如下代碼 */
        lock (b)
        {
            //do something
            lock (a)
            {
                //do something
            }
        }

在上面代碼中,根據線程1和線程2遇到不同語句的時間,可能會出現下述情況:線程1在a上加鎖,同時線程2在b上加鎖。不久,線程1開始遇到lock(b)語句,立即進入睡眠狀態,等待b上的鎖被釋放。之後,第二個線程遇到lock(a)語句,也立即進入睡眠狀態,等待a上的鎖被釋放。但是,a上的鎖永遠不會解開,因為線程1擁有這個鎖,目前正處於睡眠狀態,在b上的鎖被解開前是不會“醒過來”的。而線上程2被叫醒之前,b上的鎖不會解開,這樣線程1和線程2就互相等待對方釋放資源(最後就耗上了),這樣就形成一個死結。
解決死結的方法:讓這兩個線程以相同的順序在對象上聲明加鎖。正確的代碼如下:

Code
       /* 線程1運行如下代碼 */
        //a和b是兩個線程都可以訪問的對象引用
        lock (a)
        {
            //do something
            lock (b)
            {
                //do something
            }
        }

        /* 線程2運行如下代碼 (對a和b加鎖順序和線程1一樣)*/
        lock (a)
        {
            //do something
            lock (b)
            {
                //do something
            }
        }

b、競態條件
競態條件比死結更微妙。它很少中斷進程的執行,但可能導致資料損毀。當幾個線程視圖訪問同一個資料,但沒有考慮其他線程的執行情況時,就會發生競態條件。
註:關於競態條件不是一兩句話就可以說的清楚的,讀者可以參考相關資料,大學教材《作業系統》有詳細講解,這裡不在贅述了。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.