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

學無先后,達者為師

網站首頁 編程語言 正文

C#多線程系列之線程的創建和生命周期_C#教程

作者:癡者工良 ? 更新時間: 2022-04-19 編程語言

1,獲取當前線程信息

Thread.CurrentThread?是一個 靜態的 Thread 類,Thread 的CurrentThread?屬性,可以獲取到當前運行線程的一些信息,其定義如下:

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

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

這里有一個簡單的示例:

        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("線程標識:" + thisTHread.Name);
            Console.WriteLine("當前地域:" + thisTHread.CurrentCulture.Name);  // 當前地域
            Console.WriteLine("線程執行狀態:" + thisTHread.IsAlive);
            Console.WriteLine("是否為后臺線程:" + thisTHread.IsBackground);
            Console.WriteLine("是否為線程池線程"+thisTHread.IsThreadPoolThread);
        }

輸出

線程標識:Test
當前地域:zh-CN
線程執行狀態:True
是否為后臺線程:False
是否為線程池線程False

2,管理線程狀態

一般認為,線程有五種狀態:

新建(new 對象) 、就緒(等待CPU調度)、運行(CPU正在運行)、阻塞(等待阻塞、同步阻塞等)、死亡(對象釋放)。

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

2.1 啟動與參數傳遞

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

Thread thread = new Thread(); 

Thread 的構造函數有四個:

public Thread(ParameterizedThreadStart start);

public Thread(ThreadStart start);

public Thread(ParameterizedThreadStart start, int maxStackSize);

public Thread(ThreadStart start, int maxStackSize);

我們以啟動新的線程時傳遞參數來舉例,使用這四個構造函數呢?

2.1.1 ParameterizedThreadStart

ParameterizedThreadStart 是一個委托,構造函數傳遞的參數為需要執行的方法,然后在?Start?方法中傳遞參數。

需要注意的是,傳遞的參數類型為 object,而且只能傳遞一個。

代碼示例如下:

        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("新的線程已經啟動");
            Console.WriteLine(str);
        }

2.1.2 使用靜態變量或類成員變量

此種方法不需要作為參數傳遞,各個線程共享堆棧。

優點是不需要裝箱拆箱,多線程可以共享空間;缺點是變量是大家都可以訪問,此種方式在多線程競價時,可能會導致多種問題(可以加鎖解決)。

下面使用兩個變量實現數據傳遞:

    class Program
    {
        private string A = "成員變量";
        public static string B = "靜態變量";

        static void Main(string[] args)
        {
            // 創建一個類
            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("新的線程已經啟動");
            Console.WriteLine(A);       // 本身對象的其它成員
        }
        public static void OneTest2()
        {
            Console.WriteLine("新的線程已經啟動");
            Console.WriteLine(B);       // 全局靜態變量
        }
    }

2.1.3 委托與Lambda

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

public delegate void ThreadStart();

使用委托的話,可以這樣寫

        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("新的線程已經啟動");
        }

有那么一點點麻煩,不過我們可以使用 Lambda 快速實現。

使用 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("新的線程已經啟動");
        }

提示:如果需要處理的算法比較簡單的話,可以直接寫進委托中,不需要另外寫方法啦。

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

2.2 暫停與阻塞

Thread.Sleep()?方法可以將當前線程掛起一段時間,Thread.Join()?方法可以阻塞當前線程一直等待另一個線程運行至結束。

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

? ? 阻塞的定義:當線程由于特點原因暫停執行,那么它就是阻塞的。?
? ? 如果線程處于阻塞狀態,線程就會交出他的 CPU 時間片,并且不會消耗 CPU 時間,直至阻塞結束。?
? ? 阻塞會發生上下文切換。

代碼示例如下:

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

            Console.WriteLine($"{DateTime.Now}:大家在吃飯,吃完飯后要帶小弟弟逛街");
            Console.WriteLine("吃完飯了");
            Console.WriteLine($"{DateTime.Now}:小弟弟開始玩游戲");
            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 + "開始打游戲");
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine($"{DateTime.Now}:第幾局:" + i);
                Thread.Sleep(TimeSpan.FromSeconds(2));      // 休眠 2 秒
            }
            Console.WriteLine(Thread.CurrentThread.Name + "打完了");
        }

Join() 也可以實現簡單的線程同步,即一個線程等待另一個線程完成。

2.3 線程狀態

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

其枚舉如下:

枚舉 說明
Initialized 0 此狀態指示線程已初始化但尚未啟動。
Ready 1 此狀態指示線程因無可用的處理器而等待使用處理器。 線程準備在下一個可用的處理器上運行。
Running 2 此狀態指示線程當前正在使用處理器。
Standby 3 此狀態指示線程將要使用處理器。 一次只能有一個線程處于此狀態。
Terminated 4 此狀態指示線程已完成執行并已退出。
Transition 6 此狀態指示線程在可以執行前等待處理器之外的資源。 例如,它可能正在等待其執行堆棧從磁盤中分頁。
Unknown 7 線程的狀態未知。
Wait 5 此狀態指示線程尚未準備好使用處理器,因為它正在等待外圍操作完成或等待資源釋放。 當線程就緒后,將對其進行重排。

