日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無(wú)先后,達(dá)者為師

網(wǎng)站首頁(yè) 編程語(yǔ)言 正文

C#多線程系列之線程的創(chuàng)建和生命周期_C#教程

作者:癡者工良 ? 更新時(shí)間: 2022-04-19 編程語(yǔ)言

1,獲取當(dāng)前線程信息

Thread.CurrentThread?是一個(gè) 靜態(tài)的 Thread 類,Thread 的CurrentThread?屬性,可以獲取到當(dāng)前運(yùn)行線程的一些信息,其定義如下:

public static System.Threading.Thread CurrentThread { get; }

Thread 類有很多屬性和方法,這里就不列舉了,后面的學(xué)習(xí)會(huì)慢慢熟悉更多 API 和深入了解使用。

這里有一個(gè)簡(jiǎn)單的示例:

        static void Main(string[] args)
        {
            Thread thread = new Thread(OneTest);
            thread.Name = "Test";
            thread.Start();
            Console.ReadKey();
        }

        public static void OneTest()
        {
            Thread thisTHread = Thread.CurrentThread;
            Console.WriteLine("線程標(biāo)識(shí):" + thisTHread.Name);
            Console.WriteLine("當(dāng)前地域:" + thisTHread.CurrentCulture.Name);  // 當(dāng)前地域
            Console.WriteLine("線程執(zhí)行狀態(tài):" + thisTHread.IsAlive);
            Console.WriteLine("是否為后臺(tái)線程:" + thisTHread.IsBackground);
            Console.WriteLine("是否為線程池線程"+thisTHread.IsThreadPoolThread);
        }

輸出

線程標(biāo)識(shí):Test
當(dāng)前地域:zh-CN
線程執(zhí)行狀態(tài):True
是否為后臺(tái)線程:False
是否為線程池線程False

2,管理線程狀態(tài)

一般認(rèn)為,線程有五種狀態(tài):

新建(new 對(duì)象) 、就緒(等待CPU調(diào)度)、運(yùn)行(CPU正在運(yùn)行)、阻塞(等待阻塞、同步阻塞等)、死亡(對(duì)象釋放)。

理論的東西不說(shuō)太多,直接擼代碼。

2.1 啟動(dòng)與參數(shù)傳遞

新建線程簡(jiǎn)直滾瓜爛熟,無(wú)非?new?一下,然后?Start()。

Thread thread = new Thread(); 

Thread 的構(gòu)造函數(shù)有四個(gè):

public Thread(ParameterizedThreadStart start);

public Thread(ThreadStart start);

public Thread(ParameterizedThreadStart start, int maxStackSize);

public Thread(ThreadStart start, int maxStackSize);

我們以啟動(dòng)新的線程時(shí)傳遞參數(shù)來(lái)舉例,使用這四個(gè)構(gòu)造函數(shù)呢?

2.1.1 ParameterizedThreadStart

ParameterizedThreadStart 是一個(gè)委托,構(gòu)造函數(shù)傳遞的參數(shù)為需要執(zhí)行的方法,然后在?Start?方法中傳遞參數(shù)。

需要注意的是,傳遞的參數(shù)類型為 object,而且只能傳遞一個(gè)。

代碼示例如下:

        static void Main(string[] args)
        {
            string myParam = "abcdef";
            ParameterizedThreadStart parameterized = new ParameterizedThreadStart(OneTest);
            Thread thread = new Thread(parameterized);
            thread.Start(myParam);
            Console.ReadKey();
        }

        public static void OneTest(object obj)
        {
            string str = obj as string;
            if (string.IsNullOrEmpty(str))
                return;

            Console.WriteLine("新的線程已經(jīng)啟動(dòng)");
            Console.WriteLine(str);
        }

2.1.2 使用靜態(tài)變量或類成員變量

此種方法不需要作為參數(shù)傳遞,各個(gè)線程共享堆棧。

