<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          C# 線程、線程池、Task概念+代碼實(shí)踐

          共 11325字,需瀏覽 23分鐘

           ·

          2021-03-03 13:49


          轉(zhuǎn)自:JerryMouseLi
          cnblogs.com/JerryMouseLi/p/14135600.html

          前言


          線程中的概念很多,如果沒有代碼示例來理解,會(huì)比較晦澀,而且有些概念落不到實(shí)處,因此,本文以一些運(yùn)行示例代碼,結(jié)果來闡述線程中的一些基礎(chǔ)概念。讓自己跟讀者一起把線程中的概念理解地更深刻。


          一、線程安全


          1.1、未出現(xiàn)線程搶占


          class ThreadTest2
          {
          bool done;
          static void Main()
          {
          ThreadTest2 tt = new ThreadTest2(); // Create a common instance
          new Thread(tt.Go).Start();
          tt.Go();
          }
          // Note that Go is now an instance method
          void Go()
          {
          if (!done)
          {
          done = true;
          Console.WriteLine("Done");
          }
          }
          }



          運(yùn)行結(jié)果如下:


          Done


          1.2、線程搶占


          class ThreadTest2
          {
          bool done;
          static void Main()
          {
          ThreadTest2 tt = new ThreadTest2(); // Create a common instance
          new Thread(tt.Go).Start();
          tt.Go();
          }
          // Note that Go is now an instance method
          void Go()
          {
          if (!done)
          {
          Console.WriteLine("Done");
          done = true;
          }
          }
          }


          運(yùn)行結(jié)果如下:


          Done
          Done



          線程搶占例子2:


          for (int i = 0; i < 10; i++)
          new Thread (() => Console.Write (i)).Start();



          運(yùn)行結(jié)果



          0223557799


          1.3、避免線程搶占


          class ThreadTest2
          {
          static readonly object locker = new object();
          bool done;
          static void Main()
          {
          ThreadTest2 tt = new ThreadTest2(); // Create a common instance
          new Thread(tt.Go).Start();
          tt.Go();
          }
          // Note that Go is now an instance method
          void Go()
          {
          lock (locker)
          {
          if (!done)
          {
          Console.WriteLine("Done");
          done = true;
          }
          }
          }
          }



          運(yùn)行結(jié)果如下:


          Done


          二、線程阻塞


          class Program
          {
          static void Main()
          {
          Thread t = new Thread(Go);
          t.Start();
          t.Join();
          Console.WriteLine("Thread t has ended!");
          }
          static void Go()
          {
          for (int i = 0; i < 1000; i++) Console.Write("y");
          }
          }



          運(yùn)行結(jié)果:



          1000個(gè)y打印完畢才輸出"Thread t has ended!"。


          Thread.Sleep (500);


          也會(huì)阻塞線程,讓渡CPU的執(zhí)行權(quán)給其他線程。


          三、Thread.yield()和Thread.sleep(0)


          sleep(0)效果相當(dāng)于yield(),會(huì)讓當(dāng)前線程放棄剩余時(shí)間片,進(jìn)入相同優(yōu)先級(jí)線程隊(duì)列的隊(duì)尾,只有排在前面的所有同優(yōu)先級(jí)線程完成調(diào)度后,它才能再次獲執(zhí)行的機(jī)會(huì)。


          四、線程如何工作


          多線痛通過內(nèi)部的線程調(diào)度器(thread scheduler)管理,通過clr委托操作系統(tǒng)。線程調(diào)度器會(huì)分配適當(dāng)?shù)膱?zhí)行時(shí)間給活動(dòng)線程,線程等待(鎖)或者線程阻塞(用戶輸入)不會(huì)消耗cpu執(zhí)行時(shí)間。


          單核處理器電腦上,在Windows,時(shí)間片通常會(huì)被分配幾十毫秒,遠(yuǎn)大于線程上下文切換還時(shí)間幾毫秒。


          在多處理器計(jì)算機(jī)上,多線程是通過時(shí)間片和真正的并發(fā)實(shí)現(xiàn)的,其中不同的線程在不同的CPU上同時(shí)運(yùn)行代碼。幾乎可以肯定,由于操作系統(tǒng)需要服務(wù)自己的線程以及其他應(yīng)用程序的線程,因此還會(huì)有一定的時(shí)間片。


          當(dāng)線程的執(zhí)行由于諸如時(shí)間片之類的外部因素而被中斷時(shí),該線程被認(rèn)為是被搶占的。在大多數(shù)情況下,線程無法控制其被搶占的時(shí)間和地點(diǎn)。


          五、線程與進(jìn)程


          線程與進(jìn)程有相似之處。就像進(jìn)程在計(jì)算機(jī)上并行運(yùn)行一樣,多個(gè)線程在單個(gè)進(jìn)程中并行運(yùn)行。進(jìn)程彼此完全隔離;線程的隔離度有限。特別是,線程與在同一應(yīng)用程序中運(yùn)行的其他線程共享(堆)內(nèi)存。這就是為什么線程有用的原因:例如,一個(gè)線程可以在后臺(tái)獲取數(shù)據(jù),而另一個(gè)線程可以在數(shù)據(jù)到達(dá)時(shí)顯示數(shù)據(jù)。


          六、線程的使用和濫用


          • 利于響應(yīng)式用戶界面


          在同時(shí)并行運(yùn)行的"worker"線程上運(yùn)行耗時(shí)的任務(wù),主UI線程可以自由繼續(xù)處理鍵盤和鼠標(biāo)事件。


          • 有效利用原本被阻塞的CPU


          當(dāng)線程正在等待來自另一臺(tái)計(jì)算機(jī)或硬件的響應(yīng)時(shí),多線程很有用。當(dāng)一個(gè)線程在執(zhí)行任務(wù)時(shí)被阻塞時(shí),其他線程可以利用本來沒有負(fù)擔(dān)的計(jì)算機(jī)的其他線程來響應(yīng)任務(wù)。


          • 并行編程


          如果以"分而治之"策略在多個(gè)線程之間共享工作負(fù)載,則執(zhí)行密集計(jì)算的代碼可以在多核或多處理器計(jì)算機(jī)上更快地執(zhí)行(請(qǐng)參閱第5部分)。


          • 隨機(jī)執(zhí)行


          在多核計(jì)算機(jī)上,有時(shí)可以通過預(yù)測(cè)可能需要完成的事情然后提前進(jìn)行來提高性能。LINQPad使用此技術(shù)來加速新查詢的創(chuàng)建。一種變化是并行運(yùn)行許多不同的算法,這些算法都可以解決同一任務(wù)。誰先獲得“勝利”,當(dāng)您不知道哪種算法執(zhí)行速度最快時(shí),此方法將非常有效。


          • 允許服務(wù)同時(shí)處理請(qǐng)求


          在服務(wù)器上,客戶端請(qǐng)求可以同時(shí)到達(dá),因此需要并行處理(如果使用ASP.NET,WCF,Web服務(wù)或遠(yuǎn)程處理,.NET Framework會(huì)為此自動(dòng)創(chuàng)建線程)。這在客戶端上也很有用(例如,處理對(duì)等網(wǎng)絡(luò)-甚至來自用戶的多個(gè)請(qǐng)求)。


          使用ASP.NET和WCF之類的技術(shù),您如果不知道多線程正在發(fā)生-除非您在沒有適當(dāng)鎖定的情況下訪問共享數(shù)據(jù)(可能通過靜態(tài)字段),會(huì)破壞線程安全性。


          線程之間的交互(通常是通過共享數(shù)據(jù)),會(huì)帶來很多復(fù)雜性,但卻不可避免,因此,有必要將交互保持在最低限度,并盡可能地堅(jiān)持簡(jiǎn)單可靠的設(shè)計(jì)。


          好的策略是將多線程邏輯封裝到可重用的類中,這些類可以獨(dú)立檢查和測(cè)試。框架本身提供了許多更高級(jí)別的線程結(jié)構(gòu),我們將在后面介紹。


          線程化還會(huì)在調(diào)度和切換線程時(shí)(如果活動(dòng)線程多于CPU內(nèi)核)會(huì)導(dǎo)致資源和CPU的浪費(fèi),并且還會(huì)產(chǎn)生創(chuàng)建/釋放成本。多線程并不總是可以加快您的應(yīng)用程序的速度-如果使用過多或使用不當(dāng),它甚至可能減慢其速度。例如,當(dāng)涉及大量磁盤I / O時(shí),讓幾個(gè)工作線程按順序運(yùn)行任務(wù)比一次執(zhí)行10個(gè)線程快得多。


          七、線程傳參


          7.1、lambda表達(dá)式傳參


          最方便的方法就是通過lambda表達(dá)式調(diào)用匿名方法,傳參數(shù)。


          static void Main()
          {
          Thread t = new Thread(() =>Print("Hello from t!"));
          t.Start();
          }
          static void Print(string message)
          {
          Console.WriteLine(message);
          }



          7.2、線程start方法傳參


          static void Main()
          {
          Thread t = new Thread(Print);
          t.Start("Hello from t!");
          }
          static void Print(object messageObj)
          {
          string message = (string)messageObj;
          // We need to cast here
          Console.WriteLine(message);
          }



          7.3、線程創(chuàng)建需要時(shí)間


          string text = "t1";
          Thread t1 = new Thread ( () => Console.WriteLine (text) );

          text = "t2";
          Thread t2 = new Thread ( () => Console.WriteLine (text) );

          t1.Start();
          t2.Start();



          運(yùn)行結(jié)果:


          t2
          t2



          以上運(yùn)行結(jié)果說明,在t1線程創(chuàng)建之前text被修改成了t2。


          八、線程命名


          每個(gè)線程都有名稱屬性,目的是為了更方便調(diào)試。


          static void Main()
          {
          Thread.CurrentThread.Name = "main";
          Thread worker = new Thread(Go);
          worker.Name = "worker";
          worker.Start();
          Go();
          }
          static void Go()
          {
          Console.WriteLine("Hello from " + Thread.CurrentThread.Name);
          }



          運(yùn)行結(jié)果:


          Hello from main
          Hello from worker



          九、前臺(tái)線程與后臺(tái)線程


          Thread worker = new Thread(() => Console.ReadLine());
          if (args.Length > 0) worker.IsBackground = true;
          worker.Name = "backThread";
          worker.Start();
          Console.WriteLine("finish!");



          前臺(tái)線程會(huì)隨著主線程窗口關(guān)閉而停止,后臺(tái)線程及時(shí)主線程窗口關(guān)閉自己獨(dú)立運(yùn)行。


          十、線程優(yōu)先級(jí)


          線程優(yōu)先級(jí)決定了操作系統(tǒng)執(zhí)行活動(dòng)線程時(shí)間的長(zhǎng)短。


          enum ThreadPriority { Lowest, BelowNormal, Normal, AboveNormal, Highest }


          有時(shí)候提高了線程的優(yōu)先級(jí),但卻仍然無法滿足一些實(shí)時(shí)的應(yīng)用需求,這時(shí)候就需要提高進(jìn)程的優(yōu)先級(jí),System.Diagnostics命名空間中的process進(jìn)程類.


          using (Process p = Process.GetCurrentProcess())
          p.PriorityClass = ProcessPriorityClass.High;


          實(shí)際上,ProcessPriorityClass.High比最高優(yōu)先級(jí)低1個(gè)級(jí)別:Realtime。將進(jìn)程優(yōu)先級(jí)設(shè)置為Realtime,可指示OS,您永遠(yuǎn)不希望該進(jìn)程將CPU時(shí)間浪費(fèi)在另一個(gè)進(jìn)程上。如果您的程序進(jìn)入意外的無限循環(huán),您甚至可能會(huì)發(fā)現(xiàn)操作系統(tǒng)已鎖定,只剩下電源按鈕可以拯救您!因此,高通常是實(shí)時(shí)應(yīng)用程序的最佳選擇。


          如果您的實(shí)時(shí)應(yīng)用程序具有用戶界面,則提高處理優(yōu)先級(jí)將給屏幕更新帶來過多的CPU時(shí)間,從而減慢整個(gè)計(jì)算機(jī)的速度(尤其是在UI復(fù)雜的情況下)。降低主線程的優(yōu)先級(jí)并提高進(jìn)程的優(yōu)先級(jí)可確保實(shí)時(shí)線程不會(huì)因屏幕重繪而被搶占,但不會(huì)解決使其他應(yīng)用程序耗盡CPU時(shí)間的問題,因?yàn)椴僮飨到y(tǒng)仍會(huì)分配 整個(gè)過程的資源不成比例。理想的解決方案是使實(shí)時(shí)工作程序和用戶界面作為具有不同進(jìn)程優(yōu)先級(jí)的單獨(dú)應(yīng)用程序運(yùn)行,并通過遠(yuǎn)程處理或內(nèi)存映射文件進(jìn)行通信。內(nèi)存映射文件非常適合此任務(wù)。我們將在C#4.0的第14和25章中簡(jiǎn)要介紹它們的工作原理。


          十一、異常處理


          Go無法補(bǔ)捉異常,GoCatch能捕獲當(dāng)前線程的異常,輸出Console.WriteLine("exception.");由此可見,線程創(chuàng)建之后,異常只能由本線程捕獲,如果其調(diào)用方需要捕獲,則得用共享內(nèi)存方式往上傳,Task幫我們做了這件事,調(diào)用方可在task.result里捕獲到其他線程的異常。


          public static void Main()
          {
          try
          {
          new Thread(Go).Start();
          Console.ReadKey();
          }
          catch (Exception ex)
          {
          // We'll never get here!
          Console.WriteLine("Exception!");
          }
          }
          static void Go() { throw null; } // Throws a NullReferenceException
          static void GoCatch()
          {
          try
          {
          // ...
          throw null; // The NullReferenceException will get caught below
          // ...
          }
          catch (Exception ex)
          {
          // Typically log the exception, and/or signal another thread
          // that we've come unstuck
          // ...
          Console.WriteLine("exception.");
          }
          }



          十二、線程池


          當(dāng)你創(chuàng)建一個(gè)線程,幾百毫秒會(huì)被花費(fèi)在例如創(chuàng)建本地私有變量堆棧。每個(gè)線程都會(huì)默認(rèn)消耗1MB內(nèi)存,從而允許在非常精細(xì)的級(jí)別上應(yīng)用多線程而不會(huì)影響性能。當(dāng)利用多核處理器以“分而治之”的方式并行執(zhí)行計(jì)算密集型代碼時(shí),這很有用。


          線程池還限制了將同時(shí)運(yùn)行的工作線程總數(shù)。活動(dòng)線程過多會(huì)限制操作系統(tǒng)的管理負(fù)擔(dān),并使CPU緩存無效。一旦達(dá)到限制,作業(yè)將排隊(duì)并僅在另一個(gè)作業(yè)完成時(shí)才開始。這使任意并發(fā)的應(yīng)用程序成為可能,例如Web服務(wù)器。(異步方法模式是一種先進(jìn)的技術(shù),通過高效利用池線程來進(jìn)一步實(shí)現(xiàn)這一點(diǎn);我們?cè)贑#4.0的第23章中簡(jiǎn)要介紹了這一點(diǎn))。


          有多種進(jìn)入線程池的方法:


          • 通過Task Parallel Library(來自Framework 4.0)


          • 通過調(diào)用ThreadPool.QueueUserWorkItem


          • 通過異步委托(await)


          • 通過BackgroundWorker


          以下方法間接使用線程池:


          • WCF,遠(yuǎn)程,ASP.NET和ASMX Web服務(wù)應(yīng)用程序服務(wù)器


          • System.Timers.Timer和System.Threading.Timer


          • 以Async結(jié)尾的框架方法,例如WebClient(基于事件的異步模式)上的框架方法和大多數(shù)BeginXXX方法(異步編程模型模式)


          • PLINQ


          使用池線程時(shí),需要注意以下幾點(diǎn):


          • 無法設(shè)置池線程的名稱,這會(huì)使調(diào)試更加困難(盡管您可以在Visual Studio的“線程”窗口中進(jìn)行調(diào)試時(shí)附加說明)。


          • 池線程始終是后臺(tái)線程(這通常不是問題)。


          • 除非您調(diào)用ThreadPool.SetMinThreads(請(qǐng)參閱優(yōu)化線程池),否則阻塞線程池可能會(huì)在應(yīng)用程序的早期階段觸發(fā)額外的延遲。


          可以自由更改池線程的優(yōu)先級(jí)-將其釋放回池后將恢復(fù)為正常狀態(tài)。


          可以通過Thread.CurrentThread.IsThreadPoolThread屬性查詢當(dāng)前是否在線程池上執(zhí)行。


          12.1、通過TPL進(jìn)入線程池


          通過Task Parallel Library庫中的Task類可輕松使用線程池,Task類由Framework 4.0引入,如果你熟悉老的結(jié)構(gòu),考慮用不帶泛型Task類來替代ThreadPool.QueueUserWorkItem,而泛型Task 代表的是一個(gè)異步委托。新的結(jié)構(gòu)更快,更方便,比老的更靈活。


          使用不帶泛型例子的Task類,調(diào)用Task.Factory.StartNew,傳遞一個(gè)目標(biāo)方法的委托;


          static void Main()    // The Task class is in System.Threading.Tasks
          {
          var task=Task.Factory.StartNew(Go);
          Console.WriteLine("main");
          task.Wait() ;
          Console.WriteLine(task.Result);
          Console.ReadLine();
          }
          static string Go()
          {
          if (Thread.CurrentThread.IsThreadPoolThread)
          { Console.WriteLine("Hello from the thread pool!"); }
          else { Console.WriteLine("Hello just from the thread!"); }
          return "task complete!";
          }



          輸出結(jié)果:


          main
          Hello from the thread pool!
          task complete!



          12.1.1、Task異常捕獲


          static void Main()    // The Task class is in System.Threading.Tasks
          {
          var task=Task.Factory.StartNew(Go);
          Console.WriteLine("main");
          try
          { task.Wait(); }
          catch (Exception e)
          {
          Console.WriteLine("exception!");
          }
          Console.WriteLine(task.Result);
          Console.ReadLine();
          }
          static string Go()
          {
          if (Thread.CurrentThread.IsThreadPoolThread)
          { Console.WriteLine("Hello from the thread pool!"); }
          else { Console.WriteLine("Hello just from the thread!"); }
          throw null;
          return "task complete!";
          }



          運(yùn)行結(jié)果,在主線程中捕獲到了其他線程的異常:



          static void Main()
          {
          // Start the task executing:
          Task<string> task = Task.Factory.StartNew<string>
          ( () => DownloadString ("http://www.linqpad.net") );
          // We can do other work here and it will execute in parallel:
          RunSomeOtherMethod();
          // When we need the task's return value, we query its Result property:
          // If it's still executing, the current thread will now block (wait)
          // until the task finishes:
          string result = task.Result;
          }
          static string DownloadString (string uri)
          {
          using (var wc = new System.Net.WebClient())
          return wc.DownloadString (uri);
          }


          Task<string> 就是一個(gè)返回值為string的異步委托。


          12.2、不同過TPL進(jìn)入線程池



          如果你的框架是.Net 4.0之前的,你可以不通過Task Parallel Library 進(jìn)入線程池。


          12.2.1、QueueUserWorkItem


          static void Main()
          {
          ThreadPool.QueueUserWorkItem(Go);
          ThreadPool.QueueUserWorkItem(Go, 123);
          Console.ReadLine();
          }
          static void Go(object data) // data will be null with the first call.
          {
          Console.WriteLine("Hello from the thread pool! " + data);
          }



          運(yùn)行結(jié)果:


          Hello from the thread pool!
          Hello from the thread pool! 123


          與Task不同:


          • 后續(xù)執(zhí)行中無法返回執(zhí)行結(jié)果;


          • 無法返回異常給調(diào)用者;


          12.2.2 異步委托


          委托的EndInvoke 做了3件事:


          • 阻塞等待;


          • 返回結(jié)果;


          • 向調(diào)用者跑出異常;


          12.3、線程池優(yōu)化


          線程池從其池中的一個(gè)線程開始。分配任務(wù)后,池管理器會(huì)“注入”新線程以應(yīng)對(duì)額外的并發(fā)工作負(fù)載(最大限制)。在足夠長(zhǎng)時(shí)間的不活動(dòng)之后,如果池管理器懷疑這樣做會(huì)導(dǎo)致更好的吞吐量,則可以“退出”線程。


          可以通過調(diào)用ThreadPool.SetMaxThreads;來設(shè)置池將創(chuàng)建的線程的上限; 默認(rèn)值為:


          • 32位環(huán)境中的Framework 4.0中的1023


          • 64位環(huán)境中的Framework 4.0中的32768


          • Framework 3.5中的每個(gè)內(nèi)核250個(gè)


          • Framework 2.0中每個(gè)內(nèi)核25個(gè)


          還可以通過調(diào)用ThreadPool.SetMinThreads設(shè)置下限。下限的作用是微妙的:這是一種高級(jí)優(yōu)化技術(shù),它指示池管理器在達(dá)到下限之前不要延遲線程的分配。當(dāng)存在阻塞的線程時(shí),提高最小線程數(shù)可提高并發(fā)性。


          默認(rèn)的下限是每個(gè)處理器內(nèi)核一個(gè)線程-允許全部CPU利用率的最小值。但是,在服務(wù)器環(huán)境(例如IIS下的ASP .NET)上,下限通常要高得多-多達(dá)50個(gè)或更多。

          設(shè)置線程池最小線程數(shù)量。


          ThreadPool.SetMinThreads (50, 50);


          本文代碼Git:https://github.com/JerryMouseLi/Thread.git


          - EOF -

          回復(fù) 【關(guān)閉】學(xué)關(guān)
          回復(fù) 【實(shí)戰(zhàn)】獲取20套實(shí)戰(zhàn)源碼
          回復(fù) 【被刪】學(xué)個(gè)
          回復(fù) 【訪客】學(xué)
          回復(fù) 【小程序】學(xué)獲取15套【入門+實(shí)戰(zhàn)+賺錢】小程序源碼
          回復(fù) 【python】學(xué)微獲取全套0基礎(chǔ)Python知識(shí)手冊(cè)
          回復(fù) 【2019】獲取2019 .NET 開發(fā)者峰會(huì)資料PPT
          回復(fù) 【加群】加入dotnet微信交流群

          臥槽:微信可以這樣換個(gè)字體了!


          GET 和 POST請(qǐng)求的本質(zhì)區(qū)別是什么?原來我一直理解錯(cuò)了


          點(diǎn)贊和在看就是最大的支持??

          瀏覽 82
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  在线日本a∨ | 国产黄片免费在线观看 | 99久久人妻无码中文字幕系列 | 天天色天天综合网 | 免费看二极一级黄色片 |