久久久久在线观看_又色又爽又黄的免费视频播放_一区中文字幕_日韩电影在线播放

今日頭條 焦點(diǎn)資訊 營(yíng)銷之道 企業(yè)報(bào)道 淘寶運(yùn)營(yíng) 網(wǎng)站建設(shè) 軟件開發(fā) 400電話
  當(dāng)前位置: 首頁(yè) » 資訊 » 網(wǎng)站建設(shè) » 正文

在.Net框架中C#實(shí)現(xiàn)多線程的同步方法詳解

放大字體  縮小字體 發(fā)布日期:2018-02-18  來(lái)源:新格網(wǎng)  作者:新格網(wǎng)  瀏覽次數(shù):847  【去百度看看】
核心提示:本文主要描述在C#中線程同步的方法。線程的基本概念網(wǎng)上資料也很多就不再贅述了。直接接入主題,在多線程開發(fā)的應(yīng)用中,線程同步是不可避免的。在.Net框架中,實(shí)現(xiàn)線程同步主要通過(guò)以下的幾種方式來(lái)實(shí)現(xiàn),在MSDN的線程指南中已經(jīng)講了幾種,本文結(jié)合作者實(shí)際中用到的方式

本文主要描述在C#中線程同步的方法。線程的基本概念網(wǎng)上資料也很多就不再贅述了。直接接入主題,在多線程開發(fā)的應(yīng)用中,線程同步是不可避免的。在.Net框架中,實(shí)現(xiàn)線程同步主要通過(guò)以下的幾種方式來(lái)實(shí)現(xiàn),在MSDN的線程指南中已經(jīng)講了幾種,本文結(jié)合作者實(shí)際中用到的方式一起說(shuō)明一下。

1. 維護(hù)自由鎖(InterLocked)實(shí)現(xiàn)同步

2. 監(jiān)視器(Monitor)和互斥鎖(lock)

3. 讀寫鎖(ReadWriteLock)

4. 系統(tǒng)內(nèi)核對(duì)象

1) 互斥(Mutex), 信號(hào)量(Semaphore), 事件(AutoResetEvent/ManualResetEvent)

2) 線程池

除了以上的這些對(duì)象之外實(shí)現(xiàn)線程同步的還可以使用Thread.Join方法。這種方法比較簡(jiǎn)單,當(dāng)你在第一個(gè)線程運(yùn)行時(shí)想等待第二個(gè)線程執(zhí)行結(jié)果,那么你可以讓第二個(gè)線程Join進(jìn)來(lái)就可以了。

自由鎖(InterLocked)

對(duì)一個(gè)32位的整型數(shù)進(jìn)行遞增和遞減操作來(lái)實(shí)現(xiàn)鎖,有人會(huì)問(wèn)為什么不用++或--來(lái)操作。因?yàn)樵诙嗑程中對(duì)鎖進(jìn)行操作必須是原子的,而++和--不具備這個(gè)能力。InterLocked類還提供了兩個(gè)另外的函數(shù)Exchange, CompareExchange用于實(shí)現(xiàn)交換和比較交換。Exchange操作會(huì)將新值設(shè)置到變量中并返回變量的原來(lái)值: int oVal = InterLocked.Exchange(ref val, 1)。

監(jiān)視器(Monitor)

在MSDN中對(duì)Monitor的描述是: Monitor 類通過(guò)向單個(gè)線程授予對(duì)象鎖來(lái)控制對(duì)對(duì)象的訪問(wèn)。

Monitor類是一個(gè)靜態(tài)類因此你不能通過(guò)實(shí)例化來(lái)得到類的對(duì)象。Monitor的成員可以查看MSDN,基本上Monitor的效果和lock是一樣的,通過(guò)加鎖操作Enter設(shè)置臨界區(qū),完成操作后使用Exit操作來(lái)釋放對(duì)象鎖。不過(guò)相對(duì)來(lái)說(shuō)Monitor的功能更強(qiáng),Moniter可以進(jìn)行測(cè)試鎖的狀態(tài),因此你可以控制對(duì)臨界區(qū)的訪問(wèn)選擇,等待or離開, 而且Monitor還可以在釋放鎖之前通知指定的對(duì)象,更重要的是使用Monitor可以跨越方法來(lái)操作。Monitor提供的方法很少就只有獲取鎖的方法Enter, TryEnter;釋放鎖的方法Wait, Exit;還有消息通知方法Pulse, PulseAll。經(jīng)典的Monitor操作是這樣的:

// 通監(jiān)視器來(lái)創(chuàng)建臨界區(qū)
        static public void DelUser(string name)
        {
            try
            {
                // 等待線程進(jìn)入
                Monitor.Enter(Names);
                Names.Remove(name);
                Console.WriteLine("Del: {0}", Names.Count);
                Monitor.Pulse(Names);
            }
            finally
            {
                // 釋放對(duì)象鎖
                Monitor.Exit(Names);
            }
        }
    }

其中Names是一個(gè)List, 這里有一個(gè)小技巧,如果你想聲明整個(gè)方法為線程同步可以使用方法屬性:

// 通過(guò)屬性設(shè)置整個(gè)方法為臨界區(qū)
        [MethodImpl(MethodImplOptions.Synchronized)]
        static public void AddUser(string name)
        {
            Names.Add(name);
            Console.WriteLine("Add: {0}",Names.Count);
        }

對(duì)于Monitor的使用有一個(gè)方法是比較詭異的,那就是Wait方法。在MSDN中對(duì)Wait的描述是: 釋放對(duì)象上的鎖以便允許其他線程鎖定和訪問(wèn)該對(duì)象。

這里提到的是先釋放鎖,那么顯然我們需要先得到鎖,否則調(diào)用Wait會(huì)出現(xiàn)異常,所以我們必須在Wait前面調(diào)用Enter方法或其他獲取鎖的方法,如lock,這點(diǎn)很重要。對(duì)應(yīng)Enter方法,Monitor給出來(lái)另一種實(shí)現(xiàn)TryEnter。這兩種方法的主要區(qū)別在于是否阻塞當(dāng)前線程,Enter方法在獲取不到鎖時(shí),會(huì)阻塞當(dāng)前線程直到得到鎖。不過(guò)缺點(diǎn)是如果永遠(yuǎn)得不到鎖那么程序就會(huì)進(jìn)入死鎖狀態(tài)。我們可以采用Wait來(lái)解決,在調(diào)用Wait時(shí)加入超時(shí)時(shí)限就可以。

if (Monitor.TryEnter(Names))
            {
                Monitor.Wait(Names, 1000); // !!
                Names.Remove(name);
                Console.WriteLine("Del: {0}", Names.Count);
                Monitor.Pulse(Names);
            }

互斥鎖(lock)

lock關(guān)鍵字是實(shí)現(xiàn)線程同步的比較簡(jiǎn)單的方式,其實(shí)就是設(shè)置一個(gè)臨界區(qū)。在lock之后的{...}區(qū)塊為一個(gè)臨界區(qū),當(dāng)進(jìn)入臨界區(qū)時(shí)加互斥鎖,離開臨界區(qū)時(shí)釋放互斥鎖。MSDN對(duì)lock關(guān)鍵字的描述是: lock 關(guān)鍵字可將語(yǔ)句塊標(biāo)記為臨界區(qū),方法是獲取給定對(duì)象的互斥鎖,執(zhí)行語(yǔ)句,然后釋放該鎖。

具體例子如下:

static public void ThreadFunc(object name)
        {
            string str = name as string;
            Random rand = new Random();
            int count = rand.Next(100, 200);
            for (int i = 0; i < count; i++)
            {
                lock (NumList)
                {
                    NumList.Add(i);
                    Console.WriteLine("{0} {1}", str, i);
                }
            }
        }

對(duì)lock的使用有幾點(diǎn)建議:對(duì)實(shí)例鎖定lock(this),對(duì)靜態(tài)變量鎖定lock(typeof(val))。lock的對(duì)象訪問(wèn)權(quán)限最好是private,否則會(huì)出現(xiàn)失去訪問(wèn)控制現(xiàn)象。

讀寫鎖(ReadWriteLock)

讀寫鎖的出現(xiàn)主要是在很多情況下,我們讀資源的操作要多于寫資源的操作。但是如果每次只對(duì)資源賦予一個(gè)線程的訪問(wèn)權(quán)限顯然是低效的,讀寫鎖的優(yōu)勢(shì)是同時(shí)可以有多個(gè)線程對(duì)同一資源進(jìn)行讀操作。因此在讀操作比寫操作多很多,并且寫操作的時(shí)間很短的情況下使用讀寫鎖是比較有效率的。讀寫鎖是一個(gè)非靜態(tài)類所以你在使用前需要先聲明一個(gè)讀寫鎖對(duì)象:

static private ReaderWriterLock _rwlock = new ReaderWriterLock();