優(yōu)點(diǎn)是不需要裝箱拆箱,多線程可以共享空間;缺點(diǎn)是變量是大家都可以訪問(wèn),此種方式在多線程競(jìng)價(jià)時(shí),可能會(huì)導(dǎo)致多種問(wèn)題(可以加鎖解決)。

下面使用兩個(gè)變量實(shí)現(xiàn)數(shù)據(jù)傳遞:

    class Program
    {
        private string A = "成員變量";
        public static string B = "靜態(tài)變量";

        static void Main(string[] args)
        {
            // 創(chuàng)建一個(gè)類
            Program p = new Program();

            Thread thread1 = new Thread(p.OneTest1);
            thread1.Name = "Test1";
            thread1.Start();

            Thread thread2 = new Thread(OneTest2);
            thread2.Name = "Test2";
            thread2.Start();

            Console.ReadKey();
        }

        public void OneTest1()
        {
            Console.WriteLine("新的線程已經(jīng)啟動(dòng)");
            Console.WriteLine(A);       // 本身對(duì)象的其它成員
        }
        public static void OneTest2()
        {
            Console.WriteLine("新的線程已經(jīng)啟動(dòng)");
            Console.WriteLine(B);       // 全局靜態(tài)變量
        }
    }

2.1.3 委托與Lambda

原理是 Thread 的構(gòu)造函數(shù)?public Thread(ThreadStart start);,ThreadStart?是一個(gè)委托,其定義如下

public delegate void ThreadStart();

使用委托的話,可以這樣寫(xiě)

        static void Main(string[] args)
        {
            System.Threading.ThreadStart start = DelegateThread;

            Thread thread = new Thread(start);
            thread.Name = "Test";
            thread.Start();


            Console.ReadKey();
        }

        public static void DelegateThread()
        {
            OneTest("a", "b", 666, new Program());
        }
        public static void OneTest(string a, string b, int c, Program p)
        {
            Console.WriteLine("新的線程已經(jīng)啟動(dòng)");
        }

有那么一點(diǎn)點(diǎn)麻煩,不過(guò)我們可以使用 Lambda 快速實(shí)現(xiàn)。

使用 Lambda 示例如下:

        static void Main(string[] args)
        {
            Thread thread = new Thread(() =>
            {
                OneTest("a", "b", 666, new Program());
            });
            thread.Name = "Test";
            thread.Start();
            
            Console.ReadKey();
        }

        public static void OneTest(string a, string b, int c, Program p)
        {
            Console.WriteLine("新的線程已經(jīng)啟動(dòng)");
        }

提示:如果需要處理的算法比較簡(jiǎn)單的話,可以直接寫(xiě)進(jìn)委托中,不需要另外寫(xiě)方法啦。

可以看到,C# 是多么的方便。

2.2 暫停與阻塞

Thread.Sleep()?方法可以將當(dāng)前線程掛起一段時(shí)間,Thread.Join()?方法可以阻塞當(dāng)前線程一直等待另一個(gè)線程運(yùn)行至結(jié)束。

在等待線程?Sleep()?或?Join()?的過(guò)程中,線程是阻塞的(Blocket)。

? ? 阻塞的定義:當(dāng)線程由于特點(diǎn)原因暫停執(zhí)行,那么它就是阻塞的。?
? ? 如果線程處于阻塞狀態(tài),線程就會(huì)交出他的 CPU 時(shí)間片,并且不會(huì)消耗 CPU 時(shí)間,直至阻塞結(jié)束。?
? ? 阻塞會(huì)發(fā)生上下文切換。

