多執行緒的代價及上下文切換

位元流發表於2014-01-22
多執行緒的代價

     使用多執行緒往往可以獲得更大的吞吐率和更短的響應時間,但是,使用多執行緒不一定就比單執行緒程式跑的快,這取決於我們程式設計者的能力以及應用場景的不同。不要為了多執行緒而多執行緒,而應考慮具體的應用場景和開發實力,使用多執行緒就是希望能夠獲得更快的處理速度和利用閒置的處理能力,如果沒帶來任何好處還帶來了複雜性和一些定時炸彈,那還傻逼了嗎?只有在使用多執行緒給我們帶來的好處遠大於我們付出的代價時,才考慮使用多執行緒,有時候可能引入多執行緒帶來的效能提升抵不過多執行緒而引入的開銷,一個沒有經過良好併發設計得程式也可能比使用單執行緒還更慢。
 
多執行緒給我們帶來的代價
  • 設計更復雜
     多執行緒程式在訪問共享資料的時候往往需要我們很小心的處理,否則就會出現難以發現的BUG,一般地,多執行緒程式往往比單執行緒程式設計會更加複雜(儘管有些單執行緒處理程式可能比多執行緒程式要複雜),而且錯誤很難重現(因為執行緒排程的無序性,某些bug的出現依賴於某種特定的執行緒執行時序)。
  • 上下文切換的開銷
     執行緒是由CPU進行排程的,CPU的一個時間片內只執行一個執行緒上下文內的執行緒,當CPU由執行執行緒A切換到執行執行緒B的過程中會發生一些列的操作,這些操作主要有”儲存執行緒A的執行現場“然後”載入執行緒B的執行現場”,這個過程稱之為“上下文切換(context switch)”,這個上下文切換過程並不廉價,如果沒有必要,應該儘量減少上下文切換的發生。
  • 增加更多的資源消耗
     除了CPU執行上下文切換的消耗以外,執行緒的執行還將有其他一些資源的消耗,比如:記憶體同步的開銷(執行緒需要一些記憶體在維持執行緒本地棧,每個執行緒都有本地獨立的棧用以儲存執行緒專用資料),上下文切換的開銷(前面已經講過),執行緒建立和消亡的開銷,以及排程的開銷(佔用作業系統的一些資源來管理和協調執行緒)我們可以建立100個執行緒讓他們什麼都不做,看看他們消耗了多少記憶體。
 
上下文切換

     多數人認為使用多執行緒一定會比單執行緒執行速度快,但其實未必,因為多執行緒應用程式會帶來額外的開銷和競爭問題,他們都可能會拖慢系統的執行速度。這些因素包括:對IO裝置的競爭,對鎖的競爭,以及CPU對執行緒執行上下文的頻繁切換等。
 
     目前流行的CPU在同一時間內只能執行一個執行緒,超執行緒的處理器(包括多核處理器)可以同一時間執行多個執行緒,linux將多核處理器當作多個單獨CPU來識別的。每個程式都會分到CPU的時間片來執行,當某個程式(執行緒是輕量級程式,他們是可以並行執行的,並且共享地使用他們所屬程式的地址空間資源,比如:記憶體空間或其他資源)當程式用完時間片或者被另一個優先順序更高的程式搶佔的時候,CPU會將該程式備份到CPU的執行佇列中,其他程式被排程在CPU上執行,這個程式切換的過程被稱作“上下文切換”,過多的上下文切換會造成系統很大的開銷。
 
     在Linux中可以使用vmstat來觀察上下文切換的次數,一般來說,空閒的系統,每秒上下文切換次數大概在1500以下。
 
引起上下文切換的原因
  1. 時間片用完,CPU正常排程下一個任務
  2. 被其他優先順序更高的任務搶佔
  3. 執行任務碰到IO阻塞,排程器掛起當前任務,切換執行下一個任務
  4. 使用者程式碼主動掛起當前任務讓出CPU時間
  5. 多工搶佔資源,由於沒有搶到被掛起
  6. 硬體中斷
參考:
  1. Costs of Multithreading
  2. Context Switch
  3. Context Switch Definition
  4. 從Java視角理解系統結構(一)CPU上下文切換
  5. Futext

相關文章