讀寫鎖是通過(guò)調(diào)用AcquireReaderLock,ReleaseReaderLock,AcquireWriterLock,ReleaseWriterLock來(lái)完成讀鎖和寫鎖控制的

static public void ReaderThread(int thrdId)
        {
            try
            { // 請(qǐng)求讀鎖,如果100ms超時(shí)退出
                _rwlock.AcquireReaderLock(10);
                try
                {
                    int inx = _rand.Next(_list.Count);
                    if (inx < _list.Count)
                        Console.WriteLine("{0}thread {1}", thrdId, _list[inx]);
                }
                finally
                {
                    _rwlock.ReleaseReaderLock();
                }
            }
            catch (ApplicationException) // 如果請(qǐng)求讀鎖失敗
            {
                Console.WriteLine("{0}thread get reader lock out time!", thrdId);
            }
        }
        static public void WriterThread()
        {
            try
            {
                // 請(qǐng)求寫鎖
                _rwlock.AcquireWriterLock(100);
                try
                {
                    string val = _rand.Next(200).ToString();
                    _list.Add(val); // 寫入資源
                    Console.WriteLine("writer thread has written {0}", val);
                }
                finally
                { // 釋放寫鎖
                    _rwlock.ReleaseWriterLock();
                }
            }
            catch (ApplicationException)
            {
                Console.WriteLine("Get writer thread lock out time!");
            }
        }

如果你想在讀的時(shí)候插入寫操作請(qǐng)使用UpgradeToWriterLock和DowngradeFromWriterLock來(lái)進(jìn)行操作,而不是釋放讀鎖。

static private void UpgradeAndDowngrade(int thrdId)
        {
            try
            {
                _rwlock.AcquireReaderLock(10);
                try
                {
                    try
                    {
                        // 提升讀鎖到寫鎖
                        Lockcookie lc = _rwlock.UpgradeToWriterLock(100);
                        try
                        {
                            string val = _rand.Next(500).ToString();

                            _list.Add(val); Console.WriteLine
("Upgrade Thread{0} add {1}", thrdId, val);
                        }
                        finally
                        { // 下降寫鎖
                            _rwlock.DowngradeFromWriterLock(ref lc);
                        }
                    }
                    catch (ApplicationException)
                    {
                        Console.WriteLine("{0}thread upgrade reader lock failed!", thrdId);
                    }
                }
                finally
                {
                    // 釋放原來(lái)的讀鎖
                    _rwlock.ReleaseReaderLock();
                }
            }
            catch (ApplicationException)
            {
                Console.WriteLine("{0}thread get reader lock out time!", thrdId);
            }
        }

這里有一點(diǎn)要注意的就是讀鎖和寫鎖的超時(shí)等待時(shí)間間隔的設(shè)置。通常情況下設(shè)置寫鎖的等待超時(shí)要比讀鎖的長(zhǎng),否則會(huì)經(jīng)常發(fā)生寫鎖等待失敗的情況。

系統(tǒng)內(nèi)核對(duì)象 互斥對(duì)象(Mutex)

互斥對(duì)象的作用有點(diǎn)類似于監(jiān)視器對(duì)象,確保一個(gè)代碼塊在同一時(shí)刻只有一個(gè)線程在執(zhí)行。互斥對(duì)象和監(jiān)視器對(duì)象的主要區(qū)別就是,互斥對(duì)象一般用于跨進(jìn)程間的線程同步,而監(jiān)視器對(duì)象則用于進(jìn)程內(nèi)的線程同步。互斥對(duì)象有兩種:一種是命名互斥;另一種是匿名互斥。在跨進(jìn)程中使用到的就是命名互斥,一個(gè)已命名的互斥就是一個(gè)系統(tǒng)級(jí)的互斥,它可以被其他進(jìn)程所使用,只要在創(chuàng)建互斥時(shí)指定打開互斥的名稱就可以。在.Net中互斥是通過(guò)Mutex類來(lái)實(shí)現(xiàn)。

其實(shí)對(duì)于OpenExisting函數(shù)有兩個(gè)重載版本,

Mutex.OpenExisting (String)

Mutex.OpenExisting (String, MutexRights)

對(duì)于默認(rèn)的第一個(gè)函數(shù)其實(shí)是實(shí)現(xiàn)了第二個(gè)函數(shù) MutexRights.Synchronize|MutexRights.Modify操作。