代碼示例如下:

        static void Main(string[] args)
        {
            Thread thread = new Thread(OneTest);
            thread.Name = "小弟弟";

            Console.WriteLine($"{DateTime.Now}:大家在吃飯,吃完飯后要帶小弟弟逛街");
            Console.WriteLine("吃完飯了");
            Console.WriteLine($"{DateTime.Now}:小弟弟開(kāi)始玩游戲");
            thread.Start();

            // 化妝 5 s
            Console.WriteLine("不管他,大姐姐化妝先"); Thread.Sleep(TimeSpan.FromSeconds(5));

            Console.WriteLine($"{DateTime.Now}:化完妝,等小弟弟打完游戲");
            thread.Join();

            Console.WriteLine("打完游戲了嘛?" + (!thread.IsAlive ? "true" : "false"));
            Console.WriteLine($"{DateTime.Now}:走,逛街去");
            Console.ReadKey();
        }

        public static void OneTest()
        {
            Console.WriteLine(Thread.CurrentThread.Name + "開(kāi)始打游戲");
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine($"{DateTime.Now}:第幾局:" + i);
                Thread.Sleep(TimeSpan.FromSeconds(2));      // 休眠 2 秒
            }
            Console.WriteLine(Thread.CurrentThread.Name + "打完了");
        }

Join() 也可以實(shí)現(xiàn)簡(jiǎn)單的線程同步,即一個(gè)線程等待另一個(gè)線程完成。

2.3 線程狀態(tài)

ThreadState?是一個(gè)枚舉,記錄了線程的狀態(tài),我們可以從中判斷線程的生命周期和健康情況。

其枚舉如下:

枚舉 說(shuō)明
Initialized 0 此狀態(tài)指示線程已初始化但尚未啟動(dòng)。
Ready 1 此狀態(tài)指示線程因無(wú)可用的處理器而等待使用處理器。 線程準(zhǔn)備在下一個(gè)可用的處理器上運(yùn)行。
Running 2 此狀態(tài)指示線程當(dāng)前正在使用處理器。
Standby 3 此狀態(tài)指示線程將要使用處理器。 一次只能有一個(gè)線程處于此狀態(tài)。
Terminated 4 此狀態(tài)指示線程已完成執(zhí)行并已退出。
Transition 6 此狀態(tài)指示線程在可以執(zhí)行前等待處理器之外的資源。 例如,它可能正在等待其執(zhí)行堆棧從磁盤中分頁(yè)。
Unknown 7 線程的狀態(tài)未知。
Wait 5 此狀態(tài)指示線程尚未準(zhǔn)備好使用處理器,因?yàn)樗诘却鈬僮魍瓿苫虻却Y源釋放。 當(dāng)線程就緒后,將對(duì)其進(jìn)行重排。

但是里面有很多枚舉類型是沒(méi)有用處的,我們可以使用一個(gè)這樣的方法來(lái)獲取更加有用的信息:

        public static ThreadState GetThreadState(ThreadState ts)
        {
            return ts & (ThreadState.Unstarted |
                ThreadState.WaitSleepJoin |
                ThreadState.Stopped);
        }

根據(jù)?2.2?中的示例,我們修改一下 Main 中的方法:

        static void Main(string[] args)
        {
            Thread thread = new Thread(OneTest);
            thread.Name = "小弟弟";

            Console.WriteLine($"{DateTime.Now}:大家在吃飯,吃完飯后要帶小弟弟逛街");
            Console.WriteLine("吃完飯了");
            Console.WriteLine($"{DateTime.Now}:小弟弟開(kāi)始玩游戲");
            Console.WriteLine("弟弟在干嘛?(線程狀態(tài)):" + Enum.GetName(typeof(ThreadState), GetThreadState(thread.ThreadState)));
            thread.Start();
            Console.WriteLine("弟弟在干嘛?(線程狀態(tài)):" + Enum.GetName(typeof(ThreadState), GetThreadState(thread.ThreadState)));
            // 化妝 5 s
            Console.WriteLine("不管他,大姐姐化妝先"); Thread.Sleep(TimeSpan.FromSeconds(5));
            Console.WriteLine("弟弟在干嘛?(線程狀態(tài)):" + Enum.GetName(typeof(ThreadState), GetThreadState(thread.ThreadState)));
            Console.WriteLine($"{DateTime.Now}:化完妝,等小弟弟打完游戲");
            thread.Join();
            Console.WriteLine("弟弟在干嘛?(線程狀態(tài)):" + Enum.GetName(typeof(ThreadState), GetThreadState(thread.ThreadState)));
            Console.WriteLine("打完游戲了嘛?" + (!thread.IsAlive ? "true" : "false"));
            Console.WriteLine($"{DateTime.Now}:走,逛街去");
            Console.ReadKey();
        }

