程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 說說C# 多線程那些事-線程基礎,

說說C# 多線程那些事-線程基礎,

編輯:C#入門知識

說說C# 多線程那些事-線程基礎,


我第一次接觸“線程”的概念時,覺得它深奧難懂,看了好多本書,花了很長時間才領悟到它的真谛。現在我就以一個初學者的心態,把我所理解的“多線程”描述給大家。這一次是系列文章,比較完整的展示與線程相關的基本概念。希望對初學者有所幫助。

如果你是高手,請你別繼續看,會浪費你寶貴的時間。

一、基本概念

什麼是進程?

當一個程序開始運行時,它就是一個進程,進程包括運行中的程序和程序所使用到的內存和系統資源。 而一個進程又是由多個線程所組成的。

什麼是線程?

線程是程序中的一個執行流,每個線程都有自己的專有寄存器(棧指針、程序計數器等),但代碼區是共享的,即不同的線程可以執行同樣的函數。

什麼是多線程?

多線程是指程序中包含多個執行流,即在一個程序中可以同時運行多個不同的線程來執行不同的任務,也就是說允許單個程序創建多個並行執行的線程來完成各自的任務。

前台線程後台線程?

應用程序的主線程和通過構造一個Thread對象來顯式創建的任何線程都默認是前台線程。相反線程池線程默認為後台線程。另外由進入托管執行環境的本機代碼創建的任何線程都被標記為後台線程。

在線程的生命周期中,任何時候都可以從前台變為後台,或者從後台變為前台。

前台線程能阻止應用程序的終結。一直到所有的前台線程終止後,CLR才能關閉應用程序(即卸載承載的應用程序域)。

後台線程(有時也叫守護線程)被CLR認為是程序執行中可做出犧牲的途徑,即在任何時候(即使這個線程此時正在執行某項工作)都可能被忽略。因此,如果所有的前台線程終止,當應用程序域卸載時,所以的後台線程也會被自動終止。

 

線程是輕量級進程。一個使用線程的常見實例是現代操作系統中並行編程的實現。使用線程節省了 CPU 周期的浪費,同時提高了應用程序的效率。

二、線程的生命周期

線程生命周期開始於 System.Threading.Thread 類的對象被創建時,結束於線程被終止或完成執行時。

線程生命周期中的各種狀態:

未啟動狀態:當線程實例被創建但 Start 方法未被調用時的狀況(將該線程標記為可以運行的狀態,但具體執行時間由cpu決定。)。

就緒狀態:當線程准備好運行並等待 CPU 周期時的狀況。

不可運行狀態:下面的幾種情況下線程是不可運行的:(已經調用 Sleep 方法,已經調用 Wait 方法,通過 I/O 操作阻塞)

死亡狀態:當線程已完成執行或已中止時的狀況。

三、線程

1、主線程

進程中第一個被執行的線程稱為主線程

using System;
using System.Threading;

namespace Threading
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread th = Thread.CurrentThread;
            th.Name = "MainThread";
            Console.WriteLine("This is {0}", th.Name);
            Console.ReadKey();
        }
    }
}

輸出:This is MainThread

2、線程的創建

using System;
using System.Threading;

namespace Threading
{
    class Program
    {
        public static void Thread1()
        {
            Console.WriteLine("Thread1 starts");
        }
        public static void Thread2(object data)
        {
            Console.WriteLine("Thread2 starts,para:{0}", data.ToString());
        }
        static void Main(string[] args)
        {
            var t1 = new Thread(Thread1);
            t1.Start();

            var t2 = new Thread(Thread2);
            t2.Start("thread2");
            Console.ReadKey();
        }
    }
}

輸入:

Thread1 starts

Thread2 starts,para:thread2

3、線程的管理

sleep()掛起和Abort() 銷毀線程

通過拋出 threadabortexception 在運行時中止線程。這個異常不能被捕獲,如果有 finally 塊,控制會被送至 finally 塊