由于監(jiān)視器的設(shè)計(jì)是基于.Net框架,而Mutex類是系統(tǒng)內(nèi)核對(duì)象封裝了win32的一個(gè)內(nèi)核結(jié)構(gòu)來(lái)實(shí)現(xiàn)互斥,并且互斥操作需要請(qǐng)求中斷來(lái)完成,因此在進(jìn)行進(jìn)程內(nèi)線程同步的時(shí)候性能上要比互斥要好。

典型的使用Mutex同步需要完成三個(gè)步驟的操作:1.打開或者創(chuàng)建一個(gè)Mutex實(shí)例;2.調(diào)用WaitOne()來(lái)請(qǐng)求互斥對(duì)象;3.最后調(diào)用ReleaseMutex來(lái)釋放互斥對(duì)象。

static public void AddString(string str)
        {
            // 設(shè)置超時(shí)時(shí)限并在wait前退出非默認(rèn)托管上下文
            if (_mtx.WaitOne(1000, true))
            {
                _resource.Add(str);
                _mtx.ReleaseMutex();
            }
        }

需要注意的是,WaitOne和ReleaseMutex必須成對(duì)出現(xiàn),否則會(huì)導(dǎo)致進(jìn)程死鎖的發(fā)生,這時(shí)系統(tǒng)(.Net2.0)框架會(huì)拋出AbandonedMutexException異常。

信號(hào)量(Semaphore)

信號(hào)量就像一個(gè)夜總會(huì):它有確切的容量,并被保鏢控制。一旦滿員,就沒(méi)有人能再進(jìn)入,其他人必須在外面排隊(duì)。那么在里面離開一個(gè)人后,隊(duì)頭的人就可以進(jìn)入。信號(hào)量的構(gòu)造函數(shù)需要提供至少兩個(gè)參數(shù)-現(xiàn)有的人數(shù)和最大的人數(shù)。

信號(hào)量的行為有點(diǎn)類似于Mutex或是lock,但是信號(hào)量沒(méi)有擁有者。任意線程都可以調(diào)用Release來(lái)釋放信號(hào)量而不像Mutex和lock那樣需要線程得到資源才能釋放。

class SemaphoreTest
    {
        static Semaphore s = new Semaphore(3, 3); // 當(dāng)前值=3; 容量=3
        static void Main()
        {
            for (int i = 0; i < 10; i++)
                new Thread(Go).Start();
        }
        static void Go()
        {
            while (true)
            {
                s.WaitOne();
                Thread.Sleep(100); // 一次只有個(gè)線程能被處理
                s.Release();
            }
        }
    } 

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

一個(gè)AutoResetEvent象是一個(gè)"檢票輪盤":插入一張通行證然后讓一個(gè)人通過(guò)。"auto"的意思就是這個(gè)"輪盤"自動(dòng)關(guān)閉或者打開讓某人通過(guò)。線程將在調(diào)用WaitOne后進(jìn)行等待或者是阻塞,并且通過(guò)調(diào)用Set操作來(lái)插入線程。如果一堆線程調(diào)用了WaitOne操作,那么"輪盤"就會(huì)建立一個(gè)等待隊(duì)列。一個(gè)通行證可以來(lái)自任意一個(gè)線程,換句話說(shuō)任意一個(gè)線程都可以通過(guò)訪問(wèn)AutoResetEvent對(duì)象并調(diào)用Set來(lái)釋放一個(gè)阻塞的線程。

如果在Set被調(diào)用的時(shí)候沒(méi)有線程等待,那么句柄就會(huì)一直處于打開狀態(tài)直到有線程調(diào)用了WaitOne操作。這種行為避免了競(jìng)爭(zhēng)條件-當(dāng)一個(gè)線程還沒(méi)來(lái)得急釋放而另一個(gè)線程就開始進(jìn)入的情況。因此重復(fù)的調(diào)用Set操作一個(gè)"輪盤"哪怕是沒(méi)有等待線程也不會(huì)一次性的讓所有線程進(jìn)入。

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

class BasicWaitHandle
    {
        static EventWaitHandle wh = new AutoResetEvent(false);
        static void Main()
        {
            new Thread(Waiter).Start();
            Thread.Sleep(1000); // 等待一會(huì)兒
            wh.Set(); // 喚醒
        }
        static void Waiter()
        {
            Console.WriteLine("Waiting...");
            wh.WaitOne(); // 等待喚醒
            Console.WriteLine("Notified");
        }
    }

ManualResetEvent

