多線程總結之旅(11):線程池的使用

  爲何使用線程池?  程序員

  在面向對象編程中,建立和銷燬對象是很費時間的,由於建立一個對象要獲取內存資源或者其它更多資源,因此提升服務程序效率的一個手段就是儘量減小建立和銷燬對象的次數,特別是一些很耗資源的對象建立和銷燬。如何利用已有對象來服務就是一個須要解決的關鍵問題,其實這就是一些"池化資源"技術產生的緣由。好比你們所熟悉的數據庫鏈接池正是遵循這一思想而產生的,本文將介紹的線程池技術一樣符合這一思想。算法


  多線程是什麼?組成?特色?數據庫

  線程池是一種多線程處理形式,處理過程當中將任務添加到隊列,而後在建立線程後自動啓動這些任務。線程池中的線程由系統管理,程序員不須要費力於線程管理,能夠集中精力處理應用程序任務。編程

   組成:服務器程序利用線程技術響應客戶請求已經司空見慣,可能您認爲這樣作效率已經很高,但您有沒有想過優化一下使用線程的方法。該文章將向您介紹服務器程序如何利用線程池來優化性能並提供一個簡單的線程池實現。
  一、線程池管理器(ThreadPoolManager):用於建立並管理線程池
  二、工做線程(WorkThread): 線程池中線程
  三、任務接口(Task):每一個任務必須實現的接口,以供工做線程調度任務的執行。
  四、任務隊列:用於存放沒有處理的任務。提供一種緩衝機制。
 

  特色:安全

  • 一個進程有且只有一個線程池。
  • 線程池線程都是後臺線程(即不會阻止進程的中止)
  • 每一個線程都使用默認堆棧大小,以默認的優先級運行,並處於多線程單元中。超過最大值的其餘線程須要排隊,但它們要等到其餘線程完成後才啓動。
  • 在CLR 2.0 SP1以前的版本中,線程池中 默認最大的線程數量 = 處理器數 * 25, CLR 2.0 SP1以後就變成了 默認最大線程數量 = 處理器數 * 250,線程上限能夠改變,經過使用ThreadPool.GetMax+Threads和ThreadPool.SetMaxThreads方法,能夠獲取和設置線程池的最大線程數。
  • 默認狀況下,每一個處理器維持一個空閒線程,即默認最小線程數 = 處理器數。
  • 當進程啓動時,線程池並不會自動建立。當第一次將回調方法排入隊列(好比調用ThreadPool.QueueUserWorkItem方法)時纔會建立線程池。
  • 在對一個工做項進行排隊以後將沒法取消它。
  • 線程池中線程在完成任務後並不會自動銷燬,它會以掛起的狀態返回線程池,若是應用程序再次向線程池發出請求,那麼這個掛起的線程將激活並執行任務,而不會建立新線程,這將節約了不少開銷。只有線程達到最大線程數量,系統纔會以必定的算法銷燬回收線程。

  何時使用線程池?  服務器

  一、須要大量的線程來完成任務,且完成任務的時間比較短。 WEB服務器完成網頁請求這樣的任務,使用線程池技術是很是合適的。由於單個任務小,而任務數量巨大,你能夠想象一個熱門網站的點擊次數。 但對於長時間的任務,好比一個Telnet鏈接請求,線程池的優勢就不明顯了。由於Telnet會話時間比線程的建立時間大多了。
  二、對性能要求苛刻的應用,好比要求服務器迅速響應客戶請求。
  三、接受突發性的大量請求,但不至於使服務器所以產生大量線程的應用。突發性大量客戶請求,在沒有線程池狀況下,將產生大量線程,雖然理論上大部分操做系統線程數目最大值不是問題,短期內產生大量線程可能使內存到達極限,並出現"OutOfMemory"的錯誤。
    
   何時不適合使用線程池?
  
  ●若是須要使一個任務具備特定優先級
  ●若是具備可能會長時間運行(並所以阻塞其餘任務)的任務
  ●若是須要將線程放置到 單線程單元中(線程池中的線程均處於多線程單元中)
  ●若是須要永久標識來標識和控制線程,好比想使用專用線程來終止該線程,將其掛起或按名稱發現它。

   線程池經常使用方法介紹?
  

  一、GetMaxThreads()    :  獲取能夠同時處於活動狀態的線程池請求的最大數目。全部大於此數目的請求將保持排隊狀態,直到線程池線程變爲可用。多線程

    •   函數原型:public static void GetMaxThreads (out int workerThreads,out int completionPortThreads)

                  參數1:workerThreads :線程池中輔助線程的最大數目。 
                  參數2:completionPortThreads :線程池中異步 I/O 線程的最大數目。 異步

 

  二、GetMinThreads()    獲取線程池維護的最小空閒線程數。        函數

    • 函數原型:public static void GetMinThreads (out int workerThreads,out int completionPortThreads)
      參數1:workerThreads:當前由線程池維護的空閒輔助線程的最小數目。 
      參數2:completionPortThreads:當前由線程池維護的空閒異步 I/O 線程的最小數目。 

 

  三、SetMaxThreads()    設置能夠同時處於活動狀態的線程池的最大請求數目(不考慮計算機處理器的數目)性能

    • 函數原型:public static bool SetMinThreads (int workerThreads,intcompletionPortThreads)

               參數1:workerThreads::要由線程池維護的新的最小空閒輔助線程數。 
               參數2:completionPortThreads::要由線程池維護的新的最小空閒異步 I/O 線程數。 
               返回值:若是更改爲功,則爲 true;不然爲 false。 

 

  四、SetMinThreads()     設置線程池在新請求預測中維護的空閒線程數(不考慮計算機處理器的數目)

    • 函數原型:public static bool SetMinThreads (int workerThreads,intcompletionPortThreads)

               參數1:workerThreads:要由線程池維護的新的最小空閒輔助線程數。 
               參數2:completionPortThreads:要由線程池維護的新的最小空閒異步 I/O 線程數。 
               返回值:若是更改爲功,則爲 true;不然爲 false。 

  

  五、GetAvailableThreads()    獲取由 GetMaxThreads 返回的線程池線程的最大數目和當前活動數目之間的差值。    

    • 函數原型:public static void GetAvailableThreads (out int workerThreads,out int completionPortThreads)

                參數1:workerThreads:可用輔助線程的數目。 
                參數2:completionPortThreads:可用異步 I/O 線程的數目。 

 

  六、QueueUserWorkItem()    將方法排入隊列以便執行。此方法在有線程池線程變得可用時執行。 

    • 重載方法1:public static bool QueueUserWorkItem (WaitCallback callBack)

                返回值:若是將方法成功排入隊列,則爲 true;不然爲 false。 

    • 重載方法2:public static bool QueueUserWorkItem (WaitCallback callBack,Object state)

               參數2:state :包含方法所用數據的對象。 
               返回值:若是將方法成功排入隊列,則爲 true;不然爲 false。 
      備註:WaitCallback 回調方法必須與System.Threading.WaitCallback委託類型相匹配。

         WaitCallback函數原型:public delegate void WaitCallback(Object state);調用QueueUserWorkItem能夠經過Object來向任務過程傳遞參數。若是任務過程須要多個參數,能夠定義包含這些數據的類,並將類的實例強制轉換爲Object數據類型。

 

  七、UnsafeQueueUserWorkItem()    非安全性註冊一個等待 WaitHandle 的委託(將方法排入隊列以便執行)。

    • 函數原型:public static bool UnsafeQueueUserWorkItem (WaitCallback callBack,Object state)      //不將調用堆棧傳播到輔助線程上。這容許代碼失去調用堆棧,從而提高了它的安全特權。
    • 備註:使用 UnsafeQueueUserWorkItem 可能會無心中打開一個安全漏洞。代碼訪問安全性的權限檢查基於全部調用方對堆棧的權限進行。若是使用 UnsafeQueueUserWorkItem 將工做排在某個線程池線程上,則該線程池線程的堆棧將不會具備實際調用方的背景。惡意代碼可能會利用這一點避開權限檢查。

 

  八、RegisterWaitForSingleObject()     將指定的委託排隊到線程池。當發生如下狀況之一時,輔助線程將執行委託。

  九、UnsafeRegisterWaitForSingleObject()   非安全性將指定的委託排隊到線程池。


  線程池示例?

  咱們先來看一個簡單的線程實例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Begin in Main");
            Thread t = new Thread(ThreadInvoke);
            t.IsBackground = true;
            t.Start();

            //將當前線程掛起200毫秒
            Thread.Sleep(200);
            Console.WriteLine("End in Main");
            Console.ReadKey();
        }

        static void ThreadInvoke(object obj)
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Execute in ThreadInvoke");
                //每隔100毫秒,循環一次
                Thread.Sleep(100);
            }
        }
    }
}

 

"End in Main"並無在ThreadInvoke()方法中全部代碼執行完以後才輸出。

因而可知Main方法和ThreadInvoke是並行執行的

 

  使用線程池改造上邊的示例:

    上面介紹了只是一個最簡單的有關線程線程的例子,但在實際開發中使用的線程每每是大量的和更爲複雜的,這時,每次都建立線程、啓動線程。從性能上來說,這樣作並不理想(由於每使用一個線程就要建立一個,須要佔用系統開銷);從操做上來說,每次都要啓動,比較麻煩。爲此引入的線程池的概念。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Begin in Main");
            ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadInvoke));
            Console.WriteLine("End in Main");
            //將當前線程掛起200毫秒
            Thread.Sleep(3000);
           
            Console.ReadKey();
        }

        static void ThreadInvoke(object obj)
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Execute in ThreadInvoke");
                //每隔100毫秒,循環一次
                Thread.Sleep(100);
            }
        }
    }
}

 

Thread.Sleep(3000)這句話是必須的由於當Main方法結束後,.Net環境會自動結束銷燬線程池,爲了保證完成線程池裏的任務,因此主線程須要等待一段時間。 

由輸出結果可知,Main方法和ThreadInvoke方法是並行執行的。

相關文章
相關標籤/搜索