using System; using System.Threading; namespace Threading { class Program { public static void Thread1() { Console.WriteLine("Thread1 starts"); Console.WriteLine("Thread1 Paused for 5 seconds"); Thread.Sleep(5000); Console.WriteLine("Thread1 resumes"); } static void Main(string[] args) { var t1 = new Thread(Thread1); t1.Start(); Console.ReadKey(); } } } 線程掛起代碼 using System; using System.Threading; namespace Threading { class Program { public static void Thread1() { try { Console.WriteLine("Thread1 starts"); for (int i = 0; i <= 10; i++) { Thread.Sleep(500); Console.WriteLine(i); } Console.WriteLine("Thread1 Completed"); } catch (ThreadAbortException ex) { Console.WriteLine("Thread1 Abort Exception"); } finally { Console.WriteLine("Couldn't catch the Thread1 Exception"); } } static void Main(string[] args) { //開啟子線程 var t1 = new Thread(Thread1); t1.Start(); //主線程掛起2s Thread.Sleep(2000); //終止t1子線程 t1.Abort(); Console.ReadKey(); } } } 線程銷毀代碼

銷毀代碼執行結果:

四、線程池

在多線程程序中,線程把大部分的時間花費在等待狀態,等待某個事件發生,然後才能給予響應我們一般用ThreadPool(線程池)來解決;線程平時都處於休眠狀態,只是周期性地被喚醒我們使用使用Timer(定時器)來解決。

由於線程的創建和銷毀需要耗費一定的開銷,過多的使用線程會造成內存資源的浪費,出於對性能的考慮,於是引入了線程池的概念。線程池維護一個請求隊列,線程池的代碼從隊列提取任務,然後委派給線程池的一個線程執行,線程執行完不會被立即銷毀,這樣既可以在後台執行任務,又可以減少線程創建和銷毀所帶來的開銷。線程池線程默認為後台線程。

線程池自動管理線程線程的創建和銷毀。

代碼展示:

using System;
using System.Threading;

namespace Threading
{
    class Program
    {
        public static void Thread1(object data)
        {
            Console.WriteLine("Thread1 => {0}",data.ToString());

        }
        static void Main(string[] args)
        {
            //控制線程數大小
            //第一個參數是:線程池中輔助線程的最大數目
            //第二個參數是:線程池中異步 I/O 線程的最大數目
            ThreadPool.SetMaxThreads(3, 3);
            for (int i = 0; i < 10; i++)
            {
                //ThreadPool是靜態類無需實例化,
                //ThreadPool.QueueUserWorkItem(new WaitCallback(Thread1), i);
                ThreadPool.QueueUserWorkItem(Thread1, i);
            }
            Console.WriteLine("Thread1 sleep");
            Thread.Sleep(100000);

            Console.WriteLine("Thread1 end");
            Console.ReadKey();
        }
    }
}

運行結果:

但是為什麼最開始輸出Thread1 sleep?有時候也會在中間隨機輸出呢?

其實,線程池的啟動和終止不是我們程序所能控制的,線程池中的線程執行完之後是沒有返回值的,我們可以用ManualResetEvent通知一個或多個正在等待的線程已發生事件

修改後的代碼:

using System;
using System.Threading;

namespace Threading
{
    class Program
    {
        //新建ManualResetEvent對象並且初始化為無信號狀態
        private static ManualResetEvent mre = new ManualResetEvent(false);
        public static void Thread1(object data)
        {
            Console.WriteLine("Thread1 => {0}",data.ToString());
            if (Convert.ToInt32(data) == 9)
            {
                mre.Set();
            }
        }
        static void Main(string[] args)
        {
            //控制線程數大小
            //第一個參數是:線程池中輔助線程的最大數目
            //第二個參數是:線程池中異步 I/O 線程的最大數目
            ThreadPool.SetMaxThreads(3, 3);
            for (int i = 0; i < 10; i++)
            {
                //ThreadPool是靜態類無需實例化,
                //ThreadPool.QueueUserWorkItem(new WaitCallback(Thread1), i);
                ThreadPool.QueueUserWorkItem(Thread1, i);
            }

            //阻止當前線程,直到當前 WaitHandle 收到信號為止。 
            mre.WaitOne(Timeout.Infinite, true);

            Console.WriteLine("Thread1 sleep");
            Thread.Sleep(100000);

            Console.WriteLine("Thread1 end");
            Console.ReadKey();
        }
    }
}

輸入結果:

 ok,搞定。

 參考資料:

ThreadPool:https://msdn.microsoft.com/zh-cn/library/system.threading.threadpool.aspx#Y0

ManualResetEvent:https://msdn.microsoft.com/zh-cn/library/system.threading.manualresetevent.aspx

五、總結

多線程的好處:

可以提高CPU的利用率。在多線程程序中,一個線程必須等待的時候,CPU可以運行其它的線程而不是等待,這樣就大大提高了程序的效率。

 

多線程的不利方面:

線程也是程序,所以線程需要占用內存,線程越多占用內存也越多;

多線程需要協調和管理,所以需要CPU時間跟蹤線程;

線程之間對共享資源的訪問會相互影響,必須解決競用共享資源的問題;

線程太多會導致控制太復雜,最終可能造成很多Bug;

 

六、加關注

如果本文對你有幫助,請點擊右下角【好文要頂】和【關注我

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved