進程與線程
為什麼對於大多數合作性任務,多線程比多個獨立的進程更優越呢?這是因為,線程共用相同的記憶體空間。不同的線程可以存取記憶體中的同一個變數。所以,程式中的所有線程都可以讀或寫聲明過的全域變數。如果曾用fork() 編寫過重要代碼,就會認識到這個工具的重要性。為什麼呢?雖然fork() 允許建立多個進程,但它還會帶來以下通訊問題:如何讓多個進程相互連信,這裡每個進程都有各自獨立的記憶體空間。對這個問題沒有一個簡單的答案。雖然有許多不同種類的本地IPC (處理序間通訊),但它們都遇到兩個重要障礙:
- 強加了某種形式的額外核心開銷,從而降低效能。
- 對於大多數情形,IPC不是對於代碼的“自然”擴充。通常極大地增加了程式的複雜性。
雙重壞事: 開銷和複雜性都非好事。如果曾經為了支援 IPC而對程式大動幹戈過,那麼您就會真正欣賞線程提供的簡單共用記憶體機制。由於所有的線程都駐留在同一記憶體空間,POSIX線程無需進行開銷大而複雜的長距離調用。只要利用簡單的同步機制,程式中所有的線程都可以讀取和修改已有的資料結構。而無需將資料經由檔案描述符轉儲或擠入緊窄的共用記憶體空間。僅此一個原因,就足以讓您考慮應該採用單進程/多線程模式而非多進程/單線程模式。
為什麼要用線程?
與標準 fork()相比,線程帶來的開銷很小。核心無需單獨複製進程的記憶體空間或檔案描述符等等。這就節省了大量的CPU時間,使得線程建立比新進程建立快上十到一百倍。因為這一點,可以大量使用線程而無需太過於擔心帶來的CPU 或記憶體不足。使用 fork() 時導致的大量 CPU佔用也不複存在。這表示只要在程式中有意義,通常就可以建立線程。
當然,和進程一樣,線程將利用多CPU。如果軟體是針對多處理器系統設計的,這就真的是一大特性(如果軟體是開放源碼,則最終可能在不少平台上運行)。特定類型線程程式(尤其是CPU密集型程式)的效能將隨系統中處理器的數目幾乎線性地提高。如果正在編寫CPU非常密集型的程式,則絕對想設法在代碼中使用多線程。一旦掌握了線程編碼,無需使用繁瑣的IPC和其它複雜的通訊機制,就能夠以全新和創造性的方法解決編碼難題。所有這些特性配合在一起使得多線程編程更有趣、快速和靈活。
什麼是線程?
- 專業點的說法,線程被定義為一個獨立的指令流,它本身的運轉由作業系統來安排,但是,這意味著什麼呢?
- 對軟體開發人員來說,解釋線程最好的描述就是“procedure”可以獨立於主程式運行。
- 再進一步,設想一個包含了大量procedure的主程式,然後想象所有這些procedure在作業系統的安排下一起或者獨立的運行,這就是對於多線程程式的一個簡單描述。
- 問題是,它是如何?的呢?
- 在弄懂線程之前,第一步要搞清楚Unix進程。進程被作業系統建立,並需要相當多的“開支”,進程包含如下程式資源和程式執行狀態資訊:
- 進程ID,進程群組ID,使用者ID,群組ID
- 環境
- 工作目錄
- 程式指令
- 寄存器
- 棧
- 堆
- 檔案描述符
- 訊號動作
- 共用庫
- 處理序間通訊工具(例如訊息佇列,管道,訊號量,共用記憶體)
Unix進程 Unix進程內部的線程
- 線程使用和在進程內的生存,仍由作業系統來安排並且獨立的實體來運行,很大程度上是因為它們為可執行代碼的存在複製了剛剛好的基本資源。
- 這個獨立的控制流程之所以可以實現,是因為線程維護著如下的東西:
- 棧指標
- 寄存器
- 調度屬性(例如規則和優先順序)
- 等待序列和阻塞訊號
- 線程擁有的資料
- 它生存在進程中,並使用進程資源;
- 擁有它自己獨立的控制流程,前提是只要它的父進程還存在,並且OS支援它;
- 它僅僅複製可以使它自己調度的必要的資源;
- 它可能會同其它與之同等獨立的線程分享進程資源;
- 如果父進程死掉那麼它也會死掉——或者類似的事情;
- 它是輕量級的,因為大部分的開支已經在它的進程建立時完成了。
- 一個線程對共用的系統資源做出的改變(例如關閉一個檔案)會被所有的其它線程看到;
- 指向同一地址的兩個指標的資料是相同的;
- 對同一塊記憶體進行讀寫操作是可行的,但需要程式員作明確的同步處理操作。