<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      菜鳥之旅——學習線程(線程和線程池)

        上一篇主要介紹了進程和線程的一些基本知識,現在回歸正題,我們來學一下線程的使用,本篇主要是使用新建線程和線程池的方式。

      線程  

        先來介紹簡單的線程使用:使用new方法來創建線程,至于撤銷線程,我們不必去管(我也不知道怎么去管XD),因為CLR已經替我們去管理了。 

       創建

        先來看一個簡單的使用線程的例子:

              static void Main(string[] args)
              {
                  Thread t1 = new Thread(Menthod1);
                  Thread t2 = new Thread(Menthod2);
                  t1.Start();
                  t2.Start("線程2參數");
      
                  Console.WriteLine("主線程的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("--------------------");
                  Console.ReadLine();
              }
      
              static void Menthod1()
              {
                  Thread.Sleep(2000);
                  Console.WriteLine("線程1的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("--------------------");
              }
      
              static void Menthod2(object obj)
              {
                  Thread.Sleep(1000);
                  Console.WriteLine("線程2的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("obj:{0}", obj);
                  Console.WriteLine("--------------------");
              }

        我們可以用過new的方式創建一個線程,然后使用Start()的方法來運行該線程,線程則會在其生命周期去執行Method1方法,執行方法肯定需要時間的,但是Method1的方法過于簡單,我們使用Thread.Sleep的方法來進行停頓,這個方法可以暫時將當前的線程睡眠一段時間(毫秒為單位),因為主線程只是創建并運行t1子線程,運行任務的不是主線程,所以主線程可以繼續往后執行程序。

        我們還可以向線程執行的方法傳入一個參數,例如線程2,在t2執行Start方法時,傳入想要傳入的參數,然后就可以在運行的時候使用了;不過參數是有限制的,在子線程的方法只能接受object的類型的參數,則在使用的時候需要顯式轉換類型,還有就是只能接受一個參數,多個參數也不會支持。

       線程與Lambda表達式

        線程的new也支持Lambda表達式,若是執行方法比較簡單,或者在某些場景下,我們可以將線程執行的代碼使用Lambda內置到新建里面:

              static void Main(string[] args)
              {
                  Thread t1 = new Thread(() =>
                  {
                      Thread.Sleep(2000);
                      Console.WriteLine("線程1的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                      Console.WriteLine("--------------------");
                  });
                  t1.Start();
      
                  Console.WriteLine("主線程的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("--------------------");
                  Console.ReadLine();
              }

       

        這里這樣子寫還有一個好處,就是這里可以直接使用主方法里面的變量,當然,這也會產生線程安全的問題。

       線程同步

        一個進程中的多個線程都是可以訪問其進程的其他資源,多線程若不加以控制也是并發執行的,若在多線程的執行方法中包含操作全局變量、者靜態變量或是使用I/O設備的時候,很容易的就會產生線程安全的問題,從而導致不可預估的錯誤。這里就需要進行線程同步了,下面介紹一些線程同步的方式。

        Join:

        我們有時候開啟了n各子線程來進行輔助計算,但是又想主線程等待所有子線程計算完畢在接著執行,或者線程之間的關系更復雜,其中涉及了線程的阻塞與激活,那么就可以使用Join()的方法來阻塞主線程,實現一種最簡單的線程同步:

              static void Main(string[] args)
              {
                  Thread t1 = new Thread(Menthod1);
                  Thread t2 = new Thread(Menthod2);
                  t1.Start();
                  t1.Join();
      
                  t2.Start("線程2參數");
                  t2.Join();
      
                  Console.WriteLine("主線程的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("--------------------");
                  Console.ReadLine();
              }
      
              static void Menthod1()
              {
                  Thread.Sleep(2000);
                  Console.WriteLine("線程1的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("--------------------");
              }
      
              static void Menthod2(object obj)
              {
                  Thread.Sleep(4000);
                  Console.WriteLine("線程2的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("obj:{0}", obj);
                  Console.WriteLine("--------------------");
              }

        上面的調用阻塞的過程:先是t1開始,阻塞2秒,再接著t2執行,阻塞4秒,共計阻塞6秒,貌似沒有發揮出來多線程的優勢,但是也有可能在t2運行之前必須運行完t1,所以,Join()的調用需要視情況而定,Join()就是阻塞當前線程到當前位置,直到阻塞線程結束后,當前線程繼續運行。

        同步事件:

        除了Join()來實現線程間的阻塞與激活,還有同步事件來進行處理;同步事件有兩種:AutoResetEvent和 ManualResetEvent。它們之間唯一不同的地方就是在激活線程之后,狀態是否自動由終止變為非終止。AutoResetEvent自動變為非終止,就是說一個AutoResetEvent只能激活一個線程。而ManualResetEvent要等到它的Reset方法被調用,狀態才變為非終止,在這之前,ManualResetEvent可以激活任意多個線程:先來看ManualResetEvent的使用:

              static ManualResetEvent muilReset = new ManualResetEvent(false);
              static void Main(string[] args)
              {
      
                  Thread t1 = new Thread(Menthod1);
                  t1.Start();
                  Thread t2 = new Thread(Menthod2);
                  t2.Start("params");
                  Thread t3 = new Thread(Menthod3);
                  t3.Start();
                  muilReset.WaitOne();
      
                  Console.WriteLine("主線程的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("--------------------");
                  Console.ReadLine();
              }
      
              static void Menthod1()
              {
                  muilReset.WaitOne();
                  Console.WriteLine("線程1的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("--------------------");
              }
      
              static void Menthod2(object obj)
              {
                  muilReset.WaitOne();
                  Console.WriteLine("線程2的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("obj:{0}", obj);
                  Console.WriteLine("--------------------");
              }
      
              static void Menthod3()
              {
                  Thread.Sleep(3000);
                  Console.WriteLine("線程3的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("激活線程...");
                  Console.WriteLine("--------------------");
                  muilReset.Set();
              }

         上面例子我們將主線程、線程1、線程2阻塞,使用線程3在3秒鐘之后激活全部線程,顯示成功

        線程3的ID:12
        激活線程...
        --------------------
        線程2的ID:11
        obj:params
        --------------------
        主線程的ID:9
        --------------------
        線程1的ID:10
        --------------------

        若是使用AutoResetEvent則只能激活主線程

        線程3的ID:12
        激活線程...
        --------------------
        主線程的ID:9
        --------------------

        注:ManualResetEvent會給所有引用的線程都發送一個信號(多個線程可以共用一個ManualResetEvent,當ManualResetEvent調用Set()時,所有線程將被喚醒),而AutoResetEvent只會隨機給其中一個發送信號(只能喚醒一個)。

        這里的線程同步還可以使用委托與事件(推薦使用事件)來實現線程間的簡單通訊,比如在某一線程執行到某一結點后,通過事件向另一個或者多個線程發送更多的信息。

        Monitor:

        上述的例子是各個子線程之間沒有使用公共資源(公共變量、I/O設備等),它們只存在執行順序上的先后;我們來找一個使用公共變量的例子試一試:

              static List<int> ids = new List<int>();
              static void Main(string[] args)
              {
                  Console.WriteLine("主線程的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("--------------------");
                  for (int i = 0; i < 100; i++)
                  {
                      Thread t = new Thread(Menthod);
                      t.Start();
                  }
      
                  Console.ReadLine();
              }
      
              static void Menthod()
              {
                  ids.Add(Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine(ids.Count);
                  Console.WriteLine(ids[0]);
                  Console.WriteLine("--------------------");
                  ids.Clear();
              }

         這里新建100個子線程,然后使用一個靜態公共變量List輸出線程的Id,以上述方法運行時,有時會報出錯誤:索引超出范圍。必須為非負值并小于集合大小!說明當前子線程輸出Id時,該集合被Clear掉了,這就是一個很簡單的線程安全問題,所以需要使用Monitor來進行鎖住代碼塊,MSDN推薦定義一個私有的初始化不會再變的object變量作為一個排他鎖,因為排他鎖變了就沒意義了,下面代碼就可以變為:

              static readonly object locker = new object();
              static List<int> ids = new List<int>();
              static void Main(string[] args)
              {
                  Console.WriteLine("主線程的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("--------------------");
                  for (int i = 0; i < 100; i++)
                  {
                      Thread t = new Thread(Menthod);
                      t.Start();
                  }
      
                  Console.ReadLine();
              }
      
              static void Menthod()
              {
                  Monitor.Enter(locker);
                  ids.Add(Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine(ids.Count);
                  Console.WriteLine(ids[0]);
                  Console.WriteLine("--------------------");
                  ids.Clear();
                  Monitor.Exit(locker);
              }

        當有一個線程進入鎖住的代碼塊是,是在外面加鎖,這樣剩下的線程只能等待當前線程執行完畢后釋放鎖,這樣的話就保證了List變量在取值時不會被其他線程清除掉;盡管List是一個線程安全類,就是多線程操作該類時只有一個線程操作的類,但是這里仍然避免不了線程安全的問題,因為仍然控制不了操作的順序,在清除后讀取肯定會報錯。

        lock:

        調用Monitor執行只能有一個線程運行的代碼塊時,仍有可能會拋出異常,但是有時候又不能終止進程,使用try{}catch{}包起來是個解決方式,那干脆再封裝一次Monitor的方法,于是lock便出現了,則上述的例子可以改寫為:

              static void Menthod()
              {
                  lock (locker)
                  {
                      ids.Add(Thread.CurrentThread.ManagedThreadId);
                      Console.WriteLine(ids.Count);
                      Console.WriteLine(ids[0]);
                      Console.WriteLine("--------------------");
                      ids.Clear();
                  }
              }

        等價于:

              static void Menthod()
              {
                  try
                  {
                      Monitor.Enter(locker);
                      ids.Add(Thread.CurrentThread.ManagedThreadId);
                      Console.WriteLine(ids.Count);
                      Console.WriteLine(ids[0]);
                      Console.WriteLine("--------------------");
                      ids.Clear();
                  }
                  catch (Exception ex)
                  {
      
                  }
                  finally
                  {
                      Monitor.Exit(locker);
                  }
              }

        當線程進入lock代碼塊時,將會調用Monitor.Enter()方法,退出代碼塊會調用Monitor.Exit()方法。另外,Monitor還提供了三個靜態方法Monitor.Pulse(),Monitor.PulseAll()和Monitor.Wait() ,用來實現一種喚醒機制的同步。關于這三個方法的用法,可以參考MSDN,我這里也在學習中,就先不講述了。雖說lock沒有Monitor功能強大,但是使用確實方便,這里取舍就看實際需求了。  

         補充

        線程同步的方式還有很多,比如Mutex。還有很多的方法,以后用到的時候在研究吧。
        Mutex:Mutex不具備Wait,Pulse,PulseAll的功能,因此,我們不能使用Mutex實現類似的喚醒的功能;不過Mutex有一個比較大的特點,Mutex是跨進程的,因此我們可以在同一臺機器甚至遠程的機器上的多個進程上使用同一個互斥體。

       

      線程池

       目的

        上一篇內容提到,線程是由線程ID、程序計數器、寄存器集合和堆棧組成,是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程自己不擁有系統資源,只擁有一些在運行中必不可少的資源;這就意味著線程在進行創建與撤銷的時候,都需要分配與清空一些資源,總歸需要付出一定量的時空消耗;在一些大量使用線程(CPU密集、I/O密集)的進程里面,使用傳統的new方法會頻繁的創建、撤銷線程,雖說線程的管理是由CLR來進行的,但是總歸是影響性能,為了減少創建與撤銷的時空消耗,便引入了線程池的概念:將線程實體池化,就是事先創建一定量的線程實體,然后放到一個容器中,做統一管理,沒有任務時,線程處于空閑狀態(差不多就是就緒狀態),來任務后選擇一個空閑線程來執行,執行完畢后自動關閉線程(沒有被撤銷,只是置為空閑狀態)。

       CLR線程池

        CLR線程池是.NET框架中很重要的一部分,不光能被開發人員使用,自身的很多功能也是由線程池實現;我們在將任務委托給線程池的時候,是將該任務放到線程池的任務隊列上,若線程池內存在空閑線程,則會將該任務委托給該線程,等待調度到CPU執行,若是沒有空閑的線程且線程池所管理的線程數量還沒有達到上限的時候,線程池便會創建新的Thread實體,否則,該任務會在隊列中等待。

        數量上限:在CLR 2.0 SP1之前的版本中,線程池中 默認最大的線程數量 = 處理器數 * 25, CLR 2.0 SP1之后就變成了 默認最大線程數量 = 處理器數 * 250,線程上限可以改變,通過使用ThreadPool.GetMax+Threads和ThreadPool.SetMaxThreads方法,可以獲取和設置線程池的最大線程數。

       使用

        線程池的使用更簡單一些:

              static void Main(string[] args)
              {
                  ThreadPool.QueueUserWorkItem(new WaitCallback(Menthod1));
                  ThreadPool.QueueUserWorkItem(new WaitCallback(Menthod2), "object");
      
                  Console.WriteLine("主線程的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("--------------------");
                  Console.ReadLine();
              }
      
              static void Menthod1(object obj)
              {
                  Thread.Sleep(2000);
                  Console.WriteLine("線程1的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("--------------------");
              }
      
              static void Menthod2(object obj)
              {
                  Thread.Sleep(4000);
                  Console.WriteLine("線程2的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("obj:{0}", obj);
                  Console.WriteLine("--------------------");
              }

        這里QueueUserWorkItem方法需要傳入一個QueueUserWorkItem委托(帶object類型的參數,無返回值),所以我們需要線程執行的任務需要帶一個object的參數,并且QueueUserWorkItem方法加入時存在一個重載,可以在這里傳入一個參數。

        當然這里也可以使用Lambda表達式:

              ThreadPool.QueueUserWorkItem(new WaitCallback((object obj)=>{
                  Thread.Sleep(2000);
                  Console.WriteLine("線程3的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("obj:{0}", obj);
                  Console.WriteLine("--------------------");
              }), "lambda");
      

        線程同步

        使用線程池并發執行任務同樣會遇到線程安全的問題,一樣需要進行同步,在涉及線程使用公共資源,Monitor、lock等方法與上述線程使用一樣,同樣能達到理想的效果,就不重復介紹了;但是對于控制執行順序上,這個沒有使用new線程來的自由。

        同步事件:

        在線程池中,沒有Join方法,若想控制線程的執行順序,我推薦使用主線程等待線程池任務執行完畢,阻塞主線程的方式,這里可以使用WaitHandle:

              static void Main(string[] args)
              {
                  List<WaitHandle> handles = new List<WaitHandle>();
      
                  AutoResetEvent autoReset1 = new AutoResetEvent(false);
                  ThreadPool.QueueUserWorkItem(new WaitCallback(Menthod1), autoReset1);
                  handles.Add(autoReset1);
      
                  AutoResetEvent autoReset2 = new AutoResetEvent(false);
                  ThreadPool.QueueUserWorkItem(new WaitCallback(Menthod2), autoReset2);
                  handles.Add(autoReset2);
      
                  WaitHandle.WaitAll(handles.ToArray());
      
                  Console.WriteLine("主線程的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("--------------------");
                  Console.ReadLine();
              }
      
              static void Menthod1(object obj)
              {
                  Thread.Sleep(2000);
                  Console.WriteLine("線程1的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("--------------------");
                  AutoResetEvent handle = (AutoResetEvent)obj;
                  handle.Set();
              }
      
              static void Menthod2(object obj)
              {
                  Thread.Sleep(4000);
                  Console.WriteLine("線程2的ID:{0}", Thread.CurrentThread.ManagedThreadId);
                  Console.WriteLine("--------------------");
                  AutoResetEvent handle = (AutoResetEvent)obj;
                  handle.Set();
              }

        在這里,給線程池每個相關的線程都創建一個AutoResetEvent,在執行完畢之后分別把屬于自己的AutoResetEvent變為非終止,WaitHandle使用WaitAll方法阻塞主線程、等待所有的AutoResetEvent事件變為true,另外WaitHandle還有一個WaitAny方法阻塞,不過是只要其中一個線程結束,就會繼續運行,不再阻塞。

        注:

        1、WaitHandle同樣可以用于new創建線程的同步事件;

        2、WaitHandle等待方法(WaitAll、WaitAny)的數組長度的數目必須少于或等于 64 個,為了解決此限制,有網友封裝了一個類,比較好用:

          public class MutipleThreadResetEvent : IDisposable
          {
              private readonly ManualResetEvent done;
              private readonly int total;
              private long current;
      
              /// <summary>
              /// 構造函數
              /// </summary>
              /// <param name="total">需要等待執行的線程總數</param>
              public MutipleThreadResetEvent(int total)
              {
                  this.total = total;
                  current = total;
                  done = new ManualResetEvent(false);
              }
      
              /// <summary>
              /// 喚醒一個等待的線程
              /// </summary>
              public void SetOne()
              {
                  // Interlocked 原子操作類 ,此處將計數器減1
                  if (Interlocked.Decrement(ref current) == 0)
                  {
                      //當所以等待線程執行完畢時,喚醒等待的線程
                      done.Set();
                  }
              }
              /// <summary>
              /// 等待所有線程執行完畢
              /// </summary>
              public void WaitAll()
              {
                  done.WaitOne();
              }
      
              /// <summary>
              /// 釋放對象占用的空間
              /// </summary>
              public void Dispose()
              {
                  ((IDisposable)done).Dispose();
              }
          }
      MutipleThreadResetEvent

        

      補充

        1、線程有前臺線程和后臺線程之分,使用new創建的線程默認為前臺線程(可以使用IsBackground屬性來進行更改),線程池里面都是后臺線程

         前臺線程:前臺線程是不會被立即關閉的,它的關閉只會發生在自己執行完成時,不受外在因素的影響。假如應用程序退出,造成它的前臺線程終止,此時CLR仍然保持活動并運行,使應用程序能繼續運行,當它的的前臺線程都終止后,整個進程才會被銷毀。

         后臺線程:后臺線程是可以隨時被CLR關閉而不引發異常的,也就是說當后臺線程被關閉時,資源的回收是立即的,不等待的,也不考慮后臺線程是否執行完成,就算是正在執行中也立即被終止。

        2、線程被系統調度到CPU執行時存在優先級:這里的優先級不是優先執行,而是被調度到CPU執行的概率高;使用new創建線程與線程池的優先級默認都是Normal,不過前者可以通過Priority屬性來設置優先級。優先級有5個級別:Highest、AboveNormal、Normal、BelowNormal和Lowest。

        3、線程存在Suspend與Resume這兩個過時的方法,但不是代表不能使用,只是微軟不推薦你用,MSDN給出的原因是:請不要使用 Suspend 和 Resume 方法來同步線程活動。 沒有辦法知道當你暫停執行線程什么代碼。 如果在安全權限評估期間持有鎖,您掛起線程中的其他線程 AppDomain 可能被阻止。 如果執行類構造函數時,您掛起線程中的其他線程 AppDomain 中嘗試使用類被阻止。 可以很容易發生死鎖。你可以無視這個警告繼續使用這兩個方法進行線程同步,若覺得不怎么靠譜,那么可以在線程代碼加入判斷來保證執行正確性,或者使用控制同步事件(AutoResetEvent等)來實現線程同步。

        4、線程池的線程很珍貴,因為數量是有限的,所以不適合執行長時間的作業任務,適合執行短期并且頻繁的作業任務,若想執行長時間的作業任務,建議使用new創建新線程的方式。畢竟線程池設計的初衷就是為了解決頻繁創建與撤銷線程而造成的資源浪費。

       

      posted on 2018-02-12 17:01  愉悅的紳士  閱讀(1178)  評論(1)    收藏  舉報

      主站蜘蛛池模板: 国产性色的免费视频网站| 忘忧草在线社区www中国中文| 999国产精品999久久久久久| 日韩精品一区二区三区激| 蜜桃视频在线免费观看一区二区 | 亚洲真人无码永久在线| 人妻精品动漫H无码中字| 国产超碰人人做人人爰| 国产一区二区高潮视频| 国产精品中文字幕一区| 激情综合网五月激情五月| 女女互揉吃奶揉到高潮视频| 国产精品日日摸夜夜添夜夜添无码 | 亚洲狠狠婷婷综合久久久久图片| 九九热在线免费视频观看| 2020年最新国产精品正在播放| 国精产品一区一区三区mba下载| 久久亚洲精品国产精品| 亚洲一区在线成人av| 额尔古纳市| 日本伊人色综合网| 五月婷之久久综合丝袜美腿| 成人午夜在线观看刺激| 一边添奶一边添p好爽视频| 亚洲精品一区国产精品| 久久人人爽人人爽人人av| 久久久久久久无码高潮| 亚洲乱熟女一区二区三区| 小嫩批日出水无码视频免费 | 国产欧美在线一区二区三| 成人网站国产在线视频内射视频| 国产精品视频一品二区三| 国产精品午夜av福利| 狠狠色综合久久狠狠色综合| 中文字幕日韩视频欧美一区| 中文字幕在线精品人妻| 欧美精品V欧洲精品| 精品人妻日韩中文字幕| 99久久国产综合精品女图图等你 | 精品 无码 国产观看| 亚洲小说乱欧美另类|