C# 多線程學習系列三之CLR線程池系列之ThreadPool

一、CLR線程池

1、進程和CLR的關係 一個進程可以只包含一個CLR,也可以包含多個CLR 2、CLR和AppDomain的關係 一個CLR可以包含多個AppDomain 3、CLR和線程池的關係 一個CLR只包含一個線程池 所以得出一個CLR下的多個AppDomain共享一個線程池和一個進程下的多個CLR擁有多個線程池的結論.注:多個線程池間的線程池相互不產生影響.

4、CLR和線程池和操作請求隊列的關係 (1)、CLR第一次初始化時,線程池並沒有線程,當應用程序調用異步代碼執行一個方法時,會將該請求記錄項加入到操作請求隊列中,線程池的代碼從這個隊列中獲取記錄項,並派發給線程池線程,接着 線程池會創建線程,當然這裏會有性能開銷,但是當該線程執行完畢之後,線程池會回收這個線程,這裏注意:線程池不會直接銷燬這個線程,而是讓它處於閒置狀態.這樣就不會產生額外的性能開銷. 但是如果該線程如果長時間處於閒置狀態,那麼線程池會銷燬它,關於這個時間的計算很複雜,各個CLR對它的定義各不相同. (2)、當應用程序向線程池發起了多個請求,線程池會嘗試用一個線程來處理你所有的請求,但是如果這個線程處理壓力過大,那麼它會開啓一個新的線程來給它分擔壓力.以此類推. (3)、線程池之包含了少量線程,因爲如果線程太多,會增加性能開銷,當然如果你升級了你電腦的cpu,線程池則會創建更多的線程.這個過程線程池會自動的去讀取你得cpu核數信息,自動的去分配合適的線程數 合理地分配CPU資源.當應用程序的壓力減輕,那麼它會銷燬不用的線程.

(4)、代碼演示

     static void Main(string[] args)
        {
            Console.WriteLine("主線程開始執行,調用一個帶參數的線程池子線程");
            //主線程調用ThreadPool.QueueUserWorkItem方法向線程池的操作隊列添加一個記錄項
            //線程池會遍歷這個操作隊列的所有記錄項,然後將記錄項中派發給一個線程池線程
            //接着線程池的線程就開始執行ExecuteOtherWork方法(同時接受了主線程傳遞給它的參數)
            ThreadPool.QueueUserWorkItem(ExecuteOtherWork,666);
            Console.WriteLine("主線程繼續執行");
            Console.WriteLine("兩個線程全部執行完畢");
            Console.Read();//這行代碼必須加,因爲線程池是後臺線程,當進程關閉,該進程所有的後臺線程都會被關閉,不管是否執行完畢.
        }

        /// <summary>
        /// 線程池子線程調用的方法
        /// </summary>
        /// <param name="state"></param>
        static void ExecuteOtherWork(object state)
        {
            Console.WriteLine("線程池子線程開始執行,主線程傳遞給它的參數是:{0}", state);
            Console.WriteLine("線程池子線程執行完畢");
        }

注:這裏的輸出順序不確定,因爲在多核機器下,可能線程調度器會同時執行主線程和子線程.

四、關於線程池線程的執行上下文

(1)、什麼是執行上下文

執行上下文是初始線程的環境描述的數據結構,該結構包含以下東西:

i、安全設置(壓縮棧、Thread的Principal屬性( 獲取或設置線程的當前負責人(對基於角色的安全性而言))和Windows身份)

ii、宿主設置 詳情參見HostExecutionContext、HostExecutionContextManager類,通過該類可以設置宿主上下文的狀態、以及創建當前宿主上下文的副本.代碼,並設置子線程的上下文爲主線程的上下文:

      static void Main(string[] args)
        {
            Console.WriteLine("主線程開始執行,調用一個帶參數的線程池子線程");
            var mainThreadContext = new HostExecutionContext("0");
            mainThreadContext=mainThreadContext.CreateCopy();
            var thread = new Thread(ExecuteOtherWork);
            thread.Start(mainThreadContext);
            Console.Read();
        }

        /// <summary>
        /// 線程池子線程調用的方法
        /// </summary>
        /// <param name="state"></param>
        static void ExecuteOtherWork(object state)
        {
            var manager = new HostExecutionContextManager();
            manager.SetHostExecutionContext(state as HostExecutionContext);
            Console.WriteLine("子線程執行完畢");
        }

