注:該文用於個人學習記錄和知識交流,如有不足,歡迎指點。
一、IO密集型與CPU密集型的定義
|
對比維度
|
IO 密集型操作
|
CPU 密集型操作
|
|
定義
|
操作過程中主要依賴輸入 / 輸出(IO)交互,CPU 大部分時間處於等待 IO 完成的狀態(如等待磁盤讀寫、網絡數據傳輸等)。
|
操作過程中主要依賴 CPU 進行計算處理,CPU 持續處於高負載狀態,IO 操作極少或耗時極短。
|
|
核心特徵
|
IO 操作頻繁(如讀寫文件、網絡通信);CPU 利用率低;任務執行時間主要消耗在 IO 等待。
|
計算任務繁重(如複雜運算、邏輯處理);CPU 利用率高(接近 100%);執行時間主要消耗在計算。
|
|
C/C++ 典型例子
|
1. 文件讀寫( 2. 網絡數據收發( 3. 數據庫交互(通過 API 執行查詢 / 寫入) |
1. 大規模數學計算(矩陣乘法、數值模擬) 2. 數據加密 / 解密(AES、RSA 算法實現) 3. 複雜算法處理(千萬級數據排序、圖形渲染計算) |
|
CPU 使用率
|
較低(大部分時間等待 IO,CPU 空閒)
|
較高(持續滿負載運行,充分佔用 CPU 資源)
|
|
等待時間佔比
|
高(IO 等待時間佔總執行時間的絕大部分)
|
低(幾乎無 IO 等待,總執行時間主要為計算時間)
|
|
資源瓶頸
|
受限於 IO 設備性能(如磁盤讀寫速度、網絡帶寬)
|
受限於 CPU 處理能力(如核心數量、主頻、緩存大小)
|
二、IO密集型場景的解決方案
在 C/C++ 中處理 IO 密集型操作(如文件讀寫、網絡通信、數據庫交互等)的核心目標是避免CPU阻塞在IO 等待的時間裏、提高 CPU 利用率,並高效處理併發 IO 任務。以下是常見的解決方案及適用場景:
1. 非阻塞 IO + IO 多路複用(一般用於單線程)
單線程中、傳統阻塞 IO 會導致進程 / 線程在 IO 操作時掛起(等待數據就緒),浪費 CPU 資源。非阻塞 IO 配合 IO 多路複用可解決這一問題。
(注意:要想不浪費CPU資源,你當然可以設置多個線程,當線程阻塞時,如果線程數大於內核數時,CPU會釋放出去給需要的線程,但是注意線程的數量是有限制的(在Linux中每個線程佔8M空間),一般應用於fd數量很少的時候,在高併發場景中我們不會採用一IO一線程的形式!!!)
1.1 非阻塞 IO
通過fcntl或ioctl將文件描述符(fd)設置為非阻塞模式,此時調用read/write等 IO 函數時:
- 若數據未就緒(或緩衝區滿),函數會立即返回錯誤(
EAGAIN/EWOULDBLOCK),而非阻塞等待; - 進程可繼續執行其他任務,避免閒置。
1.2 IO 多路複用(核心)
用於同時監控多個 IO 流的就緒狀態(可讀 / 可寫 / 異常),避免對每個 IO 流單獨輪詢,大幅提升效率。常見實現:
|
機制
|
特點
|
適用場景
|
|
|
支持跨平台,但 fd 數量有限(默認 1024),每次調用需複製 fd 集合,效率低
|
簡單場景、低併發
|
|
|
突破 fd 數量限制,但仍需輪詢所有 fd,高併發時效率下降
|
中等併發、跨平台需求
|
|
|
Linux 專屬,事件驅動(只關注活躍 fd),無 fd 數量限制,效率極高
|
高併發場景(如服務器)
|
|
|
BSD/macOS 專屬,類似 epoll,支持更多事件類型(如文件修改)
|
BSD/macOS 系統的高併發場景
|
(我之前寫了一篇IO多路複用的代碼實現的博文,可以去找一找)
1.3 總結:
|
維度
|
具體內容
|
|
核心原理
|
1. 非阻塞 IO:通過 2. IO 多路複用:通過內核機制(select/poll/epoll/kqueue 等)同時監控多個 fd 的就緒狀態(可讀 / 可寫 / 異常),僅處理活躍 IO 流。 |
|
解決的問題
|
傳統阻塞 IO 中,進程 / 線程會因等待 IO 而掛起,導致 CPU 資源浪費;單線程無法高效處理大量併發 IO 流的問題。
|
|
優點
|
- 避免進程 / 線程阻塞等待 IO,CPU 利用率高; - 事件驅動模式,僅處理就緒的 IO 流,減少無效操作,效率高; - epoll/kqueue 支持無上限監控 fd,適配高併發場景。 |
|
缺點
|
- select:默認 fd 上限 1024(調整意義有限),每次調用需複製 fd 集合,高併發下效率低; - poll:無 fd 數量限制,但需輪詢所有 fd,高併發時效率隨 fd 數量增加而下降;- 編程複雜,需手動處理非阻塞 IO 邏輯和事件回調,易出現 “回調地獄”。 |
|
應用場景
|
- 高併發網絡服務(如 Web 服務器、網關、即時通訊服務器); - 需要同時處理大量 IO 流(如 thousands 級以上連接)的場景。 |
|
注意事項
|
- 高併發場景優先選擇 epoll(Linux)或 kqueue(BSD/macOS),避免使用 select/poll; - 需熟練處理非阻塞 IO 的返回值(如區分 - 事件回調邏輯需簡潔,避免阻塞事件循環。 |
2. 多線程 / 線程池
IO 密集型任務中,CPU 大部分時間處於空閒(等待 IO),因此可通過多線程並行處理多個 IO 任務,利用 CPU 多核資源。
注意:當線程阻塞時,CPU會釋放,但是線程的數量是有限制的
a. 在Linux中每個線程佔8M空間
b. 過多線程會導致上下文切換開銷激增。
所以線程一般應用於fd數量很少的時候,在高併發場景中我們不會採用一IO一線程的形式!!!
2.1 核心思路
- 每個線程處理一個獨立的 IO 流(如一個網絡連接、一個文件);
- 當線程阻塞在 IO 時,其他線程可繼續運行,避免 CPU 閒置。
2.2 線程池優化
頻繁創建 / 銷燬線程會帶來開銷,線程池通過預先創建固定數量的線程,循環處理任務隊列中的 IO 任務,減少開銷:
2.3 總結:
|
維度
|
具體內容
|
|
核心原理
|
1. 多線程:每個線程獨立處理一個 IO 流(如一個網絡連接、一個文件),當線程因 IO 阻塞時,CPU 自動調度其他就緒線程運行; 2. 線程池:預先創建固定數量的線程,循環從任務隊列中獲取 IO 任務並處理,減少線程創建 / 銷燬的開銷。 |
|
解決的問題
|
單線程串行處理多個 IO 流時效率低下的問題,通過多核 CPU 並行處理 IO 任務,提高整體吞吐量。
|
|
優點
|
- 利用多核 CPU 並行處理,提升 IO 任務吞吐量; - 基於同步阻塞 IO 模型,編程邏輯簡單,開發和維護成本低; - 線程池避免頻繁創建 / 銷燬線程的開銷,適合任務頻繁的場景。 |
|
缺點
|
- 線程棧內存佔用大(Linux 默認 8MB,可調整但仍有下限),IO 流過多時易導致內存壓力; - 線程上下文切換為內核級操作,開銷高(約幾微秒),線程數量過多會抵消並行收益; - 線程間共享數據時易出現鎖競爭,導致核心阻塞。 |
|
應用場景
|
- IO 流數量較少的場景(如幾十到幾百個連接 / 文件); - 任務邏輯簡單、IO 阻塞時間較長的場景(如少量文件批量讀寫、低併發數據庫交互)。 |
|
注意事項
|
- 線程數量建議控制在 “CPU 核心數 ×2” 以內,避免過多線程導致切換開銷激增; - 可通過 - 減少線程間共享數據,優先使用無鎖結構或線程本地存儲,避免鎖競爭。 |
3. 協程(輕量併發)
(關於協程的實現我會在後續博文詳細介紹)
協程是用户態的 “輕量級線程”,在 IO 操作時可主動掛起,讓出 CPU 給其他協程,避免線程上下文切換的開銷,適合處理海量併發 IO(如十萬級網絡連接)。
3.1 C++ 中的協程
- C++20 引入
std::coroutine,但需配合編譯器支持(如 GCC 10+、Clang 15+); - 第三方庫:Boost.Coroutine、libco(騰訊)等,封裝了協程切換邏輯。
3.2 優勢
- 協程切換成本遠低於線程(無需內核參與);
- 單線程可運行上萬協程,高效處理大量 IO 任務。
- 用同步的代碼風格寫異步邏輯
(注意:協程的切換通常搭配epoll實現)
3.3 總結:
|
維度
|
具體內容
|
|
核心原理
|
協程是用户態的輕量級併發單元,擁有獨立的棧空間但由用户態調度; 當執行 IO 操作時,協程主動掛起(用户態切換,無需內核參與),讓出 CPU 給其他協程; 依賴底層 IO 多路複用(如 epoll)監控 IO 就緒狀態,當 IO 就緒時喚醒對應的協程。 |
|
解決的問題
|
多線程在高併發場景下內存佔用大(線程棧)、上下文切換開銷高的問題,支持海量 IO 流(十萬級以上)的高效併發處理。
|
|
優點
|
- 協程切換為用户態操作,開銷極低(約幾十納秒,僅為線程切換的 1/100~1/1000); - 單線程可承載上萬甚至幾十萬協程,支持海量併發 IO; - 支持用同步代碼風格編寫異步邏輯,避免 “回調地獄”,可讀性和可維護性高。 |
|
缺點
|
- 需依賴底層 IO 多路複用(如 epoll)實現 IO 就緒通知,否則協程掛起後無法被喚醒; - 協作式調度,若某協程執行 CPU 密集型操作(長時間不掛起),會阻塞同一線程內的其他協程; - 依賴第三方庫(如 Boost.Coroutine、libco)或 C++20 標準協程(編譯器支持有限,使用門檻高)。 |
|
應用場景
|
- 十萬級以上海量併發 IO 場景(如高併發客户端、分佈式爬蟲、微服務網關); - 每個任務包含多次 IO 等待的場景(如多次數據庫查詢、多步網絡請求)。 |
|
注意事項
|
- 必須與 IO 多路複用(如 epoll)配合使用,否則無法發揮高效併發能力; - 避免在協程中執行長時間 CPU 密集型操作,需主動掛起讓出 CPU; - 根據開發環境選擇合適的協程實現(優先成熟第三方庫,C++20 標準協程需確認編譯器支持)。 |
三、CPU密集型的解決方案
在 C/C++ 中,CPU 密集型操作(如數值計算、圖像處理、加密解密、複雜算法推理等)的核心瓶頸是CPU 計算能力,解決方案的核心目標是最大化利用 CPU 資源(如多核並行、指令級優化、減少計算冗餘等)。以下是具體方案及適用場景:
1.多線程並行(利用多核 CPU)
現代 CPU 普遍為多核架構,單線程只能利用一個核心,多線程可將任務拆分到多個核心並行執行,直接提升計算吞吐量。
1.1 核心思路
- 任務拆分:將 CPU 密集型任務按邏輯拆分為獨立子任務(如分塊處理數組、分片計算矩陣),每個子任務由一個線程執行;
- 線程數控制:線程數通常設置為CPU 核心數(或核心數 ±1),避免過多線程導致上下文切換開銷抵消並行收益(CPU 密集型任務中,線程切換成本極高:當線程數大於內核數時,如果每個線程都需要CPU,那麼CPU會進行輪詢。輪詢的時間成本高於CPU實際計算的時間)。
2.2 實現方式
- 原生線程(pthread/C++11 thread):手動創建線程並分配任務,適合簡單場景。
- 線程池:避免頻繁創建 / 銷燬線程的開銷(尤其任務短而多的場景),通過預先創建固定數量的線程循環處理任務隊列。適合任務粒度小、動態生成的場景(如批量圖像處理)。
2.3 注意事項
- 避免鎖競爭:CPU 密集型任務中,線程間若頻繁競爭鎖(如共享數據寫入),會導致大量核心阻塞,嚴重降低性能。建議:
- 儘量使用無鎖數據結構(如
std::atomic)或線程本地存儲(TLS); - 拆分任務時讓數據無重疊(每個線程處理獨立數據塊),減少共享。
- 負載均衡:確保子任務計算量均勻,避免 “某線程早結束,其他線程仍在忙碌” 的情況(可通過動態任務分配優化,如 TBB 的
parallel_for)。
2.4 總結:
|
維度
|
具體內容
|
|
核心原理
|
基於多核 CPU 架構,將 CPU 密集型任務按邏輯拆分為獨立子任務(如數據分塊、計算分片),通過多線程將子任務分配到不同 CPU 核心並行執行; 線程由操作系統內核調度,當線程數與核心數匹配時,可最大化利用多核計算能力。 |
|
解決的問題
|
單線程只能利用單個 CPU 核心,無法發揮多核架構的計算潛力,導致 CPU 密集型任務(如數值計算、圖像處理)執行效率低下的問題。
|
|
優點
|
- 高效利用多核資源:通過並行計算直接提升吞吐量,理論上 N 核 CPU 可實現近 N 倍加速(忽略任務拆分開銷); - 線程通信成本低:線程共享進程地址空間,可直接訪問共享內存(需同步機制),數據交互效率高於進程間通信; - 線程池優化:預先創建線程減少頻繁創建 / 銷燬的開銷,適合短任務、動態生成任務的場景。 |
|
缺點
|
- 上下文切換開銷高:當線程數超過 CPU 核心數時,內核需頻繁切換線程(保存 / 恢復寄存器、棧等狀態),每次切換約幾微秒,會抵消並行收益; - 同步成本風險:線程共享數據時需鎖或原子操作,頻繁鎖競爭會導致核心阻塞(“串行化”),嚴重降低性能; - 資源限制:每個線程默認棧空間較大(Linux 約 8MB),過多線程會佔用大量內存。 |
|
應用場景
|
- 可拆分為獨立子任務的 CPU 密集型操作:如大數組運算、矩陣乘法、圖像分塊處理、批量加密解密; - 任務粒度適中且可動態生成的場景:如實時視頻幀處理、批量數據清洗與轉換。 |
|
注意事項
|
- 線程數控制:通常設為 “CPU 核心數” 或 “核心數 ±1”,避免線程數超過核心數導致切換開銷激增;- 減少共享與鎖競爭:優先採用 “數據無重疊拆分”(每個線程處理獨立數據塊),必要時用 - 負載均衡:確保子任務計算量均勻(如通過動態任務分配,如 TBB 的 |
2. 多進程並行(隔離性與多核利用)
多進程與多線程的核心區別是內存隔離(進程間不共享地址空間),適合以下場景:
- 任務穩定性要求高(單個進程崩潰不影響其他進程);
- 利用操作系統的 CPU 調度(避免線程庫調度的侷限性)。
2.1 實現方式
- 通過
fork(Linux)或CreateProcess(Windows)創建子進程,通過進程間通信(IPC) 傳遞數據(如管道、共享內存、消息隊列)。 - 共享內存(如
shmget/mmap)是 CPU 密集型任務的優選 IPC 方式(避免數據拷貝開銷)。
2.2 優劣
- 優勢:隔離性好,適合不穩定任務;可利用操作系統調度均衡負載。
- 劣勢:IPC 開銷高於線程間共享內存;進程創建 / 銷燬成本高於線程。
2.3 總結:
|
維度
|
具體內容
|
|
核心原理
|
通過創建多個獨立進程(如 Linux 的 |
|
解決的問題
|
- 單線程進程無法利用多核 CPU,導致 CPU 密集型任務效率低下; - 多線程方案中 “線程崩潰可能導致整個進程崩潰” 的穩定性風險,以及線程庫調度在某些場景下不如系統級進程調度靈活的侷限性 |
|
優點
|
- 隔離性強:進程擁有獨立地址空間,單個進程崩潰(如內存越界、斷言失敗)不會影響其他進程,適合穩定性要求高的任務(如異構計算節點、第三方不可靠模塊); - 調度更靈活:可利用操作系統原生調度機制(避免線程庫調度的侷限性),在複雜負載下可能獲得更均衡的核心利用; - 資源隔離:進程間內存、文件描述符等資源獨立,可避免線程間的資源競爭(如堆內存碎片影響)。 |
|
缺點
|
- IPC 開銷高:進程間數據交互需通過管道、消息隊列、共享內存等方式,其中共享內存雖高效但需手動管理同步,其他方式(如管道)存在數據拷貝開銷,效率低於線程間直接共享; - 管理成本高:進程創建 / 銷燬比線程更耗時(涉及地址空間初始化、頁表創建等),不適合短任務頻繁生成的場景; - 內存佔用高:每個進程需獨立加載代碼、數據,重複佔用內存(如相同庫的代碼段在多進程中可能被共享,但堆 / 棧獨立)。 |
|
應用場景
|
- 穩定性優先的 CPU 密集型任務:如分佈式計算節點(單個節點崩潰不影響集羣)、集成第三方不穩定算法(如實驗性模型推理); - 需資源隔離的場景:如多用户計算任務(避免用户間資源干擾)、對內存泄漏敏感的長期運行任務; - 利用系統級調度優勢的場景:如跨 NUMA 節點的大規模計算(進程更易綁定到特定 NUMA 節點)。 |
|
注意事項
|
- 優先選擇高效 IPC:CPU 密集型任務中,推薦用共享內存(如 - 進程數控制:與線程類似,進程數建議不超過 CPU 核心數(避免調度開銷),或根據 NUMA 節點數合理分配; - 避免重複初始化:通過共享庫或預加載數據減少多進程的重複初始化開銷(如提前加載模型權重到共享內存)。 |
四、線程的狀態
|
狀態名稱
|
定義(核心特徵)
|
觸發條件(進入該狀態的原因)
|
轉換方向(可進入的其他狀態)
|
|
新建(New)
|
線程已被創建(如通過 |
調用線程創建函數(如 |
調用啓動接口(如 |
|
就緒(Ready)
|
線程已啓動,具備執行條件(CPU 資源除外),等待操作系統調度分配 CPU 時間片。
|
線程新建後啓動;或阻塞狀態結束(如 IO 完成、鎖釋放); 或被搶佔後重新進入就緒隊列。 |
被操作系統調度獲得 CPU 時間片後,進入運行狀態。 |
|
運行(Running)
|
線程正在 CPU 核心上執行指令,佔用 CPU 資源。
|
從就緒狀態被調度器選中,獲得 CPU 時間片。
|
時間片耗盡或被高優先級線程搶佔,回到就緒狀態;或因等待資源(如鎖、IO),進入阻塞狀態;或執行完畢,進入終止狀態。 |
|
阻塞(Blocked)
|
線程暫時停止執行,放棄 CPU 資源,等待特定條件滿足(如 IO 完成、鎖釋放、信號通知)。
|
1. 等待互斥鎖(如 2. 執行 IO 操作(如 3. 調用阻塞式同步接口(如 |
等待的條件滿足(如鎖釋放、IO 完成)後,進入就緒狀態。 |
|
等待(Waiting)
|
阻塞狀態的細分,線程無超時等待某個事件(需被其他線程顯式喚醒)。(部分模型中獨立為狀態)
|
調用無超時等待接口(如 |
被其他線程喚醒(如 |
|
超時等待(Timed Waiting)
|
阻塞狀態的細分,線程有超時等待某個事件(超時後自動喚醒)。(部分模型中獨立為狀態)
|
調用帶超時的等待接口(如 |
超時時間到達或被提前喚醒後,進入就緒狀態。 |
|
終止(Terminated)
|
線程執行完畢(如函數返回)或被強制終止(如 |
1. 線程函數正常返回; 2. 調用終止接口(如 3. 被其他線程取消(如 |
無後續狀態轉換,線程對象可能保留(如 |
説明:
- 狀態劃分的靈活性:部分系統(如 Linux)將 “就緒” 和 “運行” 合併為 “可運行(R)” 狀態(內核視角下,兩者均為就緒隊列中的任務,僅運行狀態是當前佔用 CPU 的任務)。
- 阻塞狀態的細分:“等待”“超時等待” 是阻塞狀態的特殊場景,核心都是線程放棄 CPU 等待資源,部分模型中會將其獨立為狀態以更清晰描述等待類型。
- 核心邏輯:線程狀態轉換的核心是”是否需要CPU“ “是否佔用 CPU” 和 “是否等待資源”,從新建到終止的生命週期圍繞這兩點展開。
總的來説:線程的狀態影響着操作系統是否分配CPU給它!!!
五、CPU調度情況
1.線程數不同時
|
維度
|
線程數 ≤ CPU 內核數
|
線程數 > CPU 內核數
|
|
CPU 利用率 |
高,每個線程可在獨立內核上並行執行,無閒置核心
|
理論上仍為 100%,但實際因上下文切換開銷,有效計算時間被佔用,實際有效利用率下降 |
|
上下文切換開銷 |
低,線程間切換主要為CPU的主動讓出(如阻塞、線程結束等等)
|
高,內核需頻繁切換線程(保存 / 恢復線程狀態),切換開銷佔比顯著增加
|
|
任務吞吐量 |
隨線程數增加線性提升(接近內核數時達到峯值)
|
吞吐量先增後減,線程數超過內核數後,因切換開銷抵消並行收益,吞吐量開始下降
|
|
資源消耗(內存等) |
低,每個線程棧空間(如 Linux 默認 8MB)總消耗可控
|
高,過多線程的棧空間、內核調度數據結構會佔用大量內存,可能引發內存壓力
|
|
適用場景 |
CPU 密集型任務(如數值計算、圖像處理),需最大化核心利用率
|
IO 密集型任務(如網絡通信、文件讀寫),線程因 IO 阻塞時可讓出 CPU 給其他線程
|
|
性能表現 |
響應時間穩定,無額外調度延遲
|
響應時間波動大,調度延遲增加(線程需等待 CPU 時間片)
|
2. 搶佔式調度
|
項目
|
具體內容
|
|
核心結論
|
當線程數 > CPU 內核數時,操作系統對線程的調度屬於搶佔式調度。 |
|
原因分析
|
現代操作系統(如 Linux、Windows)的線程調度機制默認是搶佔式的: 當線程數超過 CPU 內核數時,多個線程需共享有限的 CPU 核心,操作系統會為每個線程分配時間片(一段可執行的時間);時間片耗盡後,操作系統會主動中斷當前線程(搶佔其 CPU 使用權),並切換到下一個線程執行,以保證所有線程公平獲得 CPU 資源,避免某線程長期佔用 CPU 導致其他線程 “飢餓”。 |
|
具體表現
|
- 每個線程會被分配時間片(如 Linux 默認時間片約為幾毫秒),時間片內線程可獨佔一個 CPU 核心執行; - 時間片耗盡後,操作系統會強制切換到下一個線程,即使當前線程的計算任務未完成。 |
|
補充説明
|
- 搶佔式調度是內核級的,由操作系統內核主動控制,與線程是否 “需要 CPU” 無關,只要線程處於就緒狀態,就可能被調度器搶佔或分配 CPU 時間; - 若線程因 IO 操作阻塞(如網絡讀寫),會主動讓出 CPU(屬於 “協作式讓出”),但整體調度機制仍為搶佔式(其他就緒線程會被搶佔式調度執行)。 |
3. 非搶佔式調度
|
項目
|
具體內容
|
|
核心結論
|
當線程數 ≤ CPU 內核數時,操作系統對線程的調度以 “非搶佔為主,必要時搶佔”(多數場景下線程可在分配的核心上持續執行,減少強制切換)。 |
|
原因分析
|
現代操作系統雖默認採用搶佔式調度機制,但此時線程數不超過核心數,每個線程可分配到獨立的 CPU 核心,無需共享核心資源: - 若線程均為 CPU 密集型且優先級相同,操作系統通常不會主動搶佔,允許線程在核心上持續執行(減少切換開銷); - 僅在特殊場景(如高優先級線程就緒、當前線程觸發中斷或阻塞)時,才會觸發搶佔式切換。 |
|
具體表現
|
- 線程可在分配的 CPU 核心上 “長時運行”,時間片限制較寬鬆(甚至不嚴格耗盡時間片),上下文切換極少; - 若某線程因 IO 操作阻塞或主動讓出 CPU(如 - 高優先級線程就緒時,仍會搶佔低優先級線程的核心(保證實時性)。 |
|
補充説明
|
- 此場景下 CPU 利用率接近 100%(無閒置核心),且上下文切換開銷極低,適合 CPU 密集型任務(如數值計算、圖像處理); - “非搶佔為主” 不代表完全無搶佔,操作系統仍會通過搶佔機制保證高優先級任務的響應性(如實時線程、中斷處理); - 若線程數小於核心數,空閒核心會處於 “idle” 狀態,操作系統可能將線程遷移到空閒核心以平衡負載(非搶佔,屬於主動調度優化)。 |
注意:本質上還是採取搶佔式(還是有時間片的設置),只是每個線程可以得到單獨的CPU核心,時間片很長罷了!!!表現為操作系統不頻繁調度CPU資源。
六、CPU調度與線程切換的關係
線程切換是操作系統進行 CPU 調度時的核心操作之一,二者緊密關聯但不完全等同。
具體關係拆解:
- CPU 調度(Scheduling) 是一個決策過程:操作系統的調度器根據策略(如優先級、時間片、公平性等),從就緒隊列中選擇下一個要在 CPU 上執行的線程。例如:當線程時間片耗盡、被高優先級線程搶佔、或主動阻塞時,調度器需要決定 “接下來讓哪個線程運行”。
- 線程切換(Context Switch) 是執行過程:當調度器選定下一個線程後,操作系統會實際執行 “從當前線程切換到目標線程” 的動作 —— 保存當前線程的狀態(寄存器、程序計數器、棧指針等),加載目標線程的狀態,讓其在 CPU 上繼續執行。
總結:
線程切換是 CPU 調度過程中 “執行決策” 的關鍵步驟,沒有線程切換,調度器的決策無法落地;而 CPU 調度則包含了 “選擇線程” 和 “執行切換” 的完整邏輯。因此,線程切換可以理解為操作系統 CPU 調度機制中最核心的操作環節。
七、要點(核心總結)
1. IO密集型、CPU密集型
IO密集型表現為頻繁IO等待(非本線程處理數據):在高性能網絡中、我們需要做到不阻塞,避免浪費CPU性能(可以採用非阻塞IO加epoll、多線程、協程)
CPU密集型表現為頻繁數據處理(本線程處理數據):我們需要做到充分利用CPU的核數、因為一線程只能對應一核,我們可以採用多線程(注意線程數小於等於核數,避免線程切換開銷)、多進程的處理方案。
2. CPU的調度情況
線程的狀態和個數與系統對CPU資源的調度息息相關:
我們必須理解線程數的設置如何影響CPU調度的頻率、同時理解操作系統是如何根據線程狀態來進行CPU資源的分配的!!!
3. 以上的知識有助於後面理解協程
大家一定要理解上面這些,因為內核的操作開銷都是比較大的(我們必須想辦法降低開銷)。
線程的數量的限制都是為了性能:線程數量大了、佔用內存大、同時切換開銷大(延遲高、不適合高併發IO場景)、切換規則也不容易更改。
瞭解了這些限制你才會明白為什麼需要在用户態又搞個協程。
協程又稱為輕量級線程,它也有輕量棧、狀態、調度器(類似一個管理CPU分配的內核)。它相比於線程(內核管理,開銷大、不靈活)、更加靈活輕量、CPU的分配完全由用户自定義。
(我會在後續博文詳細介紹協程給大家,包括協程的優點、理念、以及協程庫的實現和妙用!!!!)