代碼看著比較亂,請(qǐng)復(fù)制到項(xiàng)目中運(yùn)行一下。

輸出示例:

2020/4/11 11:01:48:大家在吃飯,吃完飯后要帶小弟弟逛街
吃完飯了
2020/4/11 11:01:48:小弟弟開(kāi)始玩游戲
弟弟在干嘛?(線程狀態(tài)):Unstarted
弟弟在干嘛?(線程狀態(tài)):Running
不管他,大姐姐化妝先
小弟弟開(kāi)始打游戲
2020/4/11 11:01:48:第幾局:0
2020/4/11 11:01:50:第幾局:1
2020/4/11 11:01:52:第幾局:2
弟弟在干嘛?(線程狀態(tài)):WaitSleepJoin
2020/4/11 11:01:53:化完妝,等小弟弟打完游戲
2020/4/11 11:01:54:第幾局:3
2020/4/11 11:01:56:第幾局:4
2020/4/11 11:01:58:第幾局:5
2020/4/11 11:02:00:第幾局:6
2020/4/11 11:02:02:第幾局:7
2020/4/11 11:02:04:第幾局:8
2020/4/11 11:02:06:第幾局:9
小弟弟打完了
弟弟在干嘛?(線程狀態(tài)):Stopped
打完游戲了嘛?true
2020/4/11 11:02:08:走,逛街去

可以看到?UnstartedWaitSleepJoin、Running、Stopped四種狀態(tài),即未開(kāi)始(就緒)、阻塞、運(yùn)行中、死亡。

2.4 終止

.Abort()?方法不能在 .NET Core 上使用,不然會(huì)出現(xiàn)?System.PlatformNotSupportedException:“Thread abort is not supported on this platform.”?。

后面關(guān)于異步的文章會(huì)講解如何實(shí)現(xiàn)終止。

由于 .NET Core 不支持,就不理會(huì)這兩個(gè)方法了。這里只列出 API,不做示例。

方法 說(shuō)明
Abort() 在調(diào)用此方法的線程上引發(fā) ThreadAbortException,以開(kāi)始終止此線程的過(guò)程。 調(diào)用此方法通常會(huì)終止線程。
Abort(Object) 引發(fā)在其上調(diào)用的線程中的 ThreadAbortException以開(kāi)始處理終止線程,同時(shí)提供有關(guān)線程終止的異常信息。 調(diào)用此方法通常會(huì)終止線程。

Abort()?方法給線程注入?ThreadAbortException?異常,導(dǎo)致程序被終止。但是不一定可以終止線程。

2.5 線程的不確定性

線程的不確定性是指幾個(gè)并行運(yùn)行的線程,不確定在下一刻 CPU 時(shí)間片會(huì)分配給誰(shuí)(當(dāng)然,分配有優(yōu)先級(jí))。

對(duì)我們來(lái)說(shuō),多線程是同時(shí)運(yùn)行的,但一般 CPU 沒(méi)有那么多核,不可能在同一時(shí)刻執(zhí)行所有的線程。CPU 會(huì)決定某個(gè)時(shí)刻將時(shí)間片分配給多個(gè)線程中的一個(gè)線程,這就出現(xiàn)了 CPU 的時(shí)間片分配調(diào)度。

執(zhí)行下面的代碼示例,你可以看到,兩個(gè)線程打印的順序是不確定的,而且每次運(yùn)行結(jié)果都不同。

CPU 有一套公式確定下一次時(shí)間片分配給誰(shuí),但是比較復(fù)雜,需要學(xué)習(xí)計(jì)算機(jī)組成原理和操作系統(tǒng)。

留著下次寫(xiě)文章再講。

        static void Main(string[] args)
        {
            Thread thread1 = new Thread(Test1);
            Thread thread2 = new Thread(Test2);

            thread1.Start();
            thread2.Start();

            Console.ReadKey();
        }

        public static void Test1()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("Test1:" + i);
            }
        }
        public static void Test2()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("Test2:" + i);
            }
        }

2.6 線程優(yōu)先級(jí)、前臺(tái)線程和后臺(tái)線程

Thread.Priority?屬性用于設(shè)置線程的優(yōu)先級(jí),Priority?是一個(gè) ThreadPriority 枚舉,其枚舉類型如下

枚舉 說(shuō)明
AboveNormal 3 可以將 安排在具有?Highest?優(yōu)先級(jí)的線程之后,在具有?Normal?優(yōu)先級(jí)的線程之前。
BelowNormal 1 可以將 Thread 安排在具有?Normal?優(yōu)先級(jí)的線程之后,在具有?Lowest?優(yōu)先級(jí)的線程之前。
Highest 4 可以將 Thread 安排在具有任何其他優(yōu)先級(jí)的線程之前。
Lowest 0 可以將 Thread 安排在具有任何其他優(yōu)先級(jí)的線程之后。
Normal 2 可以將 Thread 安排在具有?AboveNormal?優(yōu)先級(jí)的線程之后,在具有?BelowNormal?優(yōu)先級(jí)的線程之前。 默認(rèn)情況下,線程具有?Normal?優(yōu)先級(jí)。

優(yōu)先級(jí)排序:Highest?>?AboveNormal?>?Normal?>?BelowNormal?>?Lowest。

Thread.IsBackgroundThread?可以設(shè)置線程是否為后臺(tái)線程。

前臺(tái)線程的優(yōu)先級(jí)大于后臺(tái)線程,并且程序需要等待所有前臺(tái)線程執(zhí)行完畢后才能關(guān)閉;而當(dāng)程序關(guān)閉是,無(wú)論后臺(tái)線程是否在執(zhí)行,都會(huì)強(qiáng)制退出。

2.7 自旋和休眠

當(dāng)線程處于進(jìn)入休眠狀態(tài)或解除休眠狀態(tài)時(shí),會(huì)發(fā)生上下文切換,這就帶來(lái)了昂貴的消耗。

而線程不斷運(yùn)行,就會(huì)消耗 CPU 時(shí)間,占用 CPU 資源。

對(duì)于過(guò)短的等待,應(yīng)該使用自旋(spin)方法,避免發(fā)生上下文切換;過(guò)長(zhǎng)的等待應(yīng)該使線程休眠,避免占用大量 CPU 時(shí)間。

我們可以使用最為熟知的?Sleep()?方法休眠線程。有很多同步線程的類型,也使用了休眠手段等待線程(已經(jīng)寫(xiě)好草稿啦)。

自旋的意思是,沒(méi)事找事做。

例如:

        public static void Test(int n)
        {
            int num = 0;
            for (int i=0;i<n;i++)
            {
                num += 1;
            }
        }

通過(guò)做一些簡(jiǎn)單的運(yùn)算,來(lái)消耗時(shí)間,從而達(dá)到等待的目的。

C# 中有關(guān)于自旋的自旋鎖和?Thread.SpinWait();?方法,在后面的線程同步分類中會(huì)說(shuō)到自旋鎖。

Thread.SpinWait()?在極少數(shù)情況下,避免線程使用上下文切換很有用。其定義如下

public static void SpinWait(int iterations);

SpinWait 實(shí)質(zhì)上是(處理器)使用了非常緊密的循環(huán),并使用?iterations?參數(shù)指定的循環(huán)計(jì)數(shù)。 SpinWait 等待時(shí)間取決于處理器的速度。

SpinWait 無(wú)法使你準(zhǔn)確控制等待時(shí)間,主要是使用一些鎖時(shí)用到,例如 Monitor.Enter。

原文鏈接:https://www.cnblogs.com/whuanle/p/12708824.html

欄目分類
最近更新