湊合着看,暫時還沒有發現這麼做的實際意義.可能只有微軟知道。哈哈!CLR默認造成初始線程的上下文流向任何子線程。

注:關於上下文複製的這種機制,很清楚,肯定會造成性能上的開銷,每開啓一個新的線程就會複製原有線程的上下文給新的線程.

但是考慮到性能問題,MS提供了ExecutionContext

        private static string shareKey = "線程之間共享的數據槽值鍵";
        static void Main(string[] args)
        {
            
            Console.WriteLine("主線程開始執行,調用一個帶參數的線程池子線程");
            CallContext.LogicalSetData(shareKey, "666");
            ExecutionContext.SuppressFlow();
            var thread = new Thread(ExecuteOtherWork);
            thread.Start();
            Console.Read();
        }

        /// <summary>
        /// 線程池子線程調用的方法
        /// </summary>
        /// <param name="state"></param>
        static void ExecuteOtherWork(object state)
        {
            Console.WriteLine("線程之間共享的數據槽值:{0}", CallContext.LogicalGetData(shareKey));
            Console.WriteLine("子線程執行完畢");
        }

關於CallContext.LogicalSetData參考下面的例子

iii、邏輯調用上下文數據結構CallContext類,關於它的用法,如下:

        private static string notShareContextKey = "線程內唯一的對象,無法共享到其他線程";

        private static string shareContextKey = "線程之間共享的對象,可以傳播到其他線程";

        static void Main(string[] args)
        {
            Console.WriteLine("主線程開始執行,調用一個帶參數的線程池子線程");
            CallContext.SetData(notShareContextKey, "111");
            CallContext.LogicalSetData(shareContextKey, "666");
            var thread = new Thread(ExecuteOtherWork);
            thread.Start();
            Console.WriteLine("看看主線程能不能通過CallContext.SetData方法拿到這個數據:{0}", CallContext.GetData(notShareContextKey) ??"沒有拿到");
            Console.WriteLine("看看主線程能不能通過CallContext.LogicalSetData方法拿到主線程的邏輯上下文對象裏面設置的數據:{0}", CallContext.LogicalGetData(shareContextKey) ?? "沒有拿到");
            Console.Read();
        }

        /// <summary>
        /// 線程池子線程調用的方法
        /// </summary>
        /// <param name="state"></param>
        static void ExecuteOtherWork(object state)
        {
            var obj=CallContext.GetData("線程內唯一的對象,無法共享到其他線程");
            Console.WriteLine("看看子線程能不能通過CallContext.SetData方法拿到主線程的邏輯上下文對象裏面設置的數據:{0}", CallContext.GetData(notShareContextKey) ?? "沒有拿到");
            Console.WriteLine("看看子線程能不能通過CallContext.LogicalSetData方法拿到主線程的邏輯上下文對象裏面設置的數據:{0}", CallContext.LogicalGetData(shareContextKey) ?? "沒有拿到");
            Console.WriteLine("");
            Console.WriteLine("子線程執行完畢");
        }

CallContext.SetData設置的數據線程內唯一,不能跨線程調用,但是CallContext.LogicalSetData可以跨線程調用.後者類似於HttpContext的Session機制,用於保存用戶信息,不受多線程的影響,如果你希望你的數據隨着線程的消失而消失可以使用前者來做,其實HttpContext上下文的本質就是使用HttpContext,我推測的,沒有檢驗,倒是效果是一樣的,本身用戶的請求就相當於一個線程.所以,可以通過對這兩者的理解,可以封裝一個對象,該對象維持一個應用程序上下文,同時能滿足Web應用,可其他基於線程池的應用.即使在多線程環境下,也能很好的維護一些應用全局共享的關鍵數據.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章