1. 程式人生 > 其它 >C#執行緒入門

C#執行緒入門

前言,多執行緒在日常編碼中經常會用,本文就主執行緒和子執行緒之間的關係做個大概的總結,若有差錯,歡迎斧正。

首先開啟工作管理員,檢視當前執行的程序:選單欄右鍵選擇“Task Manager(任務管理)”或 “Ctrl”+“Alt”+“Delete”點選“Task Manager(任務管理)”

從工作管理員裡面可以看到當前所有正在執行的程序。

Tab點選“效能”:

可以看到當前正在執行的所有程序數“Processes”和執行緒數“Threads”

一般來說一個應用程式就對應一個程序,一個程序可有一個或多個執行緒即(>=1個執行緒),有且只有一個主執行緒。

程序和執行緒:

程序:是Windows系統中的一個基本概念,它包含著一個執行程式所需要的資源。

一個正在執行的應用程式在作業系統中被視為一個程序,程序可以包括一個或多個執行緒。執行緒是作業系統分配處理器時間的基本單元,在程序中可以有多個執行緒同時執行程式碼。程序之間是相對獨立的,一個程序無法訪問另一個程序的資料(除非利用分散式計算方式),一個程序執行的失敗也不會影響其他程序的執行,Windows系統就是利用程序把工作劃分為多個獨立的區域的。程序可以理解為一個程式的基本邊界。是應用程式的一個執行例程,是應用程式的一次動態執行過程。

執行緒:是程序中的基本執行單元,是作業系統分配CPU時間的基本單位,一個程序可以包含若干個執行緒,在程序入口執行的第一個執行緒被視為這個程序的主執行緒。在.NET應用程式中,都是以Main()方法作為入口的,當呼叫此方法時系統就會自動建立一個主執行緒。執行緒主要是由CPU暫存器、呼叫棧和執行緒本地儲存器(Thread Local Storage,TLS)組成的。CPU暫存器主要記錄當前所執行執行緒的狀態,呼叫棧主要用於維護執行緒所呼叫到的記憶體與資料,TLS主要用於存放執行緒的狀態資訊。

前臺執行緒後後臺執行緒:

前臺執行緒:預設情況下建立的執行緒都是前臺執行緒,只有所有的前臺執行緒關閉才能完成程式關閉。只有將執行緒屬性IsBackground設定為true,才是後臺執行緒

後臺執行緒:程式關閉,後臺執行緒不管是否執行完都將關閉