但是里面有很多枚舉類型是沒有用處的,我們可以使用一個這樣的方法來獲取更加有用的信息:

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

根據?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}:小弟弟開始玩游戲");
            Console.WriteLine("弟弟在干嘛?(線程狀態):" + Enum.GetName(typeof(ThreadState), GetThreadState(thread.ThreadState)));
            thread.Start();
            Console.WriteLine("弟弟在干嘛?(線程狀態):" + Enum.GetName(typeof(ThreadState), GetThreadState(thread.ThreadState)));
            // 化妝 5 s
            Console.WriteLine("不管他,大姐姐化妝先"); Thread.Sleep(TimeSpan.FromSeconds(5));
            Console.WriteLine("弟弟在干嘛?(線程狀態):" + Enum.GetName(typeof(ThreadState), GetThreadState(thread.ThreadState)));
            Console.WriteLine($"{DateTime.Now}:化完妝,等小弟弟打完游戲");
            thread.Join();
            Console.WriteLine("弟弟在干嘛?(線程狀態):" + Enum.GetName(typeof(ThreadState), GetThreadState(thread.ThreadState)));
            Console.WriteLine("打完游戲了嘛?" + (!thread.IsAlive ? "true" : "false"));
            Console.WriteLine($"{DateTime.Now}:走,逛街去");
            Console.ReadKey();
        }

代碼看著比較亂,請復制到項目中運行一下。

輸出示例:

2020/4/11 11:01:48:大家在吃飯,吃完飯后要帶小弟弟逛街
吃完飯了
2020/4/11 11:01:48:小弟弟開始玩游戲
弟弟在干嘛?(線程狀態):Unstarted
弟弟在干嘛?(線程狀態):Running
不管他,大姐姐化妝先
小弟弟開始打游戲
2020/4/11 11:01:48:第幾局:0
2020/4/11 11:01:50:第幾局:1
2020/4/11 11:01:52:第幾局:2
弟弟在干嘛?(線程狀態):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
小弟弟打完了
弟弟在干嘛?(線程狀態):Stopped
打完游戲了嘛?true
2020/4/11 11:02:08:走,逛街去

可以看到?Unstarted、WaitSleepJoin、Running、Stopped四種狀態,即未開始(就緒)、阻塞、運行中、死亡。

2.4 終止

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

后面關于異步的文章會講解如何實現終止。

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

方法 說明
Abort() 在調用此方法的線程上引發 ThreadAbortException,以開始終止此線程的過程。 調用此方法通常會終止線程。
Abort(Object) 引發在其上調用的線程中的 ThreadAbortException以開始處理終止線程,同時提供有關線程終止的異常信息。 調用此方法通常會終止線程。

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

2.5 線程的不確定性

線程的不確定性是指幾個并行運行的線程,不確定在下一刻 CPU 時間片會分配給誰(當然,分配有優先級)。

對我們來說,多線程是同時運行的,但一般 CPU 沒有那么多核,不可能在同一時刻執行所有的線程。CPU 會決定某個時刻將時間片分配給多個線程中的一個線程,這就出現了 CPU 的時間片分配調度。

執行下面的代碼示例,你可以看到,兩個線程打印的順序是不確定的,而且每次運行結果都不同。

CPU 有一套公式確定下一次時間片分配給誰,但是比較復雜,需要學習計算機組成原理和操作系統。

留著下次寫文章再講。

        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 線程優先級、前臺線程和后臺線程

Thread.Priority?屬性用于設置線程的優先級,Priority?是一個 ThreadPriority 枚舉,其枚舉類型如下

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

優先級排序:Highest?>?AboveNormal?>?Normal?>?BelowNormal?>?Lowest。

Thread.IsBackgroundThread?可以設置線程是否為后臺線程。

前臺線程的優先級大于后臺線程,并且程序需要等待所有前臺線程執行完畢后才能關閉;而當程序關閉是,無論后臺線程是否在執行,都會強制退出。

2.7 自旋和休眠

當線程處于進入休眠狀態或解除休眠狀態時,會發生上下文切換,這就帶來了昂貴的消耗。

而線程不斷運行,就會消耗 CPU 時間,占用 CPU 資源。

對于過短的等待,應該使用自旋(spin)方法,避免發生上下文切換;過長的等待應該使線程休眠,避免占用大量 CPU 時間。

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

自旋的意思是,沒事找事做。

例如:

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

通過做一些簡單的運算,來消耗時間,從而達到等待的目的。

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

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

public static void SpinWait(int iterations);

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

SpinWait 無法使你準確控制等待時間,主要是使用一些鎖時用到,例如 Monitor.Enter。

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

欄目分類
最近更新