執行緒管理#

執行緒池#

許多 Arrow C++ 操作會跨多個執行緒分配工作,以利用底層硬體平行性。例如,當讀取 Parquet 檔案時,我們可以平行解碼每個欄位。為了實現這一點,我們會將任務提交給某種類型的執行器。

在 Arrow C++ 中,我們使用執行緒池進行平行排程,並在使用者要求序列執行時使用事件迴圈。使用者可以提供自己的自訂實作,儘管這是一個進階概念,此處不涵蓋。

CPU 與 I/O#

為了最大限度地減少上下文切換的開銷,我們的 CPU 密集型任務的預設執行緒池具有固定大小,預設為 std::thread::hardware_concurrency。這表示 CPU 任務永遠不應長時間阻塞,因為這將導致 CPU 利用率不足。為了實現這一點,我們有一個單獨的執行緒池,應用於需要阻塞的任務。由於這些任務通常與 I/O 操作相關聯,因此我們將其稱為 I/O 執行緒池。此模型通常與非同步計算相關聯。

I/O 執行緒池的大小目前預設為 8 個執行緒,應根據 I/O 硬體的平行處理能力來調整大小。例如,如果大多數讀取和寫入發生在典型的 HDD 上,則預設值 8 可能已足夠。另一方面,當大多數讀取和寫入發生在 S3 等遠端檔案系統上時,通常可以從許多並行讀取中受益,並且可以通過增加 I/O 執行緒池的大小來提高 I/O 效能。預設 I/O 執行緒池的大小可以使用 ARROW_IO_THREADS 環境變數或 arrow::io::SetIOThreadPoolCapacity() 函數來管理。

增加 CPU 執行緒池的大小不太可能帶來任何好處。在某些情況下,減少 CPU 執行緒池的大小可能更有意義,以便減少 Arrow C++ 對與其他程序或使用者執行緒共用硬體的影響。預設 CPU 執行緒池的大小可以使用 OMP_NUM_THREADS 環境變數或 arrow::SetCpuThreadPoolCapacity() 函數來管理。

序列執行#

Arrow C++ 中可能使用執行緒的操作通常可以通過某種類型的參數配置為序列執行。在這種情況下,我們通常會將 CPU 執行器替換為由呼叫執行緒操作的事件迴圈。但是,許多操作將繼續使用 I/O 執行緒池。這表示即使請求序列執行,也可能仍然會發生一些平行處理。

Jemalloc 背景執行緒#

當使用 jemalloc 分配器 時,jemalloc 將建立少量背景執行緒來管理池。這些執行緒的影響應極小,但在執行 Valgrind 等分析工具時可能會顯示為記憶體洩漏。這是無害的,可以安全地抑制,或者可以在不使用 jemalloc 的情況下編譯 Arrow C++。

非同步工具程式#

Future#

Arrow C++ 使用 arrow::Future 在執行緒之間傳達結果。通常,當操作需要執行某些長時間運行的任務,並且這些任務將會阻塞一段時間時,將會建立 arrow::Futurearrow::Future 物件主要用於內部使用,並且任何傳回 arrow::Future 的方法通常也會有一個同步變體。