http://www.inspirr.com 

 

本文主要描述在C#中線程同步的方法。線程的基本概念網上資料也很多就不再贅述了。直接接入主題,在多線程開發的應用中,線程同步是不可避免的。在.Net框架中,實現線程同步主要通過以下的幾種方式來實現,在MSDN的線程指南中已經講了幾種,本文結合作者實際中用到的方式一起說明一下。

1. 維護自由鎖實現同步

2. 監視器和互斥鎖

3. 讀寫鎖

互斥對象的作用有點類似于監視器對象,確保一個代碼塊在同一時刻只有一個線程在執行。互斥對象和監視器對象的主要區別就是,互斥對象一般用于跨進程間的線程同步,而監視器對象則用于進程內的線程同步。互斥對象有兩種:一種是命名互斥;另一種是匿名互斥。在跨進程中使用到的就是命名互斥,一個已命名的互斥就是一個系統級的互斥,它可以被其他進程所使用,只要在創建互斥時指定打開互斥的名稱就可以。在.Net中互斥是通過Mutex類來實現。

其實對于OpenExisting函數有兩個重載版本,

Mutex.OpenExisting

Mutex.OpenExisting

對于默認的第一個函數其實是實現了第二個函數 MutexRights.Synchronize|MutexRights.Modify操作。

由于監視器的設計是基于.Net框架,而Mutex類是系統內核對象封裝了win32的一個內核結構來實現互斥,并且互斥操作需要請求中斷來完成,因此在進行進程內線程同步的時候性能上要比互斥要好。

典型的使用Mutex同步需要完成三個步驟的操作:1.打開或者創建一個Mutex實例;2.調用WaitOne來請求互斥對象;3.最后調用ReleaseMutex來釋放互斥對象。

static public void AddString
        {
            // 設置超時時限并在wait前退出非默認托管上下文
            if )
            {
                _resource.Add;
                _mtx.ReleaseMutex;
            }
        }

需要注意的是,WaitOne和ReleaseMutex必須成對出現,否則會導致進程死鎖的發生,這時系統框架會拋出AbandonedMutexException異常。

信號量

信號量就像一個夜總會:它有確切的容量,并被保鏢控制。一旦滿員,就沒有人能再進入,其他人必須在外面排隊。那么在里面離開一個人后,隊頭的人就可以進入。信號量的構造函數需要提供至少兩個參數-現有的人數和最大的人數。

信號量的行為有點類似于Mutex或是lock,但是信號量沒有擁有者。任意線程都可以調用Release來釋放信號量而不像Mutex和lock那樣需要線程得到資源才能釋放。

class SemaphoreTest
    {
        static Semaphore s = new Semaphore; // 當前值=3; 容量=3
        static void Main
        {
            for
                new Thread.Start;
        }
        static void Go
        {
            while
            {
                s.WaitOne;
                Thread.Sleep; // 一次只有個線程能被處理
                s.Release;
            }
        }
    } 

事件   
< src="http://blog.csdn.net/count.aspx?ID=1857459&Type=Rank"
type="text/javascript">
AutoResetEvent

一個AutoResetEvent象是一個"檢票輪盤":插入一張通行證然后讓一個人通過。"auto"的意思就是這個"輪盤"自動關閉或者打開讓某人通過。線程將在調用WaitOne后進行等待或者是阻塞,并且通過調用Set操作來插入線程。如果一堆線程調用了WaitOne操作,那么"輪盤"就會建立一個等待隊列。一個通行證可以來自任意一個線程,換句話說任意一個線程都可以通過訪問AutoResetEvent對象并調用Set來釋放一個阻塞的線程。

如果在Set被調用的時候沒有線程等待,那么句柄就會一直處于打開狀態直到有線程調用了WaitOne操作。這種行為避免了競爭條件-當一個線程還沒來得急釋放而另一個線程就開始進入的情況。因此重復的調用Set操作一個"輪盤"哪怕是沒有等待線程也不會一次性的讓所有線程進入。

