欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#多線程系列之線程等待

 更新時(shí)間:2022年02月14日 09:12:36   作者:癡者工良  
本文詳細(xì)講解了C#多線程中的線程等待,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

前言

volatile 關(guān)鍵字

volatile 關(guān)鍵字指示一個(gè)字段可以由多個(gè)同時(shí)執(zhí)行的線程修改。

我們繼續(xù)使用《C#多線程(3):原子操作》中的示例:

        static void Main(string[] args)
        {
            for (int i = 0; i < 5; i++)
            {
                new Thread(AddOne).Start();
            }
            Thread.Sleep(TimeSpan.FromSeconds(5));
            Console.WriteLine("sum = " + sum);
            Console.ReadKey();
        }
        private static int sum = 0;
        public static void AddOne()
        {
            for (int i = 0; i < 100_0000; i++)
            {
                sum += 1;
            }
        }

運(yùn)行后你會(huì)發(fā)現(xiàn),結(jié)果不為 500_0000,而使用 Interlocked.Increment(ref sum);后,可以獲得準(zhǔn)確可靠的結(jié)果。

你試試再運(yùn)行下面的示例:

        static void Main(string[] args)
        {
            for (int i = 0; i < 5; i++)
            {
                new Thread(AddOne).Start();
            }
            Thread.Sleep(TimeSpan.FromSeconds(5));
            Console.WriteLine("sum = " + sum);
            Console.ReadKey();
        }
        private static volatile int sum = 0;
        public static void AddOne()
        {
            for (int i = 0; i < 100_0000; i++)
            {
                sum += 1;
            }
        }

你以為正常了?哈哈哈,并沒(méi)有。

volatile 的作用在于讀,保證了觀察的順序和寫(xiě)入的順序一致,每次讀取的都是最新的一個(gè)值;不會(huì)干擾寫(xiě)操作。

詳情請(qǐng)點(diǎn)擊:https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/volatile

其原理解釋:https://theburningmonk.com/2010/03/threading-understanding-the-volatile-modifier-in-csharp/

三種常用等待

這三種等待分別是:

Thread.Sleep();
Thread.SpinWait();
Task.Delay();

Thread.Sleep(); 會(huì)阻塞線程,使得線程交出時(shí)間片,然后處于休眠狀態(tài),直至被重新喚醒;適合用于長(zhǎng)時(shí)間的等待;

Thread.SpinWait(); 使用了自旋等待,等待過(guò)程中會(huì)進(jìn)行一些的運(yùn)算,線程不會(huì)休眠,用于微小的時(shí)間等待;長(zhǎng)時(shí)間等待會(huì)影響性能;

Task.Delay(); 用于異步中的等待,異步的文章后面才寫(xiě),這里先不理會(huì);

這里我們還需要繼續(xù) SpinWait 和 SpinLock 這兩個(gè)類型,最后再進(jìn)行總結(jié)對(duì)照。

再說(shuō)自旋和阻塞

前面我們學(xué)習(xí)過(guò)自旋和阻塞的區(qū)別,這里再來(lái)擼清楚一下。

線程等待有內(nèi)核模式(Kernel Mode)和用戶模式(User Model)。

因?yàn)橹挥胁僮飨到y(tǒng)才能控制線程的生命周期,因此使用 Thread.Sleep() 等方式阻塞線程,發(fā)生上下文切換,此種等待稱為內(nèi)核模式。

用戶模式使線程等待,并不需要線程切換上下文,而是讓線程通過(guò)執(zhí)行一些無(wú)意義的運(yùn)算,實(shí)現(xiàn)等待。也稱為自旋。

SpinWait 結(jié)構(gòu)

微軟文檔定義:為基于自旋的等待提供支持。

SpinWait 是結(jié)構(gòu)體;Thread.SpinWait() 的原理就是 SpinWait 。
如果你想了解 Thread.SpinWait() 是怎么實(shí)現(xiàn)的,可以參考 https://www.tabsoverspaces.com/233735-how-is-thread-spinwait-actually-implemented

