天天看點

并行程式設計(1):了解并行

什麼是并行

并行是指兩個或者多個事件在同一時刻發生。

在程式運作中,并行指

多個CPU核心

同時執行不同的任務;對于單核心CPU,嚴格來說是沒有程式并行的。并行是為了提高任務執行效率,更快的擷取結果。

與并發的差別:

并發是指兩個或者多個事件在同一時段發生。

相對于并行,并發強調的是同一時段,是宏觀上的同時發生。實際上,同一時刻隻有一個任務在被執行,多個任務是分時地交替執行的。并發是為了更合理地配置設定資源。

并行程式設計(1):了解并行

如何實作并行

并行程式設計中我們隻關注應用層面的并行,CPU的指令并行技術(指令流水等)不在我們的考慮範圍。

從并行的意義來看,并行程式設計的目的無非是讓多個CPU核心同時執行不同業務邏輯,擷取優良的性能。但是,要怎樣實作并行呢?實作并行,我們要借助

程序

線程

為了更好地管理計算機中運作的程式,計算機作業系統引入

程序

狹義定義:程序是正在運作的程式的執行個體(an instance of a computer program that is being executed)。

廣義定義:程序是一個具有一定獨立功能的程式關于某個資料集合的一次運作活動。

——百度百科

由于程序擁有計算機資源,在建立、切換和撤銷的過程中開銷較大,這就限制了程序的并發程度;多核CPU的日漸普及的環境下,為提高并行粒度和并行計算的效率,引入了一種輕型的程序——

線程

線程(英語:thread)是作業系統能夠進行運算排程的最小機關。它被包含在程序之中,是程序中的實際運作機關。

——百度百科

線程包含于程序,同一程序的線程共享該程序的資源。線程出現後,線程取代程序作為作業系統排程和分派的基本機關,極大地減少了程序切換帶來的性能損失,使得更細粒度和更高性能的并行得以實作。

程序的排程

一台計算機會運作很多程式,這些程式程序的數量多會大于CPU的核心數量。每個CPU核心同一時間隻能執行一個程序,那作業系統是如何管理這些程序的呢?

當啟動一個程式的執行個體時,作業系統将建立一個程序用來排程該程式執行個體。一個程序主要包含以下的資訊:

  • 程序控制塊PCB,用于作業系統控制該程式執行個體
    • 程序辨別資訊,如PID、名稱等
    • 現場資訊,存放程序運作時處理器現場資訊
    • 控制資訊,存放作業系統用于管理和排程程序的資訊
  • 專有的虛拟位址空間
  • 句柄清單
  • 程式執行個體的代碼和資料,被映射到程序私有虛拟位址空間
  • 程式狀态字資訊

程序的狀态模型,如下圖:

并行程式設計(1):了解并行

作業系統按照程序狀态進行程式排程。

  • 啟動程式時,作業系統建立程序,此時程序為

    建立

    • 運作資源充足時,作業系統送出程序到

      就緒

      狀态,等待CPU選擇或者搶占CPU執行
    • 運作資源不足,如主存不夠,作業系統會挂起程序,程序狀态改為

      就緒挂起

      ,等待作業系統的恢複
  • 就緒狀态的程序
    • CPU空閑時,會選擇執行就緒狀态的程序,被選中的程序進入

      運作狀态

    • 程序優先級高時,将搶占目前正在執行程序的CPU資源,自身進入運作狀态
    • 作業系統會根據目前的可用資源,把就緒狀态的程序挂起
  • 就緒挂起的程序
    • 目前沒有就緒的程序,或者就緒挂起的某個程序具有較高的優先級,作業系統會将就緒挂起的程序恢複到就緒狀态
  • 運作狀态的程序
    • 程序自然結束、被強制終結或者出現無法解決的異常,将進入

      終止

      狀态,終止的線程不再參與程序排程
    • 程序到達運作的時間片或者出現優先級高的程序搶占了CPU,程序會回到就緒狀态等待排程
    • 程序等待資源、I/O或者信号時,會進入

      阻塞

      狀态
    • 優先級較高的程序搶占CPU,而此時系統資源不足,則正在運作的線程會被轉入就緒挂起狀态
  • 阻塞狀态的程序
    • 程序阻塞的條件被滿足,如等待的資源到位、I/O完成或收到信号,會進入就緒狀态
    • 程序在等待資源、I/O或者信号時,若系統檢測到運作資源不足,會将阻塞的程序挂起進入

      阻塞挂起

      狀态
  • 阻塞挂起的程序
    • 當被挂起的程序具有較高優先級,同時由于其他程序的退出使資源充裕,程序會被轉為阻塞狀态
    • 挂起的阻塞程序得到資源、I/O完成或者收到信号後,被轉入就緒挂起狀态

上述便是程序的排程過程,其中挂起的程序不占有任何資源。程序的排程很大程度是依賴于運作資源的;程序的優先級也是影響程序排程的重要因素;此外程序的排程還會涉及程序間的通信和同步問題,這裡不做展開。

實際上,相對于程序,在并行程式設計中我們更關心線程,因為線程才是系統排程的基本機關。

線程的排程

在Windows系統中,每個程序至少有一個線程,每個線程都包含下面的内容:

  • 線程核心對象,包含線程上下文(包含CPU寄存器資訊的記憶體塊)
  • 線程環境塊,包含線程的異常處理鍊首、本地存儲資料等
  • 使用者模式棧,存儲傳給方法的局部變量和實參
  • 核心模式棧,線程調用作業系統核心函數時,所傳實參從使用者模式棧複制到核心模式棧
  • DLL線程連接配接和分離,線程建立和銷毀時,所依賴的DLL需要收到通知才能執行相關資源的初始化和清理

從線程所含内容,我們可以知道線程的建立和銷毀是有着時間和空間開銷的,雖然這些開銷相較于程序來說小了很多,但仍是影響程式效率的重要因素。特别是在并行處理的時候,線程的頻繁建立和銷毀将對并行性能産生極為嚴重的影響。

系統同一時間隻給一個CPU核心配置設定一個線程,CPU執行該線程達一個時間片後,系統會給該CPU核心配置設定另一個線程。系統配置設定線程至CPU核心的過程就是線程的上下文切換過程,此間,系統将執行3個動作:

  1. 把CPU寄存器的值儲存到正在運作的線程上下文中
  2. 從現有線程集合中選取一個線程準備配置設定
  3. 把選中線程上下文中儲存的CPU寄存器值加載到CPU寄存器中

線程上下文切換會對程式性能帶來很嚴重的影響,特别是切換到一個新程序的新線程時,很可能需要從RAM中加載代碼和資料,大家知道RAM相對于CPU高速緩存太慢了。

線程的建立、切換及銷毀都是有着不可忽視的開銷,在追求高性能的程式中,我們應盡量少地線程,最優性能的線程數是機器CPU的核心數。當然,性能隻是程式的一個方面,響應性和可靠性也是要關注的重點。

小結

并行在程序層面依賴于系統可用系統資源和CPU核心數,單核CPU的程式并行,實質上是并發;線上程層面則主要依賴于CPU核心數以及我們安排線程的方式。

後續将以.NET為例總結并發程式設計。

注:本文關于程序和線程的相關内容以Windows作業系統為參考。