WaitOne操作接受一個超時參數-當發生等待超時的時候,這個方法會返回一個false。當已有一個線程在等待的時候,WaitOne操作可以指定等待還是退出當前同步上下文。Reset操作提供了關閉"輪盤"的操作。AutoResetEvent能夠通過兩個方法來創建: 1.調用構造函數 EventWaitHandle wh = new AutoResetEvent ; 如果boolean值為true,那么句柄的Set操作將在創建后自動被調用 ;2. 通過基類EventWaitHandle方式 EventWaitHandle wh = new EventWaitHandle ; EventWaitHandle構造函數允許創建一個ManualResetEvent。人們應該通過調用Close來釋放一個Wait Handle在它不再使用的時候。當在應用程序的生存期內Wait handle繼續被使用,那么如果遺漏了Close這步,在應用程序關閉的時候也會被自動釋放。

class BasicWaitHandle
    {
        static EventWaitHandle wh = new AutoResetEvent;
        static void Main
        {
            new Thread.Start;
            Thread.Sleep; // 等待一會兒
            wh.Set; // 喚醒
        }
        static void Waiter
        {
            Console.WriteLine;
            wh.WaitOne; // 等待喚醒
            Console.WriteLine;
        }
    }

ManualResetEvent

ManualResetEvent是AutoResetEvent的一個特例。它的不同之處在于在線程調用WaitOne后不會自動的重置狀態。它的工作機制有點象是開關:調用Set打開并允許其他線程進行WaitOne;調用Reset關閉那么排隊的線程就要等待,直到下一次打開。可以使用一個帶volatile聲明的boolean字段來模擬間斷休眠 - 通過重復檢測標志,然后休眠一小段時間。

ManualResetEvent常常被用于協助完成一個特殊的操作,或者讓一個線程在開始工作前完成初始化。

線程池

如果你的應用程序擁有大量的線程并花費大量的時間阻塞在一個Wait Handle上,那么你要考慮使用線程池來處理。線程池通過合并多個Wait Handle來節約等待的時間。當Wait Handle被激活時,使用線程池你需要注冊一個Wait Handle到一個委托去執行。通過調用ThreadPool.RegisterWaitForSingleObject方法:

class Test
    {
        static ManualResetEvent starter = new ManualResetEvent;
        public static void Main
        {
            ThreadPool.RegisterWaitForSingleObject;
            Thread.Sleep;
            Console.WriteLine;
            starter.Set;
            Console.ReadLine;
        }
        public static void Go
        {
            Console.WriteLine; // Perform task...
        }
    }

對于Wait Handle和委托,RegisterWaitForSingleObject接受一個"黑盒"對象并傳遞給你的委托,超時設置和boolean標志指示了關閉和循環的請求。所有進入池中的線程都被認為是后臺線程,這就意味著它們不再由應用程序控制,而是由系統控制直到應用程序退出。

注意:如果這時候調用Abort操作,可能會發生意想不到的情況。

你也可以通過調用QueueUserWorkItem方法使用線程池,指定委托并立即被執行。這時你不能在多任務情況下保存共享線程,但是可以得到另外的好處:線程池會保持一個線程的總容量,當作業數超出容量時自動插入任務。

class Test
    {
        static object workerLocker = new object;
        static int runningWorkers = 100;
        public static void Main
        {
            for
            {
                ThreadPool.QueueUserWorkItem;
            }
            Console.WriteLine;
            lock
            {
                while
                    Monitor.Wait;
            }
            Console.WriteLine;
            Console.ReadLine;
        }
        public static void Go
        {
            Console.WriteLine;
            Thread.Sleep;
            Console.WriteLine;
            lock
            {
                runningWorkers--;
                Monitor.Pulse;
            }
        }
    }

為了傳遞多個對象到目標方法,你必須定義一個客戶對象并包含所有屬性或通過調用異步的委托。如Go方法接受兩參數:

ThreadPool.QueueUserWorkItem { Go ; });

其他的方法可以使用異步委托。


  Tag: 設計公司 | 網頁設計公司 | 廣告公司 | 網站設計 | 平面設計 | 互動媒體 | 網頁設計 | Web design | Website design | design house | 媒體公司 | Iphone app | 程式設計 | Flash 網頁 | Flash game | 動畫設計 | 後期製作 | 網上商店 | 網上宣傳 | 網頁服務 | 

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 yiyuanle 的頭像
    yiyuanle

    yiyuanle

    yiyuanle 發表在 痞客邦 留言(0) 人氣()