示例如下:

        static void Main(string[] args)
        {
            Console.OutputEncoding = Encoding.UTF8;

            //前臺執行緒
            Thread qtxc = new Thread(() => RunLoop(10
)); qtxc.Name = "qiantai thread"; //後臺執行緒 Thread htxc = new Thread(() => RunLoop(20)); htxc.Name = "houtai thread"; htxc.IsBackground = true; //將執行緒設定為後臺執行緒 //啟動執行緒 qtxc.Start(); htxc.Start(); } public static void RunLoop(int count) { //獲取當前執行緒名稱 string threadName = Thread.CurrentThread.Name; for (int i = 0; i < count; i++) { Console.WriteLine($"{threadName} index:{(i + 1).ToString()}"); //休眠1秒 Thread.Sleep(1000); } Console.WriteLine($"{threadName} complete"); }

程式碼中定義了一個迴圈列印的方法,其中前臺執行緒列印10次,後臺執行緒列印20次,執行結果如下:

從執行結果可以看出,前臺執行緒列印完成後,後臺執行緒未列印完,但是程式自動結束了。

然後調整引數,前臺執行緒列印20次,後臺執行緒列印10次:

    class Program
    {
        static void Main(string[] args)
        {
            //前臺執行緒
            Thread qtxc = new Thread(() => RunLoop(20));
            qtxc.Name = "qiantai thread";


            //後臺執行緒
            Thread htxc = new Thread(() => RunLoop(10));
            htxc.Name = "houtai thread";
            htxc.IsBackground = true; //將執行緒設定為後臺執行緒

            //啟動執行緒
            qtxc.Start();
            htxc.Start();
        }



        public static void RunLoop(int count)
        {
            //獲取當前執行緒名稱
            string threadName = Thread.CurrentThread.Name;
            for (int i = 0; i < count; i++)
            {
                Console.WriteLine($"{threadName}  index:{(i + 1).ToString()}");

                //休眠1秒
                Thread.Sleep(1000);
            }
            Console.WriteLine($"{threadName} complete");
        }
    }

執行結果如下:

從執行結果可以看出,後臺執行緒列印完成後,前臺執行緒未列印完繼續列印,前臺執行緒列印完成後,程式結束。

結論就是:前臺執行緒結束時,不管後臺執行緒是否結束,程式都將結束;後臺執行緒結束時,若有前臺執行緒再執行,程式將繼續執行,等到前臺執行緒執行完後,程式結束。

後臺執行緒一般用於處理不重要的事情,應用程式結束時,後臺執行緒是否執行完成對整個應用程式沒有影響。如果要執行的事情很重要,需要將執行緒設定為前臺執行緒。

主執行緒和子執行緒之間的關係:

  • 預設情況,在新開啟一個子執行緒的時候,他是前臺執行緒,只有將執行緒的IsBackground屬性設為true;他才是後臺執行緒
  • 當子執行緒是前臺執行緒,則主執行緒結束並不影響其他執行緒的執行,只有所有前臺執行緒都結束,程式結束
  • 當子執行緒是後臺執行緒,則主執行緒的結束,會導致子執行緒的強迫結束
  • 不管是前臺執行緒還是後臺執行緒,如果執行緒內出現了異常,都會導致程序的終止。
  • 託管執行緒池中的執行緒都是後臺執行緒

C#中執行緒的使用:

引入名稱空間:System.Threading

Thread執行緒常用方法列表:

方法 說明
Start 開始執行緒
Sleep 使執行緒暫停指定的一段時間。如Thread.Sleep(3000),即執行緒暫停三秒後繼續執行
Abort 線上程到達安全點時,使其停止。
Suspend 線上程到達安全點時,使其暫停。
Resume 重新啟動掛起的執行緒
**安全點: **
安全點是指程式碼中公共語言執行時可以安全地執行自動“垃圾回收”的位置。 垃圾回收是指釋放不再使用的變數並回收記憶體的過程。 呼叫執行緒的 Abort 或 Suspend 方法時,公共語言執行時將對程式碼進行分析,確定讓執行緒停止執行的適當位置。 Thread常用屬性:
屬性 說明
IsAlive 如果執行緒處於活動狀態,則返回TRUE
IsBackground 是否是後臺執行緒
Name 獲取或設定執行緒的名稱。 通常用於在除錯時發現各個執行緒。
Priority 獲取或設定作業系統用於確定執行緒排程優先順序的值
ApartmentState 獲取或設定用於特定執行緒的執行緒模型。 執行緒模型線上程呼叫非託管程式碼時很重要。
ThreadState 執行緒狀態
IsThreadPoolThread 獲取一個值,該值指示執行緒是否屬於託管執行緒池。
ManagedThreadId 獲取當前託管執行緒的唯一識別符號。
每個執行緒都有一個優先順序屬性,用於確定其執行所佔用的處理器時間片大小。 作業系統為高優先順序執行緒分配較長的時間段,併為低優先順序執行緒分配較短的時間段。 新建立的執行緒具有值 Normal,但可以將 Priority 屬性更改為 ThreadPriority 列舉中的任何值。 以上就是Thread類常用屬性即方法,其他用法可以檢視Thread底層。 執行緒同步: 所謂同步:實質就是在某一時刻,只有一個執行緒可以訪問變數;如果不能確保對變數的訪問是同步的,就會產生錯誤。 C#為同步訪問變數提供了一個非常簡單的方式,即使用“Lock”關鍵詞,它可以把一段程式碼定義為互斥段,互斥段在某一個時刻內,只允許一個執行緒進入執行,而其他執行緒必須等待。Lock使用語法如下: Lock(expression)
{
statement_block
} 來個例項,就拿藥店賣藥為例子:假設藥店的“999感冒靈”總量是固定的,然後分發到各個藥店出售,一個藥店賣出一包,總量就會減少一包, 再不考慮執行緒同步的情況下, 示例如下:
    class Program
    {
        static void Main(string[] args)
        {
            //開啟兩個執行緒來代表兩個分店
            Shop shop = new Shop();

            //執行緒1,分店1
            Thread thread1 = new Thread(shop.Sell);
            thread1.Name = "shop1";

            //執行緒2,分店2
            Thread thread2 = new Thread(shop.Sell);
            thread2.Name = "shop2";

            //開啟執行緒,開始賣藥
            thread1.Start();
            thread2.Start();

            Console.ReadKey();
        }


    }

    public class Shop
    {
        /// <summary>
        /// 剩餘“999感冒靈”的數量
        /// </summary>
        public int num = 10;

        public void Sell()
        {
            do
            {
                string threadName = Thread.CurrentThread.Name;
                if (num > 0)
                {
                    Thread.Sleep(1000);
                    num -= 1;
                    Console.WriteLine($"{threadName} sell a“999”,stock{num} pack");
                }
                else
                {
                    Console.WriteLine("stock nothing!");
                }
            } while (num > 0);//判斷是否有庫存,如果有就可以持續出售
        }
    }

執行結果:

從執行結果可以看出,線上程未同步的情況下,兩家藥店都在銷售庫存的藥,但是店鋪1銷售的時候不知道庫存沒有了,於是出現了庫存 “-1”的情況,顯然是有問題的;

然後調整方案,加入執行緒同步,示例如下:

    class Program
    {
        static void Main(string[] args)
        {
            //開啟兩個執行緒來代表兩個分店
            Shop shop = new Shop();

            //執行緒1,分店1
            Thread thread1 = new Thread(shop.Sell);
            thread1.Name = "shop1";

            //執行緒2,分店2
            Thread thread2 = new Thread(shop.Sell);
            thread2.Name = "shop2";

            //開啟執行緒,開始賣藥
            thread1.Start();
            thread2.Start();

            Console.ReadKey();
        }


    }

    public class Shop
    {
        /// <summary>
        /// 剩餘“999感冒靈”的數量
        /// </summary>
        public int num = 10;

        public void Sell()
        {
            do
            {
                string threadName = Thread.CurrentThread.Name;
                lock (this) //使用lock關鍵字解決執行緒同步的問題
                {
                    if (num > 0)
                    {
                        Thread.Sleep(1000);
                        num -= 1;
                        Console.WriteLine($"{threadName} sell a“999”,stock{num} pack");
                    }
                    else
                    {
                        Console.WriteLine("stock nothing!");
                    }
                }
            } while (num > 0);//判斷是否有庫存,如果有就可以持續出售
        }
    }

執行結果如下:

從執行結果可以看出,線上程同步的情況下,每個藥店都從庫存裡面拿藥,藥店1出售的時候,藥店2就等著,當藥店1銷售完最後一包藥,就沒有另外的銷售記錄了,無論是藥店1還是藥店2來拿藥,庫存都沒有了,這就是執行緒同步的重要性!

結論:expression代表你希望跟蹤的物件:
如果你想保護一個類的例項,一般地,你可以使用this;
如果你想保護一個靜態變數(如互斥程式碼段在一個靜態方法內部),一般使用類名就可以了
而statement_block就算互斥段的程式碼,這段程式碼在一個時刻內只可能被一個執行緒執行。

以上就是執行緒的入門級教程~

不積跬步,無以至千里;不積小流,無以成江海。ヾ(◍°∇°◍)ノ゙