多線程詳細介紹

什麼是進程線程:咱們來看一下本身的任務管理器

  

這裏的每一項都是一個進程,咱們的發佈的每個應用程序都須要一個進程去運行,在一個進程內能夠有多個線程去計算執行程序。咱們看下面的圖片:html

  

咱們能夠看一下進程和線程的數量,很明顯能夠看出,線程和進程的關係。咱們的每個操做都須要一個線程來執行,鼠標的點擊就須要線程去響應咱們的操做。api

如今咱們不難理解,咱們一個應用程序就表明一個進程,想讓咱們的程序高效的運行咱們就能夠啓用多個線程去執行了,固然採用多線程的話有好處也是有代價的,好處合理的利用計資源了,可是線程過多了,你的CPU利用率就加大了,也有可能致使電腦的卡死。安全

在咱們的程序中線程的表明就是:Thread本篇文章咱們就說一下線程。咱們先了解一下同步異步。多線程

同步:指的是在同一線程下執行,而且會等待結果執行完畢。框架

異步:再也不同一個線程下執行,而且執行得順序不可控,不會等待執行結果完畢。異步

咱們先用委託來演示一下多線程,若是不怎麼了解委託得能夠看一下上一篇文章:性能

    DelegateMethod Method = () =>
            {
           Console.WriteLine($"個人線程ID是:{Thread.CurrentThread.ManagedThreadId}");
            };
            Method.BeginInvoke(null, null);
            Method.Invoke();

原本想用上面的代碼去先簡單的演示一下,誰知道NetCore 的程序集提供了,可是平臺目前還不支持。 咱們能夠私下使用NET 試一下。學習

使用Thread 演示spa

 

            Console.WriteLine($"*********************************我是同步方法 *******************************");

            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine($"個人線程ID是:{Thread.CurrentThread.ManagedThreadId}");
            }
            Console.WriteLine($"*********************************同步方法結束 *******************************");

            Console.WriteLine($"*********************************我是異步方法 *******************************");
            for (int i = 0; i < 10; i++)
            {
                 Thread thread = new Thread(() => { Console.WriteLine($"個人線程ID是:{Thread.CurrentThread.ManagedThreadId}"); });
                 thread.Start();
            }
            Console.WriteLine($"*********************************異步方法結束 *******************************");
  
            Console.ReadKey();

執行結果:pwa

   

上面的代碼執行結果中咱們能夠看到:同步方法 是同一個線程來執行的,之上而下有序的執行,可是異步方法啓動了多個線程去執行的,而且線程是無序的。看到這樣的狀況咱們就會知道若是我啓動了不少線程線程用完以後也是有回收的,回收以後一樣會分配的,也就是說同一個操做中線程的ID 可能會屢次出現的。

            Console.WriteLine($"*********************************異步啓動線程數量計算  *******************************");
            List<int> ListInt = new List<int>();
            int Conut = 0;
            for (int i = 0; i < 2000; i++)
            {
                Thread thread = new Thread(() =>
                {
                    if (ListInt.Contains(Thread.CurrentThread.ManagedThreadId))
                    {
                        Conut++;
                        Console.WriteLine($"個人重複的線程個人 ID是:{Thread.CurrentThread.ManagedThreadId}  重複線程總數量:{Conut}");
                    }
                
                    ListInt.Add(Thread.CurrentThread.ManagedThreadId);
                });
                thread.Start();
            }
            Console.WriteLine($"*********************************異步啓動線程數量計算結果:{ListInt.Count}  *******************************");

結果: 

  

上面的結果咱們能夠看到:線程回收再利用。其實thread線程的回收就是咱們的GC來作的,這就是C# 的強大之處,自動幫助咱們回收了。須要注意的是這樣使用線程咱們的回收過程是比較慢的,這個回收速度是咱們計算機性能決定的。

在上面的結果中咱們能夠看到咱們總共申請了1996個線程,其中有881個線程是重複的,線程的申請和銷燬是耗費不少性能的,接下來咱們看一下線程池。

線程thread有哪些可操做的屬性

CurrentContext

獲取線程正在其中執行的當前上下文。

CurrentCulture

獲取或設置當前線程的區域性。

CurrentPrinciple

獲取或設置線程的當前負責人(對基於角色的安全性而言)。

CurrentThread

獲取當前正在運行的線程。

CurrentUICulture

獲取或設置資源管理器使用的當前區域性以便在運行時查找區域性特定的資源。

ExecutionContext

獲取一個 ExecutionContext 對象,該對象包含有關當前線程的各類上下文的信息。

IsAlive

獲取一個值,該值指示當前線程的執行狀態。

IsBackground

獲取或設置一個值,該值指示某個線程是否爲後臺線程。

IsThreadPoolThread

獲取一個值,該值指示線程是否屬於託管線程池。

ManagedThreadId

獲取當前託管線程的惟一標識符。

Name

獲取或設置線程的名稱。

Priority

獲取或設置一個值,該值指示線程的調度優先級。

ThreadState