線程阻塞是會(huì)耗費(fèi)上下文切換的,對(duì)于過(guò)短的線程等待,這種切換的代價(jià)會(huì)比較昂貴的。在我們前面的示例中,大量使用了 Thread.Sleep() 和各種類型的等待方法,這其實(shí)是不合理的。

SpinWait 則提供了更好的選擇。

屬性和方法

老規(guī)矩,先來(lái)看一下 SpinWait 常用的屬性和方法。

屬性:

屬性說(shuō)明
Count獲取已對(duì)此實(shí)例調(diào)用 SpinOnce() 的次數(shù)。
NextSpinWillYield獲取對(duì) SpinOnce() 的下一次調(diào)用是否將產(chǎn)生處理器,同時(shí)觸發(fā)強(qiáng)制上下文切換。

方法:

方法說(shuō)明
Reset()重置自旋計(jì)數(shù)器。
SpinOnce()執(zhí)行單一自旋。
SpinOnce(Int32)執(zhí)行單一自旋,并在達(dá)到最小旋轉(zhuǎn)計(jì)數(shù)后調(diào)用 Sleep(Int32) 。
SpinUntil(Func)在指定條件得到滿足之前自旋。
SpinUntil(Func, Int32)在指定條件得到滿足或指定超時(shí)過(guò)期之前自旋。
SpinUntil(Func, TimeSpan)在指定條件得到滿足或指定超時(shí)過(guò)期之前自旋。

自旋示例

下面來(lái)實(shí)現(xiàn)一個(gè)讓當(dāng)前線程等待其它線程完成任務(wù)的功能。

其功能是開(kāi)辟一個(gè)線程對(duì) sum 進(jìn)行 +1,當(dāng)新的線程完成運(yùn)算后,主線程才能繼續(xù)運(yùn)行。

    class Program
    {
        static void Main(string[] args)
        {
            new Thread(DoWork).Start();

            // 等待上面的線程完成工作
            MySleep();

            Console.WriteLine("sum = " + sum);
            Console.ReadKey();
        }

        private static int sum = 0;
        private static void DoWork()
        {
            for (int i = 0; i < 1000_0000; i++)
            {
                sum++;
            }
            isCompleted = true;
        }

        // 自定義等待等待
        private static bool isCompleted = false;
        private static void MySleep()
        {
            int i = 0;
            while (!isCompleted)
            {
                i++;
            }
        }
    }

新的實(shí)現(xiàn)

我們改進(jìn)上面的示例,修改 MySleep 方法,改成:

        private static bool isCompleted = false;        
        private static void MySleep()
        {
            SpinWait wait = new SpinWait();
            while (!isCompleted)
            {
                wait.SpinOnce();
            }
        }

或者改成

        private static bool isCompleted = false;        
        private static void MySleep()
        {
            SpinWait.SpinUntil(() => isCompleted);
        }

SpinLock 結(jié)構(gòu)

微軟文檔:提供一個(gè)相互排斥鎖基元,在該基元中,嘗試獲取鎖的線程將在重復(fù)檢查的循環(huán)中等待,直至該鎖變?yōu)榭捎脼橹埂?/p>

SpinLock 稱為自旋鎖,適合用在頻繁爭(zhēng)用而且等待時(shí)間較短的場(chǎng)景。主要特征是避免了阻塞,不出現(xiàn)昂貴的上下文切換。

筆者水平有限,關(guān)于 SpinLock ,可以參考 https://www.c-sharpcorner.com/UploadFile/1d42da/spinlock-class-in-threading-C-Sharp/

另外,還記得 Monitor 嘛?SpinLock 跟 Monitor 比較像噢~http://www.dbjr.com.cn/article/237307.htm

在《C#多線程(10:讀寫(xiě)鎖)》中,我們介紹了 ReaderWriterLock 和 ReaderWriterLockSlim ,而 ReaderWriterLockSlim 內(nèi)部依賴于 SpinLock,并且比 ReaderWriterLock 快了三倍。

屬性和方法

SpinLock 常用屬性和方法如下:

屬性:

屬性說(shuō)明
IsHeld獲取鎖當(dāng)前是否已由任何線程占用。
IsHeldByCurrentThread獲取鎖是否已由當(dāng)前線程占用。
IsThreadOwnerTrackingEnabled獲取是否已為此實(shí)例啟用了線程所有權(quán)跟蹤。

方法:

方法說(shuō)明
Enter(Boolean)采用可靠的方式獲取鎖,這樣,即使在方法調(diào)用中發(fā)生異常的情況下,都能采用可靠的方式檢查 lockTaken 以確定是否已獲取鎖。
Exit()釋放鎖。
Exit(Boolean)釋放鎖。
TryEnter(Boolean)嘗試采用可靠的方式獲取鎖,這樣,即使在方法調(diào)用中發(fā)生異常的情況下,都能采用可靠的方式檢查 lockTaken 以確定是否已獲取鎖。
TryEnter(Int32, Boolean)嘗試采用可靠的方式獲取鎖,這樣,即使在方法調(diào)用中發(fā)生異常的情況下,都能采用可靠的方式檢查 lockTaken 以確定是否已獲取鎖。
TryEnter(TimeSpan, Boolean)嘗試采用可靠的方式獲取鎖,這樣,即使在方法調(diào)用中發(fā)生異常的情況下,都能采用可靠的方式檢查 lockTaken 以確定是否已獲取鎖。

示例

SpinLock 的模板如下:

        private static void DoWork()
        {
            SpinLock spinLock = new SpinLock();
            bool isGetLock = false;     // 是否已獲得了鎖
            try
            {
                spinLock.Enter(ref isGetLock);
                // 運(yùn)算
            }
            finally
            {
                if (isGetLock)
                    spinLock.Exit();
            }
        }

這里就不寫(xiě)場(chǎng)景示例了。

需要注意的是, SpinLock 實(shí)例不能共享,也不能重復(fù)使用。

等待性能對(duì)比

大佬的文章,.NET 中的多種鎖性能測(cè)試數(shù)據(jù):http://kejser.org/synchronisation-in-net-part-3-spinlocks-and-interlocks/

這里我們簡(jiǎn)單測(cè)試一下阻塞和自旋的性能測(cè)試對(duì)比。

我們經(jīng)常說(shuō),Thread.Sleep() 會(huì)發(fā)生上下文切換,出現(xiàn)比較大的性能損失。具體有多大呢?我們來(lái)測(cè)試一下。(以下運(yùn)算都是在 Debug 下測(cè)試)

測(cè)試 Thread.Sleep(1)

        private static void DoWork()
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();
            for (int i = 0; i < 1_0000; i++)
            {
                Thread.Sleep(1);
            }
            watch.Stop();
            Console.WriteLine(watch.ElapsedMilliseconds);
        }

筆者機(jī)器測(cè)試,結(jié)果大約 20018。Thread.Sleep(1) 減去等待的時(shí)間 10000 毫秒,那么進(jìn)行 10000 次上下文切換需要花費(fèi) 10000 毫秒,約每次 1 毫秒。

上面示例改成:

            for (int i = 0; i < 1_0000; i++)
            {
                Thread.Sleep(2);
            }

運(yùn)算,發(fā)現(xiàn)結(jié)果為 30013,也說(shuō)明了上下文切換,大約需要一毫秒。

改成 Thread.SpinWait(1000)

            for (int i = 0; i < 100_0000; i++)
            {
                Thread.SpinWait(1000);
            }

結(jié)果為 28876,說(shuō)明自旋 1000 次,大約需要 0.03 毫秒。

到此這篇關(guān)于C#多線程系列之線程等待的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • C#多線程與異步的區(qū)別詳解

    C#多線程與異步的區(qū)別詳解

    多線程和異步操作兩者都可以達(dá)到避免調(diào)用線程阻塞的目的,從而提高軟件的可響應(yīng)性。甚至有些時(shí)候我們就認(rèn)為多線程和異步操作是等同的概念。但是,多線程和異步操作還是有一些區(qū)別的。而這些區(qū)別造成了使用多線程和異步操作的時(shí)機(jī)的區(qū)別
    2017-06-06
  • C#抓取網(wǎng)頁(yè)數(shù)據(jù) 解析標(biāo)題描述圖片等信息 去除HTML標(biāo)簽

    C#抓取網(wǎng)頁(yè)數(shù)據(jù) 解析標(biāo)題描述圖片等信息 去除HTML標(biāo)簽

    本文主要一步一步介紹利用C#抓取頁(yè)面數(shù)據(jù)的過(guò)程,抓取HTML,獲取標(biāo)題、描述、圖片等信息,并去除HTML,希望對(duì)大家有所幫助。
    2016-04-04
  • C#后臺(tái)調(diào)用WebApi接口的實(shí)現(xiàn)方法

    C#后臺(tái)調(diào)用WebApi接口的實(shí)現(xiàn)方法

    本文主要介紹了C#后臺(tái)調(diào)用WebApi接口的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • 基于C#實(shí)現(xiàn)的HOOK鍵盤(pán)鉤子實(shí)例代碼

    基于C#實(shí)現(xiàn)的HOOK鍵盤(pán)鉤子實(shí)例代碼

    這篇文章主要介紹了基于C#實(shí)現(xiàn)的HOOK鍵盤(pán)鉤子實(shí)例,需要的朋友可以參考下
    2014-07-07
  • automation服務(wù)器不能創(chuàng)建對(duì)象 解決方法

    automation服務(wù)器不能創(chuàng)建對(duì)象 解決方法

    本文主要介紹如何解決“automation服務(wù)器不能創(chuàng)建對(duì)象”錯(cuò)誤,從而解決Visual Studio.Net不能正常使用的問(wèn)題,需要的朋友可以參考下。
    2016-06-06
  • 利用C#實(shí)現(xiàn)合并Word文檔功能

    利用C#實(shí)現(xiàn)合并Word文檔功能

    合并Word文檔可以快速地將多份編輯好的文檔合在一起,避免復(fù)制粘貼時(shí)遺漏內(nèi)容,以及耗費(fèi)不必要的時(shí)間。本文將分為以下兩部分介紹如何通過(guò)C#合并Word文檔,并附上VB.NET代碼供大家參考,希望對(duì)大家有所幫助
    2022-12-12
  • C#實(shí)現(xiàn)把圖片轉(zhuǎn)換成二進(jìn)制以及把二進(jìn)制轉(zhuǎn)換成圖片的方法示例

    C#實(shí)現(xiàn)把圖片轉(zhuǎn)換成二進(jìn)制以及把二進(jìn)制轉(zhuǎn)換成圖片的方法示例

    這篇文章主要介紹了C#實(shí)現(xiàn)把圖片轉(zhuǎn)換成二進(jìn)制以及把二進(jìn)制轉(zhuǎn)換成圖片的方法,結(jié)合具體實(shí)例形式分析了基于C#的圖片與二進(jìn)制相互轉(zhuǎn)換以及圖片保存到數(shù)據(jù)庫(kù)的相關(guān)操作技巧,需要的朋友可以參考下
    2017-06-06
  • C#將字母或數(shù)字加密成字母的方法

    C#將字母或數(shù)字加密成字母的方法

    這篇文章主要介紹了C#將字母或數(shù)字加密成字母的方法,涉及C#操作字符串的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-03-03
  • C# wpf簡(jiǎn)單顏色板的實(shí)現(xiàn)

    C# wpf簡(jiǎn)單顏色板的實(shí)現(xiàn)

    wpf本身沒(méi)有提供顏色板之類的控件,有些業(yè)務(wù)使用場(chǎng)景需要使用顏色板之類的控件,本文就簡(jiǎn)單實(shí)現(xiàn),感興趣的可以了解一下
    2021-10-10
  • C#中感嘆號(hào)(!) 的作用總結(jié)

    C#中感嘆號(hào)(!) 的作用總結(jié)

    這篇文章主要給大家總結(jié)介紹了C#中感嘆號(hào)(!) 的作用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12

最新評(píng)論