C# 多線程(一)- 瞭解多線程的基本概念

一、多線程基礎

1.多線程術語

CPU或內核是實際執行指定程序的指定單元,現在一個CPU通常也支持多線程;

進程,指給定程序當前正在執行的實例,操作系統一個主要功能就是管理進程;

單線程程序只僅包含一個線程的進程,對應的是多線程程序;

線程安全:在多線程程序中具有正確的表現

線程處理模型:指代碼向調用者提出一些調用規範,只有滿足這些規範要求才能保證線程安全;

線程池:指多個線程的集合,也是決定如何向線程分配工作的邏輯;有任務需要分配時,從線程池取線程,並在任務結束後放回線程池;

下面兩篇關於多線程基礎的知識,必須首先掌握才能進一步學習多線程編程

掘金-一篇讓你明白進程與線程之間的區別與聯繫

簡書:操作系統--阻塞,睡眠,掛起

時間片:時間片是指分時操作系統分配給每個正在運行的進程微觀上的一段CPU時間(搶佔內核:一個進程從開始運行到被搶佔的時間);現代操作系統允許同時運行多個進程,但是一個計算機通常只有一個CPU,它又不可能真正地同時運行多個任務,所以它看起來像同時運行,實際上是輪番運行,由於時間片很短,用戶不會感覺到;

2.多線程處理

多線程處理主要用於兩個方面:實現多任務和解決延遲

爲了解決CPU資源不足問題,操作系統採用時間分片的機制來模擬多個線程併發執行;處理器在切換到下一個線程之前,執行一個特定線程的時間週期稱爲時間片,在一個給定的內核中切換執行線程的動作成爲上下文切換;

3.線程處理的問題

  • 大多數操作都不是原子性的
  • 競態條件所造成的不確定性,包含競態條件的代碼,它的行爲切換取決於上下文切換時機,從而造成了程序執行結果的不確定性;
  • 內存模型的複雜性,CPU不會每次要用一個變量時都去訪問主內存,而是在“高速內存”生成一個副本,這個緩存會定時與主內存同步,但是兩個線程可能以爲自己在讀取相同的位置,其實不是;
  • 鎖定造成的死鎖問題

二、使用System.Threading

1.System.Threading.thread完成異步操作

操作系統實現線程並提供各種非託管API來創建線程,CLR封裝好這些非託管線程,並在託管代碼中通過System.Threading.Thread來公開它們。該類的實例就代表一個獨立的控制點,可以將一個線程當做一個房間內的工作者,它獨立地按照你的指令進行工作;

示例:控制點由一個實例表示,線程需要知道啓動時運行什麼代碼,所以構造器需要獲取對要執行代碼的委託,委託類型爲ThreadStart,然後調用Start()啓動線程,然後調用Join()告訴主線程等候工作者線程完成;

    class Program
    {
        static void Main(string[] args)
        {
            ThreadStart threadStart=DoWork;
            Thread thread=new Thread(threadStart);
            thread.Start();
            for(int i=0;i<1000;i++){
                Console.Write("-");
            }
            thread.Join();
        }

        public static void DoWork(){
            for(int i=0;i<1000;i++){
                Console.Write("+");
            }
        }
    }

2.線程管理

線程包含了大量的屬性和方法,用於管理它們的執行;

  • Start():開啓一個線程
  • Join():使一個線程等待另一個線程,告訴操作系統暫停執行當前線程,知道另一個線程終止;cnblog:關於C#中Thread.Join()的一點理解
  • IsBackGround():新線程默認爲“前臺”線程,操作系統將在進程的所有前臺線程完成後終止進程。可以將thread.IsBackGround設爲true,從而將線程標記爲“後臺”線程,這樣,即使後臺線程仍在運行,操作系統也允許進程終止;
  • Priority:每個線程都關聯了優先級,使用ThreadPriority枚舉來設置,操作系統傾向於把時間片調度給高優先級線程;
  • ThreadState:可以通過ThreadState屬性訪問當前的線程狀態,爲枚舉類型;

3.Sleep()函數

靜態方法Thread.Sleep()是當前線程進入睡眠,其實就是告訴操作系統在指定時間內不要爲該線程調度任何時間片,參數指定了操作系統要等待它多長時間;

線程睡眠的目的通常是和其它線程就某個事件同步,但是操作系統不保證計時的精確度,也就是如果指定睡眠123ms,那麼操作系統至少讓線程睡眠123秒,當往往時間更長,所以不可以將Thread.Sleep()當做高精度計時器用,它不是;

Thread.Sleep()還是有一些用處的,首先,可以將線程睡眠時間設爲零,相當於告訴操作系統“當前線程剩下的時間片就送給其它線程了”;其次,可以在測試代碼中使用該函數來模擬高延遲操作;除此之外,慎重使用;

4.Abort()函數

Thread對象的Abort()方法一旦被執行,就會嘗試銷燬線程,它會造成“運行時”在線程中引發ThreadAbortException異常;

開發人員應該在萬不得已時才使用Abort();

三、線程池處理

線程太多會對性能造成負面影響,線程上下文切換不是免費的,通過時間分片模擬兩個作業“並行”可能比一個接一個運行還要慢;

線程池:開發人員不是直接分配線程,而是告訴線程池想要完成什麼工作,結束以後,線程不是中斷並被銷燬,而是回到池中,從而節省了當更多的工作來臨時還要分配新線程的開銷;

        static void Main(string[] args)
        {
            ThreadPool.QueueUserWorkItem(DoWork, '+');
            for (int count = 0; count < 1000; count++)
            {
                Console.Write('-');
            }
            Thread.Sleep(1000);
        }
        public static void DoWork(object state) {
            for ( int count = 0; count < 1000; count++) {
                Console.Write(state);
            }
        }

線程池也會有缺點,線程池的效率是通過重用線程來獲得的,當然它也需要遵循一些規範才行,因爲它的效率是在滿足了一定條件下才會出現的;

  • 假設調度的所有工作都能及時結束,使線程能回到池中併爲其他任務所用,線程池還假定所有工作的運行時間都比較短;

  • 線程池通過確保線程的創建數量“剛剛好”,沒有一個處理因爲運行太多線程而超出負荷,從而防止過度的時間分片;但是這也限制着,一旦池中所有線程都被佔用了,正在排隊的工作必須延遲執行;

總結:線程池能夠很好的完成作業,但是該作業中不應該包含處理長時間運行的作業,或者處理需要與其他線程同步的作業;開發人員真正要做的事構建高級抽象,將線程和線程池作爲實現細節使用,這種抽象由任務並行庫來實現,也就是Task Parallel Library-TPL;

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