ManualResetEvent是AutoResetEvent的一個(gè)特例。它的不同之處在于在線程調(diào)用WaitOne后不會(huì)自動(dòng)的重置狀態(tài)。它的工作機(jī)制有點(diǎn)象是開關(guān):調(diào)用Set打開并允許其他線程進(jìn)行WaitOne;調(diào)用Reset關(guān)閉那么排隊(duì)的線程就要等待,直到下一次打開。可以使用一個(gè)帶volatile聲明的boolean字段來(lái)模擬間斷休眠 - 通過(guò)重復(fù)檢測(cè)標(biāo)志,然后休眠一小段時(shí)間。

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

線程池(Thread Pooling)

如果你的應(yīng)用程序擁有大量的線程并花費(fèi)大量的時(shí)間阻塞在一個(gè)Wait Handle上,那么你要考慮使用線程池(Thead pooling)來(lái)處理。線程池通過(guò)合并多個(gè)Wait Handle來(lái)節(jié)約等待的時(shí)間。當(dāng)Wait Handle被激活時(shí),使用線程池你需要注冊(cè)一個(gè)Wait Handle到一個(gè)委托去執(zhí)行。通過(guò)調(diào)用ThreadPool.RegisterWaitForSingleObject方法:

class Test
    {
        static ManualResetEvent starter = new ManualResetEvent(false);
        public static void Main()
        {
            ThreadPool.RegisterWaitForSingleObject(starter, Go, "hello", -1, true);
            Thread.Sleep(5000);
            Console.WriteLine("Signaling worker...");
            starter.Set();
            Console.ReadLine();
        }
        public static void Go(object data, bool timedOut)
        {
            Console.WriteLine("Started " + data); // Perform task...
        }
    }

對(duì)于Wait Handle和委托,RegisterWaitForSingleObject接受一個(gè)"黑盒"對(duì)象并傳遞給你的委托(就像ParameterizedThreadStart),超時(shí)設(shè)置和boolean標(biāo)志指示了關(guān)閉和循環(huán)的請(qǐng)求。所有進(jìn)入池中的線程都被認(rèn)為是后臺(tái)線程,這就意味著它們不再由應(yīng)用程序控制,而是由系統(tǒng)控制直到應(yīng)用程序退出。

注意:如果這時(shí)候調(diào)用Abort操作,可能會(huì)發(fā)生意想不到的情況。

你也可以通過(guò)調(diào)用QueueUserWorkItem方法使用線程池,指定委托并立即被執(zhí)行。這時(shí)你不能在多任務(wù)情況下保存共享線程,但是可以得到另外的好處:線程池會(huì)保持一個(gè)線程的總?cè)萘浚?dāng)作業(yè)數(shù)超出容量時(shí)自動(dòng)插入任務(wù)。

class Test
    {
        static object workerLocker = new object();
        static int runningWorkers = 100;
        public static void Main()
        {
            for (int i = 0; i < runningWorkers; i++)
            {
                ThreadPool.QueueUserWorkItem(Go, i);
            }
            Console.WriteLine("Waiting for threads to complete...");
            lock (workerLocker)
            {
                while (runningWorkers > 0)
                    Monitor.Wait(workerLocker);
            }
            Console.WriteLine("Complete!");
            Console.ReadLine();
        }
        public static void Go(object instance)
        {
            Console.WriteLine("Started: " + instance);
            Thread.Sleep(1000);
            Console.WriteLine("Ended: " + instance);
            lock (workerLocker)
            {
                runningWorkers--;
                Monitor.Pulse(workerLocker);
            }
        }
    }

為了傳遞多個(gè)對(duì)象到目標(biāo)方法,你必須定義一個(gè)客戶對(duì)象并包含所有屬性或通過(guò)調(diào)用異步的委托。如Go方法接受兩參數(shù):

ThreadPool.QueueUserWorkItem (delegate (object notUsed) { Go (23,34); });

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

 
關(guān)鍵詞: C#,.Net框架,多線程同步
 
[ 資訊搜索 ]  [ 加入收藏 ]  [ 告訴好友 ]  [ 打印本文 ]  [ 違規(guī)舉報(bào) ]  [ 關(guān)閉窗口 ]

 
0條 [查看全部]  相關(guān)評(píng)論

 
網(wǎng)站首頁(yè) | 關(guān)于我們 | 聯(lián)系方式 | 使用協(xié)議 | 版權(quán)隱私 | 網(wǎng)站地圖 | 排名推廣 | 廣告服務(wù) | 積分換禮 | 網(wǎng)站留言 | RSS訂閱 | 吉ICP備19006030號(hào)-4
企業(yè)800網(wǎng) · 提供技術(shù)支持