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

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

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

      .NET基礎拾遺(5)多線程開發基礎

       Index :

       (1)類型語法、內存管理和垃圾回收基礎

       (2)面向對象的實現和異常的處理基礎

       (3)字符串、集合與流

       (4)委托、事件、反射與特性

       (5)多線程開發基礎

       (6)ADO.NET與數據庫開發基礎

       (7)WebService的開發與應用基礎

      一、多線程編程的基本概念

        下面的一些基本概念可能和.NET的聯系并不大,但對于掌握.NET中的多線程開發來說卻十分重要。我們在開始嘗試多線程開發前,應該對這些基礎知識有所掌握,并且能夠在操作系統層面理解多線程的運行方式。

      1.1 操作系統層面的進程和線程

        (1)進程

        進程代表了操作系統上運行著的一個應用程序。進程擁有自己的程序塊,擁有獨占的資源和數據,并且可以被操作系統調度。But,即使是同一個應用程序,當被強制啟動多次時,也會被安放到不同的進程之中單獨運行。

        直觀地理解進程最好的方式就是通過進程管理器瀏覽,其中每條記錄就代表了一個活動著的進程:

        (2)線程

        線程有時候也被稱為輕量級進程,它的概念和進程十分相似,是一個可以被調度的單元,并且維護自己的堆棧和上下文環境。線程是附屬于進程的,一個進程可以包含1個或多個線程,并且同一進程內的多個線程共享一塊內存塊和資源

        由此看來,一個線程是一個操作系統可調度的基本單元,但是它的調度受限于該線程所屬的進程,也就是說操作系統首先決定執行下一個執行的進程,進而才會調度該進程內的線程。一個線程的基本生命周期如下圖所示:

        (3)進程和線程的區別

        最大的區別在于隔離性,每個進程都會被單獨隔離(進程擁有自己的內存、資源和運行數據,一個進程的崩潰不會影響到其他進程,因此進程間的交互也相對困難),而同一進程內的所有線程則共享內存和資源,并且一個線程可以訪問和結束同一進程內的其他線程。

      1.2 多線程程序在操作系統中是并行執行的嗎?

        (1)線程的調度

        在計算機系統發展的早期,操作系統層面不存在并行的概念,所有的應用程序都在排隊等候一個單線程的隊列之中,每個程序都必須等到前面的程序都安全執行完畢之后才能獲得執行的權利,一個小小的錯誤將會導致操作系統上的所有程序的阻塞。在后來的操作系統中,逐漸產生了分時和進程、線程的概念。

        多個線程由操作系統進行調度控制,決定何時運行哪個線程。所謂線程調度,是指操作系統決定如何安排線程執行順序的算法。按常規分類,線程調度可以分為以下兩種:

        ①搶占式調度

        搶占式調度是指每個線程都只有極少的運行時間(在Windows NT內核模式下這個時間不會超過20ms),而當時間片用完時該線程就會被強制暫停,保存上下文并把運行權利交給下一個線程。這樣調度的結果就是:所有的線程都在被不停地快速切換運行,使得用戶感覺所有的線程都在并行運行

        ②非搶占式調度

        非搶占式調度是指某個線程在運行時不會被操作系統強制暫停,它可以持續地運行直到運行告一段落并主動交出運行權。在這樣的調度方式之下,線程的運行就是單隊列的,并且可能產生惡意程序長期霸占運行權的情況。

      PS:現在很多的操作系統(包括Windows在內),都同時采用了搶占式和非搶占式模式。對于那些優先級較高的線程,OS采用非搶占式來給予充分的時間運行,而對于普通的線程,則采用搶占式模式來快速地切換執行。

        (2)線程的并行問題

        在單核單CPU的硬件架構上,線程的并行運行完全是用戶的主觀體驗。事實上,在任一時刻只可能存在一個處于運行狀態的線程。但在多CPU或多核的架構上,情況則略有不同。多CPU多核的架構則允許系統完全并行地運行兩個或多個無其他資源爭用的線程,理論上這樣的架構可以使運行性能整數倍地提高。

      PS:微軟公司曾經提出超線程技術,簡單說來這是一種邏輯上模擬多CPU的技術,但實際上它們卻共享物理處理器和緩存,超線程對性能的提高相當有限。

      1.3 神馬是纖程?

        (1)纖程的概念

        纖程是微軟公司在Windows上提出的一個概念,其設計目的是用來方便地移植其他操作系統上的應用程序。一個線程可以擁有0個或多個纖程,一個纖程可以視為一個輕量級的線程,它擁有自己的棧和上下文狀態。But,纖程的調度是由程序員編碼控制的,當一個纖程所在線程得到運行時,程序員需要手動地決定運行哪一個纖程

      PS:事實上,Windows操作系統內核是不知道纖程的存在的,它只負責調度所有的線程,而纖程之所以成為操作系統的概念,是因為Windows提供了關于線程操作的Win32函數,能夠方便地幫助程序員進行線程編程。

        (2)纖程和線程的區別

        纖程和線程最大的區別在于:線程的調度受操作系統的管理,程序員無法進行完全干涉。但纖程卻完全受控于程序員本身,允許程序員對多任務進行自定義的調度和控制,因此纖程帶給程序員很大的靈活性。

        下圖展示了進程、線程以及纖程三者之間的關系:

        (3)纖程在.NET中的地位

        需要謹記是的一點是:.NET運行框架沒有做出關于線程真實性的保證!也就是說,我們在.NET程序中新建的線程并不一定是操作系統層面上產生的一個真正線程。在.NET框架寄宿的情況下,一個程序中的線程很可能對應某個纖程

      PS:所謂CLR寄宿,就是指CLR運行在某個應用程序而非操作系統內。常見的寄宿例子是微軟公司的SQL Server 2005。

      二、.NET中的多線程編程

        .NET為多線程編程提供了豐富的類型和機制,程序員需要做的就是掌握這些類型和機制的使用方法和運行原理。

      2.1 如何在.NET程序中手動控制多個線程?

        .NET中提供了多種實現多線程程序的方法,但最直接且靈活性最大的,莫過于主動創建、運行、結束所有線程。

        (1)第一個多線程程序

        .NET提供了非常直接的控制線程類型的類型:System.Threading.Thread類。使用該類型可以直觀地創建、控制和結束線程。下面是一個簡單的多線程程序:

          class Program
          {
              static void Main(string[] args)
              {
                  Console.WriteLine("進入多線程工作模式:");
                  for (int i = 0; i < 10; i++)
                  {
                      Thread newThread = new Thread(Work);
                      // 開啟新線程
                      newThread.Start();
                  }
      
                  Console.ReadKey();
              }
      
              static void Work()
              {
                  Console.WriteLine("線程開始");
                  // 模擬做了一些工作,耗費1s時間
                  Thread.Sleep(1000);
                  Console.WriteLine("線程結束");
              }
          }
      View Code

        在主線程中,該代碼創建了10個新的線程,這個10個線程的工作互不干擾,宏觀上來看它們應該是并行運行的,執行的結果也證實了這一點:

        

      PS:這里再次強調一點,當new了一個Thread類型對象并不意味著生成了一個線程,事實上線程的生成是在調用Thread的Start方法的時候。另外在之前的介紹中,這里的線程并不一定是操作系統層面上產生的一個真正線程!

        (2)控制線程的狀態

        很多時候,我們需要主動關心線程當前所處的狀態。在任意時刻,.NET中的線程都會處于如下圖所示的幾個狀態中的某一個狀態上,該圖也直觀地展示了一個線程可能經過的狀態轉換過程(該圖并沒有列出所有的狀態轉換途徑/原因):

        下面的示例代碼則展示了我們如何手動地查看和控制一個線程的狀態:

          class Program
          {
              static void Main(string[] args)
              {
                  Console.WriteLine("開始測試線程1");
                  // 初始化一個線程 thread1
                  Thread thread1 = new Thread(Work1);
                  // 這時狀態:UnStarted
                  PrintState(thread1);
                  // 啟動線程
                  Console.WriteLine("現在啟動線程");
                  thread1.Start();
                  // 這時狀態:Running
                  PrintState(thread1);
                  // 讓線程飛一會 3s
                  Thread.Sleep(3 * 1000);
                  // 讓線程掛起
                  Console.WriteLine("現在掛起線程");
                  thread1.Suspend();
                  // 給線程足夠的時間來掛起,否則狀態可能是SuspendRequested
                  Thread.Sleep(1000);
                  // 這時狀態:Suspend
                  PrintState(thread1);
                  // 繼續線程
                  Console.WriteLine("現在繼續線程");
                  thread1.Resume();
                  // 這時狀態:Running
                  PrintState(thread1);
                  // 停止線程
                  Console.WriteLine("現在停止線程");
                  thread1.Abort();
                  // 給線程足夠的時間來終止,否則的話可能是AbortRequested
                  Thread.Sleep(1000);
                  // 這時狀態:Stopped
                  PrintState(thread1);
                  Console.WriteLine("------------------------------");
                  Console.WriteLine("開始測試線程2");
                  // 初始化一個線程 thread2
                  Thread thread2 = new Thread(Work2);
                  // 這時狀態:UnStarted
                  PrintState(thread2);
                  // 啟動線程
                  thread2.Start();
                  Thread.Sleep(2 * 1000);
                  // 這時狀態:WaitSleepJoin
                  PrintState(thread2);
                  // 給線程足夠的時間結束
                  Thread.Sleep(10 * 1000);
                  // 這時狀態:Stopped
                  PrintState(thread2);
      
                  Console.ReadKey();
              }
      
              // 普通線程方法:一直在運行從未被超越
              private static void Work1()
              {
                  Console.WriteLine("線程運行中...");
                  // 模擬線程運行,但不改變線程狀態
                  // 采用忙等狀態
                  while (true) { }
              }
      
              // 文藝線程方法:運行10s就結束
              private static void Work2()
              {
                  Console.WriteLine("線程開始睡眠:");
                  // 睡眠10s
                  Thread.Sleep(10 * 1000);
                  Console.WriteLine("線程恢復運行");
              }
      
              // 打印線程的狀態
              private static void PrintState(Thread thread)
              {
                  Console.WriteLine("線程的狀態是:{0}", thread.ThreadState.ToString());
              }
          }
      View Code

        上述代碼的執行結果如下圖所示:

      PS:為了演示方便,上述代碼刻意地使線程處于各個狀態并打印出來。在.NET Framework 4.0 及之后的版本中,已經不再鼓勵使用線程的掛起狀態,以及Suspend和Resume方法了。

      2.2 如何使用.NET中的線程池?

        (1).NET中的線程池是神馬

        我們都知道,線程的創建和銷毀需要很大的性能開銷,在Windows NT內核的操作系統中,每個進程都會包含一個線程池。而在.NET中呢,也有自己的線程池,它是由CLR負責管理的。

        線程池相當于一個緩存的概念,在該池中已經存在了一些沒有被銷毀的線程,而當應用程序需要一個新的線程時,就可以從線程池中直接獲取一個已經存在的線程。相對應的,當一個線程被使用完畢后并不會立刻被銷毀,而是放入線程池中等待下一次使用

        .NET中的線程池由CLR管理,管理的策略是靈活可變的,因此線程池中的線程數量也是可變的,使用者只需向線程池提交需求即可,下圖則直觀地展示了CLR是如何處理線程池需求的:

      PS:線程池中運行的線程均為后臺線程(即線程的 IsBackground 屬性被設為true),所謂的后臺線程是指這些線程的運行不會阻礙應用程序的結束。相反的,應用程序的結束則必須等待所有前臺線程結束后才能退出。

        (2)在.NET中使用線程池

        在.NET中通過 System.Threading.ThreadPool 類型來提供關于線程池的操作,ThreadPool 類型提供了幾個靜態方法,來允許使用者插入一個工作線程的需求。常用的有以下三個靜態方法:

        ① static bool QueueUserWorkItem(WaitCallback callback)

        ② static bool QueueUserWorkItem(WaitCallback callback, Object state)

        ③ static bool UnsafeQueueUserWorkItem(WaitCallback callback, Object state)

        有了這幾個方法,我們只需要將線程要處理的方法作為參數傳入上述方法即可,隨后的工作都由CLR的線程池管理程序來完成。其中,WaitCallback 是一個委托類型,該委托方法接受一個Object類型的參數,并且沒有返回值。下面的代碼展示了如何使用線程池來編寫多線程的程序:

          class Program
          {
              static void Main(string[] args)
              {
                  string taskInfo = "運行10秒";
                  // 插入一個新的請求到線程池
                  bool result = ThreadPool.QueueUserWorkItem(DoWork, taskInfo);
                  // 分配線程有可能會失敗
                  if (!result)
                  {
                      Console.WriteLine("分配線程失敗");
                  }
                  else
                  {
                      Console.WriteLine("按回車鍵結束程序");
                  }
      
                  Console.ReadKey();
              }
      
              private static void DoWork(object state)
              {
                  // 模擬做了一些操作,耗時10s
                  for (int i = 0; i < 10; i++)
                  {
                      Console.WriteLine("工作者線程的任務是:{0}", state);
                      Thread.Sleep(1000);
                  }
              }
          }
      View Code

        上述代碼執行后,如果不輸入任何字符,那么會得到如下圖所示的執行結果:

      PS:事實上,UnsafeQueueWorkItem方法實現了完全相同的功能,二者的差別在于UnsafeQueueWorkItem方法不會將調用線程的堆棧傳遞給輔助線程,這就意味著主線程的權限限制不會傳遞給輔助線程。UnsafeQueueWorkItem由于不進行這樣的傳遞,因此會得到更高的運行效率,但是潛在地提升了輔助線程的權限,也就有可能會成為一個潛在的安全漏洞。

      2.3 如何查看和設置線程池的上下限?

        線程池的線程數是有限制的,通常情況下,我們無需修改默認的配置。但在一些場合,我們可能需要了解線程池的上下限和剩余的線程數。線程池作為一個緩沖池,有著其上下限。在通常情況下,當線程池中的線程數小于線程池設置的下限時,線程池會設法創建新的線程,而當線程池中的線程數大于線程池設置的上限時,線程池將銷毀多余的線程

      PS:在.NET Framework 4.0中,每個CPU默認的工作者線程數量最大值為250個,最小值為2個。而IO線程的默認最大值為1000個,最小值為2個。

        在.NET中,通過 ThreadPool 類型提供的5個靜態方法可以獲取和設置線程池的上限和下限,同時它還額外地提供了一個方法來讓程序員獲知當前可用的線程數量,下面是這五個方法的簽名:

        ① static void GetMaxThreads(out int workerThreads, out int completionPortThreads)

        ② static void GetMinThreads(out int workerThreads, out int completionPortThreads)

        ③ static bool SetMaxThreads(int workerThreads, int completionPortThreads)

        ④ static bool SetMinThreads(int workerThreads, int completionPortThreads)

        ⑤ static void GetAvailableThreads(out int workerThreads, out int completionPortThreads)

        下面的代碼示例演示了如何查詢線程池的上下限閾值和可用線程數量:

          class Program
          {
              static void Main(string[] args)
              {
                  // 打印閾值和可用數量
                  GetLimitation();
                  GetAvailable();
      
                  // 使用掉其中三個線程
                  Console.WriteLine("此處申請使用3個線程...");
                  ThreadPool.QueueUserWorkItem(Work);
                  ThreadPool.QueueUserWorkItem(Work);
                  ThreadPool.QueueUserWorkItem(Work);
      
                  Thread.Sleep(1000);
      
                  // 打印閾值和可用數量
                  GetLimitation();
                  GetAvailable();
                  // 設置最小值
                  Console.WriteLine("此處修改了線程池的最小線程數量");
                  ThreadPool.SetMinThreads(10, 10);
                  // 打印閾值
                  GetLimitation();
      
                  Console.ReadKey();
              }
      
      
              // 運行10s的方法
              private static void Work(object o)
              {
                  Thread.Sleep(10 * 1000);
              }
      
              // 打印線程池的上下限閾值
              private static void GetLimitation()
              {
                  int maxWork, minWork, maxIO, minIO;
                  // 得到閾值上限
                  ThreadPool.GetMaxThreads(out maxWork, out maxIO);
                  // 得到閾值下限
                  ThreadPool.GetMinThreads(out minWork, out minIO);
                  // 打印閾值上限
                  Console.WriteLine("線程池最多有{0}個工作者線程,{1}個IO線程", maxWork.ToString(), maxIO.ToString());
                  // 打印閾值下限
                  Console.WriteLine("線程池最少有{0}個工作者線程,{1}個IO線程", minWork.ToString(), minIO.ToString());
                  Console.WriteLine("------------------------------------");
              }
      
              // 打印可用線程數量
              private static void GetAvailable()
              {
                  int remainWork, remainIO;
                  // 得到當前可用線程數量
                  ThreadPool.GetAvailableThreads(out remainWork, out remainIO);
                  // 打印可用線程數量
                  Console.WriteLine("線程池中當前有{0}個工作者線程可用,{1}個IO線程可用", remainWork.ToString(), remainIO.ToString());
                  Console.WriteLine("------------------------------------");
              }
          }
      View Code

        該實例的執行結果如下圖所示:

      PS:上面代碼示例在不同的計算機上運行可能會得到不同的結果,線程池中的可用數碼不會再初始時達到最大值,事實上CLR會嘗試以一定的時間間隔來逐一地創建新線程,但這個時間間隔非常短。

      2.4 如何定義線程獨享的全局數據?

        線程和進程最大的一個區別就在于線程間可以共享數據和資源,而進程則充分地隔離。在很多場合,即使同一進程的多個線程之間擁有相同的內存空間,也需要在邏輯上為某些線程分配獨享的數據。例如,在實際開發中往往會針對一些ORM如EF一類的上下文實體做線程內唯一實例的設置,這時就需要用到下面提到的技術。

        (1)線程本地存儲(Thread Local Storage,TLS)

        很多時候,程序員可能會希望擁有線程內可見的變量,而不希望其他線程對其進行訪問和修改(傳統方式中的靜態變量是對整個應用程序域可見的),這就需要用到TLS的概念。所謂的線程本地存儲(TLS)是指存儲在線程環境塊內的一個結構,用來存放該線程內獨享的數據。進程內的線程不能訪問不屬于自己的TLS,這就保證了TLS內的數據在線程內是全局共享的,而對于線程外確實不可見的

        (2)定義和使用TLS變量

        在.NET中提供了下列連個方法來存取線程獨享的數據,它們都定義在System.Threading.Thread類型中:

        ① object GetData(LocalDataStoreSlot slot)

        ② void SetData(LocalDataStoreSlot slot, object data)

        下面的代碼示例則展示了這個機制的使用方法:

          class Program
          {
              static void Main(string[] args)
              {
                  Console.WriteLine("開始測試數據插槽:");
                  // 創建五個線程來同時運行,但是這里不適合用線程池,
                  // 因為線程池內的線程會被反復使用導致線程ID一致
                  for (int i = 0; i < 5; i++)
                  {
                      Thread thread = new Thread(ThreadDataSlot.Work);
                      thread.Start();
                  }
      
                  Console.ReadKey();
              }
          }
      
          /// <summary>
          /// 包含線程方法和數據插槽
          /// </summary>
          public class ThreadDataSlot
          {
              // 分配一個數據插槽,注意插槽本身是全局可見的,因為這里的分配是在所有線程
              // 的TLS內創建數據塊
              private static LocalDataStoreSlot localSlot = Thread.AllocateDataSlot();
      
              // 線程要執行的方法,操作數據插槽來存放數據
              public static void Work()
              {
                  // 將線程ID注冊到數據插槽中,一個應用程序內線程ID不會重復
                  Thread.SetData(localSlot, Thread.CurrentThread.ManagedThreadId);
                  // 查看一下剛剛插入的數據
                  Console.WriteLine("線程{0}內的數據是:{1}",Thread.CurrentThread.ManagedThreadId.ToString(),Thread.GetData(localSlot).ToString());
                  // 這里線程休眠1秒
                  Thread.Sleep(1000);
                  // 查看其他線程的運行是否干擾了當前線程數據插槽內的數據
                  Console.WriteLine("線程{0}內的數據是:{1}", Thread.CurrentThread.ManagedThreadId.ToString(), Thread.GetData(localSlot).ToString());
              }
          }
      View Code

        該實例的執行結果如下圖所示,從下圖可以看出多線程的并行運行并沒有破壞每個線程插槽內的數據,這就是TLS所提供的功能。

            

      PS:LocalDataStoreSlot對象本身并不是線程共享的,初始化一個LocalDataStoreSlot對象意味著在應用程序域內的每個線程上都分配了一個數據插槽。

        (3)ThreadStaticAttribute特性的使用

        除了使用上面說到的數據槽之外,我們還有另一種方式,即ThreadStaticAttribute特性。申明了該特性的變量,會被.NET作為線程獨享的數據來使用。我們可以將其理解為一種被.NET封裝了的TLS機制,本質上,它仍然使用了線程環境塊來存放數據

        下面的示例代碼展示了ThreadStaticAttribute特性的使用:

          class Program
          {
              static void Main(string[] args)
              {
                  Console.WriteLine("開始測試數據插槽:");
                  // 創建五個線程來同時運行,但是這里不適合用線程池,
                  // 因為線程池內的線程會被反復使用導致線程ID一致
                  for (int i = 0; i < 5; i++)
                  {
                      Thread thread = new Thread(ThreadStatic.Work);
                      thread.Start();
                  }
      
                  Console.ReadKey();
              }
          }
      
          /// <summary>
          /// 包含線程靜態數據
          /// </summary>
          public class ThreadStatic
          {
              // 值類型的線程靜態數據
              [ThreadStatic]
              private static int threadId = 0;
              // 引用類型的線程靜態數據
              private static Ref refThreadId = new Ref();
      
              /// <summary>
              /// 線程執行的方法,操作線程靜態數據
              /// </summary>
              public static void Work()
              {
                  // 存儲線程ID,一個應用程序域內線程ID不會重復
                  threadId = Thread.CurrentThread.ManagedThreadId;
                  refThreadId.Id = Thread.CurrentThread.ManagedThreadId;
                  // 查看一下剛剛插入的數據
                  Console.WriteLine("[線程{0}]:線程靜態值變量:{1},線程靜態引用變量:{2}", Thread.CurrentThread.ManagedThreadId.ToString(), threadId, refThreadId.Id.ToString());
                  // 睡眠1s
                  Thread.Sleep(1000);
                  // 查看其他線程的運行是否干擾了當前線程靜態數據
                  Console.WriteLine("[線程{0}]:線程靜態值變量:{1},線程靜態引用變量:{2}", Thread.CurrentThread.ManagedThreadId.ToString(), threadId, refThreadId.Id.ToString());
              }
          }
      
          /// <summary>
          /// 簡單引用類型
          /// </summary>
          public class Ref
          {
              private int id;
      
              public int Id
              {
                  get
                  {
                      return id;
                  }
                  set
                  {
                      id = value;
                  }
              }
          }
      View Code

        該實例的執行結果如下圖所示,正如我們所看到的,對于使用了ThreadStatic特性的字段,.NET會將其作為線程獨享的數據來處理,當某個線程對一個使用了ThreadStatic特性的字段進行賦值后,這個值只有這個線程自己可以看到并訪問修改,該值對于其他線程時不可見的。相反,沒有標記該特性的,則會被多個線程所共享。

        

      2.5 如何使用異步模式讀取一個文件?

        異步模式是在處理流類型時經常采用的一種方式,其應用的領域相當廣闊,包括讀寫文件、網絡傳輸、讀寫數據庫,甚至可以采用異步模式來做任何計算工作。相對于手動編寫線程代碼,異步模式是一個高效的編程模式。

        (1)所謂異步模式是個什么鬼?

        所謂的異步模式,是指在啟動一個操作之后可以繼續執行其他工作而不會發生阻塞。以讀取文件為例,在同步模式下,當程序執行到Read方法時,需要等到讀取動作結束后才能繼續往下執行。而異步模式則可以簡單地通知開始讀取任務之后,繼續其他的操作。 異步模式的優點就在于不需要使當前線程等待,而可以充分地利用CPU時間。

      PS:異步模式區別于線程池機制的地方在于其允許程序查看操作的執行狀態,而如果利用線程池的后臺線程,則無法確切地知道操作的進行狀態以及其是否已經結束。

        使用異步模式可以通過一些異步聚集技巧來查看異步操作的結果,所謂的聚集技巧是指查看操作是否結束的方法,常用的方式是:在調用BeingXXX方法時傳入操作結束后需要執行的方法(又稱為回調方法),同時把執行異步操作的對象傳入以便執行EndXXX方法

        (2)使用異步模式讀取一個文件

        下面的示例代碼中:

        ① 主線程中負責開始異步讀取并傳入聚集時需要使用的方法和狀態對象:

          partial class Program
          {
              // 測試文件
              private const string testFile = @"C:\AsyncReadTest.txt";
              private const int bufferSize = 1024;
      
              static void Main(string[] args)
              {
                  // 刪除已存在文件
                  if (File.Exists(testFile))
                  {
                      File.Delete(testFile);
                  }
      
                  // 寫入一些東西以便后面讀取
                  using (FileStream stream = File.Create(testFile))
                  {
                      string content = "我是文件具體內容,我是不是帥得掉渣?";
                      byte[] contentByte = Encoding.UTF8.GetBytes(content);
                      stream.Write(contentByte, 0, contentByte.Length);
                  }
      
                  // 開始異步讀取文件具體內容
                  using (FileStream stream = new FileStream(testFile, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.Asynchronous))
                  {
                      byte[] data = new byte[bufferSize];
                      // 將自定義類型對象實例作為參數
                      ReadFileClass rfc = new ReadFileClass(stream, data);
                      // 開始異步讀取
                      IAsyncResult result = stream.BeginRead(data, 0, data.Length, FinshCallBack, rfc);
                      // 模擬做了一些其他的操作
                      Thread.Sleep(3 * 1000);
                      Console.WriteLine("主線程執行完畢,按回車鍵退出程序");
                  }
      
                  Console.ReadKey();
              }
          }
      View Code

        ② 定義了完成異步操作讀取之后需要調用的方法,其邏輯是簡單地打印出文件的內容:

          partial class Program
          {
              /// <summary>
              /// 完成異步操作后的回調方法
              /// </summary>
              /// <param name="result">狀態對象</param>
              private static void FinshCallBack(IAsyncResult result)
              {
                  ReadFileClass rfc = result.AsyncState as ReadFileClass;
                  if (rfc != null)
                  {
                      // 必須的步驟:讓異步讀取占用的資源被釋放掉
                      int length = rfc.stream.EndRead(result);
                      // 獲取讀取到的文件內容
                      byte[] fileData = new byte[length];
                      Array.Copy(rfc.data, 0, fileData, 0, fileData.Length);
                      string content = Encoding.UTF8.GetString(fileData);
                      // 打印讀取到的文件基本信息
                      Console.WriteLine("讀取文件結束:文件長度為[{0}],文件內容為[{1}]", length.ToString(), content);
                  }
              }
          }
      View Code

        ③ 定義了作為狀態對象傳遞的類型,這個類型對所有需要傳遞的數據包進行打包:

          /// <summary>
          /// 傳遞給異步操作的回調方法
          /// </summary>
          public class ReadFileClass
          {
              // 以便回調方法中釋放異步讀取的文件流
              public FileStream stream;
              // 文件內容
              public byte[] data;
      
              public ReadFileClass(FileStream stream,byte[] data)
              {
                  this.stream = stream;
                  this.data = data;
              }
          }
      View Code

        下圖展示了該實例的執行結果:

        如上面的實例,使用回調方法的異步模式需要花費一點額外的代碼量,因為它需要將異步操作的對象及操作的結果數據都打包到一個類型里以便能夠傳遞回給回調的委托方法,這樣在委托方法中才能夠有機會處理操作的結果,并且調用EndXXX方法以釋放資源。

      2.6 如何阻止線程執行上下文的傳遞?

        (1)何為線程的執行上下文

        在.NET中,每一個線程都會包含一個執行上下文,執行上下文是指線程運行中某時刻的上下文概念,類似于一個動態過程的快照(SnapShot)。在.NET中,System.Threading中的ExecutionContext類型代表了一個執行上下文,該執行上下文會包含:安全上下文、調用上下文、本地化上下文、事務上下文和CLR宿主上下文等等。通常情況下,我們將所有這些綜合成為線程的上下文。

        (2)執行上下文的流動

        當程序中新建一個線程時,執行上下文會自動地從當前線程流入到新建的線程之中,這樣做可以保證新建的線程天生就就有和主線程相同的安全設置和文化等設置。下面的示例代碼通過修改安全上下文來展示線程上下文的流動性,主要使用到ExecutionContext類的Capture方法來捕獲當前想成的執行上下文。

        ① 首先定義一些輔助犯法,封裝了文件的創建、刪除和文件訪問權限檢查:

          partial class Program
          {
              private static void CreateTestFile()
              {
                  if (!File.Exists(testFile))
                  {
                      FileStream stream = File.Create(testFile);
                      stream.Dispose();
                  }
              }
      
              private static void DeleteTestFile()
              {
                  if (File.Exists(testFile))
                  {
                      File.Delete(testFile);
                  }
              }
      
              // 嘗試訪問測試文件來測試安全上下文
              private static void JudgePermission(object state)
              {
                  try
                  {
                      // 嘗試訪問文件
                      File.GetCreationTime(testFile);
                      // 如果沒有異常則測試通過
                      Console.WriteLine("權限測試通過");
                  }
                  catch (SecurityException)
                  {
                      // 如果出現異常則測試通過
                      Console.WriteLine("權限測試沒有通過");
                  }
                  finally
                  {
                      Console.WriteLine("------------------------");
                  }
              }
          }
      View Code

        ② 其次在入口方法中使主線程和創建的子線程訪問指定文件來查看權限上下文流動到子線程中的情況:(這里需要注意的是由于在.NET 4.0及以上版本中FileIOPermission的Deny方法已過時,為了方便測試,將程序的.NET版本調整為了3.5)

          partial class Program
          {
              private const string testFile = @"C:\TestContext.txt";
      
              static void Main(string[] args)
              {
                  try
                  {
                      CreateTestFile();
                      // 測試當前線程的安全上下文
                      Console.WriteLine("主線程權限測試:");
                      JudgePermission(null);
                      // 創建一個子線程 subThread1
                      Console.WriteLine("子線程權限測試:");
                      Thread subThread1 = new Thread(JudgePermission);
                      subThread1.Start();
                      subThread1.Join();
                      // 現在修改安全上下文,阻止文件訪問
                      FileIOPermission fip = new FileIOPermission(FileIOPermissionAccess.AllAccess, testFile);
                      fip.Deny();
                      Console.WriteLine("已成功阻止文件訪問");
                      // 測試當前線程的安全上下文
                      Console.WriteLine("主線程權限測試:");
                      JudgePermission(null);
                      // 創建一個子線程 subThread2
                      Console.WriteLine("子線程權限測試:");
                      Thread subThread2 = new Thread(JudgePermission);
                      subThread2.Start();
                      subThread2.Join();
                      // 現在修改安全上下文,允許文件訪問
                      SecurityPermission.RevertDeny();
                      Console.WriteLine("已成功恢復文件訪問");
                      // 測試當前線程安全上下文
                      Console.WriteLine("主線程權限測試:");
                      JudgePermission(null);
                      // 創建一個子線程 subThread3
                      Console.WriteLine("子線程權限測試:");
                      Thread subThread3 = new Thread(JudgePermission);
                      subThread3.Start();
                      subThread3.Join();
      
                      Console.ReadKey();
                  }
                  finally
                  {
                      DeleteTestFile();
                  }
              }
          }
      View Code

        該實例的執行結果如下圖所示,從圖中可以看出程序中通過FileIOPermission對象來控制對主線程對文件的訪問權限,并且通過新建子線程來查看主線程的安全上下文的改變是否會影響到子線程。

            

        正如剛剛說到,主線程的安全上下文將作為執行上下文的一部分由主線程傳遞給子線程。

        (3)阻止上下文的流動

        有的時候,系統需要子線程擁有新的上下文。拋開功能上的需求,執行上下文的流動確實使得程序的執行效率下降很多,線程上下文的包裝是一個成本較高的工作,而有的時候這樣的包裝并不是必須的。在這種情況下,我們如果需要手動地防止線程上下文的流動,常用的有下列兩種方法:

        ① System.Threading.ThreadPool類中的UnsafeQueueUserWorkItem方法

        ② ExecutionContext類中的SuppressFlow方法

        下面的代碼示例展示了如何使用上面兩種方法阻止執行上下文的流動:

          partial class Program
          {
              private const string testFile = @"C:\TestContext.txt";
      
              static void Main(string[] args)
              {
                  try
                  {
                      CreateTestFile();
                      // 現在修改安全上下文,阻止文件訪問
                      FileIOPermission fip = new FileIOPermission(FileIOPermissionAccess.AllAccess, testFile);
                      fip.Deny();
                      Console.WriteLine("已成功阻止文件訪問");
                      // 主線程權限測試
                      Console.WriteLine("主線程權限測試:");
                      JudgePermission(null);
                      // 使用UnsafeQueueUserWorkItem方法創建一個子線程
                      Console.WriteLine("子線程權限測試:");
                      ThreadPool.UnsafeQueueUserWorkItem(JudgePermission, null);
      
                      Thread.Sleep(1000);
      
                      // 使用SuppressFlow方法
                      using (var afc = ExecutionContext.SuppressFlow())
                      {
                          // 測試當前線程安全上下文
                          Console.WriteLine("主線程權限測試:");
                          JudgePermission(null);
                          // 創建一個子線程 subThread1
                          Console.WriteLine("子線程權限測試:");
                          Thread subThread1 = new Thread(JudgePermission);
                          subThread1.Start();
                          subThread1.Join();
                      }
      
                      // 現在修改安全上下文,允許文件訪問
                      SecurityPermission.RevertDeny();
                      Console.WriteLine("已成功恢復文件訪問");
                      // 測試當前線程安全上下文
                      Console.WriteLine("主線程權限測試:");
                      JudgePermission(null);
                      // 創建一個子線程 subThread2
                      Console.WriteLine("子線程權限測試:");
                      Thread subThread2 = new Thread(JudgePermission);
                      subThread2.Start();
                      subThread2.Join();
      
                      Console.ReadKey();
                  }
                  finally
                  {
                      DeleteTestFile();
                  }
              }
          }
      View Code

        該實例的執行結果如下圖所示,可以看出,通過前面的兩種方式有效地阻止了主線程的執行上下文流動到新建的線程之中,這樣的機制對于性能的提高有一定的幫助。

        

      三、多線程編程中的線程同步

      3.1 理解同步塊和同步塊索引

        同步塊是.NET中解決對象同步問題的基本機制,該機制為每個堆內的對象(即引用類型對象實例)分配一個同步索引,該索引中只保存一個表明數組內索引的整數。具體過程是:.NET在加載時就會新建一個同步塊數組,當某個對象需要被同步時,.NET會為其分配一個同步塊,并且把該同步塊在同步塊數組中的索引加入該對象的同步塊索引中。下圖展現了這一機制的實現:

        同步塊機制包含以下幾點:

        ① 在.NET被加載時初始化同步塊數組;

        ② 每一個被分配在堆上的對象都會包含兩個額外的字段,其中一個存儲類型指針,而另外一個就是同步塊索引,初始時被賦值為-1;

        ③ 當一個線程試圖使用該對象進入同步時,會檢查該對象的同步索引:

          如果同步索引為負數,則會在同步塊數組中新建一個同步塊,并且將該同步塊的索引值寫入該對象的同步索引中;

          如果同步索引不為負數,則找到該對象的同步塊并檢查是否有其他線程在使用該同步塊,如果有則進入等待狀態,如果沒有則申明使用該同步塊;

        ④ 當一個對象退出同步時,該對象的同步索引被修改為-1,并且相應的同步塊數組中的同步塊被視為不再使用。

      3.2 C#中的lock關鍵字有啥作用?

        lock關鍵字可能是我們在遇到線程同步的需求時最常用的方式,但lock只是一個語法糖,為什么這么說呢,下面慢慢道來。

        (1)lock的等效代碼其實是Monitor類的Enter和Exit兩個方法

          private object locker = new object();
          public void Work()
          {
                lock (locker)
                {
                    // 做一些需要線程同步的工作
                }
           }

        事實上,lock關鍵字時一個方便程序員使用的語法糖,它等效于安全地使用System.Threading.Monitor類型,它直接等效于下面的代碼:

          private object locker = new object();
          public void Work()
          {
              // 避免直接使用私有成員locker(直接使用有可能會導致線程不安全)
              object temp = locker;
              Monitor.Enter(temp);
              try
              {
                  // 做一些需要線程同步的工作
              }
              finally
              {
                  Monitor.Exit(temp);
              }
          }

        (2)System.Threading.Monitor類型的作用和使用

        Monitor類型的Enter和Exit方法用來實現進入和退出對象的同步,當Enter方法被調用時,對象的同步索引將被檢查,并且.NET將負責一系列的后續工作來保證對象訪問時的線程同步,而Exit方法的調用則保證了當前線程釋放該對象的同步塊。

        下面的代碼示例演示了如何使用lock關鍵字來實現線程同步:

          class Program
          {
              static void Main(string[] args)
              {
                  // 多線程測試靜態方法的同步
                  Console.WriteLine("開始測試靜態方法的同步:");
                  for (int i = 0; i < 5; i++)
                  {
                      Thread thread = new Thread(Lock.StaticIncrement);
                      thread.Start();
                  }
                  // 這里等待線程執行結束
                  Thread.Sleep(5 * 1000);
                  Console.WriteLine("-------------------------------");
                  // 多線程測試實例方法的同步
                  Console.WriteLine("開始測試實例方法的同步:");
                  Lock l = new Lock();
                  for (int i = 0; i < 6; i++)
                  {
                      Thread thread = new Thread(l.InstanceIncrement);
                      thread.Start();
                  }
      
                  Console.ReadKey();
              }
          }
      
          public class Lock
          {
              // 靜態方法同步鎖
              private static object staticLocker = new object();
              // 實例方法同步鎖
              private object instanceLocker = new object();
      
              // 成員變量
              private static int staticNumber = 0;
              private int instanceNumber = 0;
      
              // 測試靜態方法的同步
              public static void StaticIncrement(object state)
              {
                  lock (staticLocker)
                  {
                      Console.WriteLine("當前線程ID:{0}", Thread.CurrentThread.ManagedThreadId.ToString());
                      Console.WriteLine("staticNumber的值為:{0}", staticNumber.ToString());
                      // 這里可以制造線程并行執行的機會,來檢查同步的功能
                      Thread.Sleep(200);
                      staticNumber++;
                      Console.WriteLine("staticNumber自增后為:{0}", staticNumber.ToString());
                  }
              }
      
              // 測試實例方法的同步
              public void InstanceIncrement(object state)
              {
                  lock (instanceLocker)
                  {
                      Console.WriteLine("當前線程ID:{0}",Thread.CurrentThread.ManagedThreadId.ToString());
                      Console.WriteLine("instanceNumber的值為:{0}", instanceNumber.ToString());
                      // 這里可以制造線程并行執行的機會,來檢查同步的功能
                      Thread.Sleep(200);
                      instanceNumber++;
                      Console.WriteLine("instanceNumber自增后為:{0}", instanceNumber.ToString());
                  }
              }
          }
      View Code

        下圖是該實例的執行結果:

        

      PS:線程同步本身違反了多線程并行運行的原則,所以我們在使用線程同步時應該盡量做到將lock加在最小的程序塊上。對于靜態方法的同步,一般采用靜態私有的引用對象成員,而對于實例方法的同步,一般采用私有的引用對象成員。

      3.3 可否使用值類型對象來實現線程同步嗎?

        前面已經說到,在.NET中每個堆內的對象都會有一個同步索引字段,用以指向同步塊的位置。但是,對于值類型來說,它們的對象是分配在堆棧上的,也就是說值類型是沒有同步索引這一字段的,所以直接使用值類型對象無法實現線程同步

        如果在程序中對于lock關鍵字使用了值類型對象,會直接導致一個編譯錯誤:

      3.4 可否使用引用類型對象自身進行同步?

        引用類型的對象是分配在堆上的,必然會包含同步索引,也可以分配同步塊,所以原則上可以在對象的方法內對自身進行同步。而事實上,這樣的代碼也確實能有效地保證線程同步。But,這樣的代碼健壯性存在一定問題。

        (1)lock(this)

        回顧lock(this)的設計,就可以看出問題來:this代表了執行代碼的當前對象,可以預見該對象可以被任何使用者訪問,這就導致了不僅對象內部的代碼在爭用同步塊,連類型的使用者也可以有意無意地進入到爭用的隊伍中→這顯然不符合設計意圖

        下面通過一個代碼示例展示了一個惡意的使用者是如何導致類型死鎖的:

          class Program
          {
              static void Main(string[] args)
              {
                  Console.WriteLine("開始使用");
                  SynchroThis st = new SynchroThis();
                  // 模擬惡意的使用者
                  Monitor.Enter(st);
                  // 正常的使用者會收到惡意使用者的影響
                  // 下面的代碼完全正確,但卻被死鎖
                  Thread thread = new Thread(st.Work);
                  thread.Start();
                  thread.Join();
                  // 程序不會執行到這里
                  Console.WriteLine("使用結束");
      
                  Console.ReadKey();
              }
          }
      
          public class SynchroThis
          {
              private int number = 0;
      
              public void Work(object state)
              {
                  lock (this)
                  {
                      Console.WriteLine("number現在的值為:{0}", number.ToString());
                      number++;
                      // 模擬做了其他工作
                      Thread.Sleep(200);
                      Console.WriteLine("number自增后值為:{0}", number.ToString());
                  }
              }
          }
      View Code

        運行這個示例,我們發現程序完全被死鎖,這是因為一個惡意的使用者在使用了同步塊之后卻沒有對其進行釋放,導致了SynchroThis類型的方法被組織。

        (2)lock(typeof(類型名))

        這樣的設計有時候會被用來在靜態方法中實現線程同步,因為靜態方法的訪問需要通過類型來進行,但它也和lock(this)一樣,缺乏健壯性。下面展示了常見的錯誤使用代碼示例:

          class Program
          {
              static void Main(string[] args)
              {
                  Console.WriteLine("開始使用");
                  SynchroThis st = new SynchroThis();
                  // 模擬惡意的使用者
                  Monitor.Enter(typeof(SynchroThis));
                  // 正常的使用者會收到惡意使用者的影響
                  // 下面的代碼完全正確,但卻被死鎖
                  Thread thread = new Thread(SynchroThis.Work);
                  thread.Start();
                  thread.Join();
                  // 程序不會執行到這里
                  Console.WriteLine("使用結束");
      
                  Console.ReadKey();
              }
          }
      
          public class SynchroThis
          {
              private static int number = 0;
      
              public static void Work(object state)
              {
                  lock (typeof(SynchroThis))
                  {
                      Console.WriteLine("number現在的值為:{0}", number.ToString());
                      number++;
                      // 模擬做了其他工作
                      Thread.Sleep(200);
                      Console.WriteLine("number自增后值為:{0}", number.ToString());
                  }
              }
          }
      View Code

        可以發現,當一個惡意的使用者對type對象進行同步時,也會造成所有的使用者被死鎖。

      PS:應該完全避免使用this對象和當前類型對象作為同步對象,而應該在類型中定義私有的同步對象,同時應該使用lock而不是Monitor類型,這樣可以有效地減少同步塊不被釋放的情況。

      3.5 互斥體是個什么鬼?Mutex和Monitor兩個類型的功能有啥區別?

        (1)什么是互斥體?

        在操作系統中,互斥體(Mutex)是指某些代碼片段在任意時間內只允許一個線程進入。例如,正在進行一盤棋,任意時刻只允許一個棋手往棋盤上落子,這和線程同步的概念基本一致。

        (2).NET中的互斥體

        Mutex類是.NET中為我們封裝的一個互斥體類型,和Mutex類似的還有Semaphore(信號量)等類型。下面的示例代碼展示了Mutext類型的使用:

          class Program
          {
              const string testFile = "C:\\TestMutex.txt";
              /// <summary>
              /// 這個互斥體保證所有的進程都能得到同步
              /// </summary>
              static Mutex mutex = new Mutex(false, "TestMutex");
      
              static void Main(string[] args)
              {
                  //留出時間來啟動其他進程
                  Thread.Sleep(3000);
                  DoWork();
                  mutex.Close();
                  Console.ReadKey();
              }
      
              /// <summary>
              /// 往文件里寫連續的內容
              /// </summary>
              static void DoWork()
              {
                  long d1 = DateTime.Now.Ticks;
                  mutex.WaitOne();
                  long d2 = DateTime.Now.Ticks;
                  Console.WriteLine("經過了{0}個Tick后進程{1}得到互斥體,進入臨界區代碼。", (d2 - d1).ToString(), Process.GetCurrentProcess().Id.ToString());
      
                  try
                  {
                      if (!File.Exists(testFile))
                      {
                          FileStream fs = File.Create(testFile);
                          fs.Dispose();
                      }
                      for (int i = 0; i < 5; i++)
                      {
                          // 每次都保證文件被關閉再重新打開
                          // 確定有mutex來同步,而不是IO機制
                          using (FileStream fs = File.Open(testFile, FileMode.Append))
                          {
                              string content = "【進程" + Process.GetCurrentProcess().Id.ToString() +
                                  "】:" + i.ToString() + "\r\n";
                              Byte[] data = Encoding.Default.GetBytes(content);
                              fs.Write(data, 0, data.Length);
                          }
                          // 模擬做了其他工作
                          Thread.Sleep(300);
                      }
                  }
                  finally
                  {
                      mutex.ReleaseMutex();
                  }
              }
          }
      View Code

        模擬多個用戶,執行上述代碼,下圖就是在我的計算機上的執行結果:

        現在打開C盤目錄下的TestMutext.txt文件,將看到如下圖所示的結果:

            

        (3)Mutex和Monitor的區別

        這兩者雖然都用來進行同步的功能,但實現方法不同,其最顯著的兩個差別如下:

        ① Mutex使用的是操作系統的內核對象,而Monitor類型的同步機制則完全在.NET框架之下實現,這就導致了Mutext類型的效率要比Monitor類型要低很多

        ② Monitor類型只能同步同一應用程序域中的線程,而Mutex類型卻可以跨越應用程序域和進程

      3.6 如何使用信號量Semaphore?

        這里首先借用阮一峰的《進程與線程的一個簡單解釋》中的介紹來說一下Mutex和Semaphore:

        一個防止他人進入的簡單方法,就是門口加一把鎖。先到的人鎖上門,后到的人看到上鎖,就在門口排隊,等鎖打開再進去。這就叫"互斥鎖"(Mutual exclusion,縮寫 Mutex),防止多個線程同時讀寫某一塊內存區域。

        還有些房間,可以同時容納n個人,比如廚房。也就是說,如果人數大于n,多出來的人只能在外面等著。這好比某些內存區域,只能供給固定數目的線程使用。

        這時的解決方法,就是在門口掛n把鑰匙。進去的人就取一把鑰匙,出來時再把鑰匙掛回原處。后到的人發現鑰匙架空了,就知道必須在門口排隊等著了。這種做法叫做"信號量"(Semaphore),用來保證多個線程不會互相沖突。

        不難看出,mutex是semaphore的一種特殊情況(n=1時)。也就是說,完全可以用后者替代前者。但是,因為mutex較為簡單,且效率高,所以在必須保證資源獨占的情況下,還是采用這種設計。

        現在我們知道了Semaphore是干啥的了,再把目光放到.NET中的Sempaphore上。Semaphore 繼承自WaitHandle(Mutex也繼承自WaitHandle),它用于鎖機制,與Mutex不同的是,它允許指定數量的線程同時訪問資源,在線程超過數量以后,則進行排隊等待,直到之前的線程退出。Semaphore很適合應用于Web服務器這樣的高并發場景,可以限制對資源訪問的線程數。此外,Sempaphore不需要一個鎖的持有者,通常也將Sempaphore聲明為靜態的。

        下面的示例代碼演示了4條線程想要同時執行ThreadEntry()方法,但同時只允許2條線程進入:

          class Program
          {
              // 第一個參數指定當前有多少個“空位”(允許多少條線程進入)
              // 第二個參數指定一共有多少個“座位”(最多允許多少個線程同時進入)
              static Semaphore sem = new Semaphore(2, 2);
      
              const int threadSize = 4;
      
              static void Main(string[] args)
              {
                  for (int i = 0; i < threadSize; i++)
                  {
                      Thread thread = new Thread(ThreadEntry);
                      thread.Start(i + 1);
                  }
      
                  Console.ReadKey();
              }
      
              static void ThreadEntry(object id)
              {
                  Console.WriteLine("線程{0}申請進入本方法", id);
                  // WaitOne:如果還有“空位”,則占位,如果沒有空位,則等待;
                  sem.WaitOne();
                  Console.WriteLine("線程{0}成功進入本方法", id);
                  // 模擬線程執行了一些操作
                  Thread.Sleep(100);
                  Console.WriteLine("線程{0}執行完畢離開了", id);
                  // Release:釋放一個“空位”
                  sem.Release();
              }
          }
      View Code

        上面示例的執行結果如下圖所示:

        

        如果將資源比作“座位”,Semaphore接收的兩個參數中:第一個參數指定當前有多少個“空位”(允許多少條線程進入),第二個參數則指定一共有多少個“座位”(最多允許多少個線程同時進入)。WaitOne()方法則表示如果還有“空位”,則占位,如果沒有空位,則等待;Release()方法則表示釋放一個“空位”。

      感嘆一下:人生中有很多人在你的城堡中進進出出,城中的人想出去,城外的人想沖進來。But,一個人身邊的位置只有那么多,你能給的也只有那么多,在這個狹小的圈子里,有些人要進來,就有一些人不得不離開

      參考資料

      (1)朱毅,《進入IT企業必讀的200個.NET面試題》

      (2)張子陽,《.NET之美:.NET關鍵技術深入解析》

      (3)王濤,《你必須知道的.NET》

      (4)阮一峰,《進程與線程的一個簡單解釋

       

      posted @ 2015-09-30 23:54  EdisonZhou  閱讀(40498)  評論(39)    收藏  舉報
      主站蜘蛛池模板: 久久99久久99精品免观看| 久章草这里只有精品| 乱码精品一区二区亚洲区| 99久久国产综合精品成人影院| 亚洲欧美综合人成在线| 亚洲欧美在线观看品| 少妇人妻偷人精品免费| 伊人色综合久久天天小片| 亚洲欧美日韩愉拍自拍美利坚| 欧洲码亚洲码的区别入口| 国色天香成人一区二区| 国产丰满乱子伦无码专区| 一面膜上边一面膜下边视频| 国产成人亚洲欧美二区综合| 日韩人妻无码精品无码中文字幕| 精品一区二区三区蜜桃久| 国内不卡一区二区三区| 久久亚洲国产精品久久| 日韩熟妇中文色在线视频| 日韩国产成人精品视频| 国产精品欧美亚洲韩国日本久久| 国产一区二区不卡在线视频| 国产日韩一区二区天美麻豆| 亚洲精品久久久中文字幕痴女| 欧洲性开放老太大| 九九久久亚洲精品美国国内| 国产精品多p对白交换绿帽| 青田县| 新和县| 久热视频这里只有精品6| 浮妇高潮喷白浆视频| 天天弄天天模| 人妻内射一区二区在线视频| 高清自拍亚洲精品二区 | 宝兴县| 亚洲AV无码秘?蜜桃蘑菇| 无码AV动漫精品一区二区免费| 人妻系列中文字幕精品| 男女爽爽无遮挡午夜视频| 又污又黄又无遮挡的网站| 国产精品第一页中文字幕|