獲取一個值,該值包含當前線程的狀態。

 

 線程池:ThreadPool

       ThreadPool:線程池中的線程都是後臺線程IsBackground屬性都是True.不會影響全部的前臺線程,也就是說不會影響用戶體驗。每一個線程都有默認的堆棧大小和優先級,位於多線程池中。 一旦線程池中的線程完成任務,它將返回到等待線程隊列中,這時咱們就能夠利用這些閒置的線程。經過這種重複使用,應用程序能夠避免產生爲每一個任務建立新線程的開銷。在每個進程中都只會有一個線程池

 

            //public static bool SetMaxThreads(int workerThreads, int completionPortThreads);
            //public static bool SetMinThreads(int workerThreads, int completionPortThreads);
            //workerThreads 工做線程數量  completionPortThreads I/O線程數量
            ThreadPool.SetMaxThreads(12,12);  
            ThreadPool.SetMinThreads(12, 12);

我這裏設置工做線程,I/O線程數量來源於我得計算機核心數量,保持每一個核心最大最小線程都啓動一個。查看計算機處理器核心數量。

  

上面的設置是說我在線程池中給準備了數量爲12 的線程。你能夠申請最多12個線程,在使用完以後我會立馬進行自動的回收,回收以後的線程繼續存放在線程池中等待使用。相比於 Thread線程池ThreadPool對於線程的回收更快,性能更好。

代碼看一下性能:

            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            for (int i = 0; i < 1000; i++)
            {
                Thread thread = new Thread(() =>
                {
                    Console.WriteLine($"*********************************我是多線程Thread方法 *******************************");
                });
                thread.Start();
            }
            Console.WriteLine($"*********************************Thread方法結束  耗費時間 :{stopwatch.ElapsedMilliseconds}  *******************************");

            Console.WriteLine($"*********************************多線程ThreadPool啓動  *******************************");

            Stopwatch stopwatch1 = new Stopwatch();
            stopwatch1.Start();
            for (int i = 0; i < 1000; i++)
            {
                    WaitCallback act = (t) =>
                    {
                        Console.WriteLine($"*********************************我是多線程ThreadPool方法 *******************************");
                    };
                    ThreadPool.QueueUserWorkItem(act);
            }
            Console.WriteLine($"*********************************ThreadPool方法結束  耗費時間 :{stopwatch1.ElapsedMilliseconds}  *******************************");

結果:Thread

  

結果:ThreadPool

  

上面的兩個方法咱們都只是輸出一行字符,可是以1000次的來講看一下性能相差有多少。因此建議你們都使用線程池。

推薦官方文檔:https://docs.microsoft.com/zh-cn/dotnet/standard/threading/?view=netframework-4.7.2

線程池:Task

隨着框架的發展咱們有了Task 他也是基於ThreadPool來從新封裝的。他的出現方便了咱們對多線程的回調等待更好的操做。

推薦一篇博客:https://www.cnblogs.com/lonelyxmas/p/9509298.html

並行計算的多線程 Parallel

Parallel 多線程,這個相似同步的,他是在Task的基礎之上又一次的封裝。假如說咱們啓動多個線程,她像其餘的同樣啓動了不少的子線程去執行的,而是和當前線程同樣並行去執行的。而且當前線程也參與執行。他會卡住線程,等到所有執行完畢後纔會繼續

這個操做上不如Task 靈活,好比Task 能夠等待其中一個線程執行完成後繼續主線程,Parallel 是必須等待所有執行完畢。

Parallel裏面大體分爲三個方法: For,ForEach,Invoke

Invoke:

            Console.WriteLine($"*********************************我是主線程線程 ID:{Thread.CurrentThread.ManagedThreadId} *******************************");
            Action act = () => {

                Console.WriteLine($"個人線程ID是:{Thread.CurrentThread.ManagedThreadId}");
            };
            Parallel.Invoke(act, act, act, act, act);

結果:

  

上面結果中咱們能夠看到主線程參與了進來。

ForEach:

        List<int> vs = new List<int>() { 1, 2, 3, 4, 5 };
            Parallel.ForEach<int>(vs, t =>
            {
                Console.WriteLine($"*********************************我是  {t} *******************************");
            });

結果:

  

For

       List<int> vs = new List<int>() { 1, 2, 3, 4, 5 };
            //從零開始 循環多少次
            Parallel.For(0, vs.Count,t=> {
                Console.WriteLine($"*********************************我是  {t} *******************************");
            });

結果:

  

上面的代碼中咱們能夠看到Parallel 適合在咱們循環的時候去使用這樣並行的去執行,咱們能夠減小程序的執行時間。

若是當咱們須要執行的集合過大有可能會 並行不少線程時咱們怕會影響咱們計算機的I/O 咱們還能夠設置最大的並行數防止程序執行時i/o風暴

       //設置 Parallel 最大並行線程的數量
            ParallelOptions options = new ParallelOptions();
            //最大並行數爲10;
            options.MaxDegreeOfParallelism = 10;

Parallel 官方介紹:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.parallel?redirectedfrom=MSDN&view=netframework-4.7.2

獲取當前計算機 最大線程數

        int workerThreads;
            int completionPortThreads;
            ThreadPool.GetMaxThreads( out workerThreads, out completionPortThreads);
            Console.WriteLine($"最大工做線程:{workerThreads} 最大I/O線程:{completionPortThreads} ");

            ThreadPool.GetMinThreads(out workerThreads, out completionPortThreads);
            Console.WriteLine($"最小工做線程:{workerThreads} 最小 I/O線程:{completionPortThreads} ");

結果:

  

有不足之處 但願你們指出相互學習,

            轉載請註明出處 謝謝!

相關文章
相關標籤/搜索