线程管理#

另请参阅

线程管理API参考

线程池#

许多Arrow C++操作将工作分配到多个线程中,以利用底层硬件并行性。例如,当读取 Parquet 文件时,我们可以并行解码每一列。为了实现这一点,我们将任务提交给某种执行器。

在Arrow C++中,我们使用线程池进行并行调度,并在用户请求串行执行时使用事件循环。用户可以提供自己的自定义实现,但这属于高级概念,此处不作介绍。

CPU vs. I/O#

为了最大限度地减少上下文切换的开销,我们用于CPU密集型任务的默认线程池具有固定大小,默认为进程CPU亲和性(在Linux上)或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线程池的大小不太可能带来任何好处。在某些情况下,为了减少Arrow C++对与其他进程或用户线程共享的硬件的影响,减小CPU线程池的大小可能是有意义的。默认CPU线程池的大小可以通过OMP_NUM_THREADS环境变量或arrow::SetCpuThreadPoolCapacity()函数进行管理。

串行执行#

Arrow C++中可能使用线程的操作通常可以通过某种参数配置为串行运行。在这种情况下,我们通常将CPU执行器替换为由调用线程操作的事件循环。然而,许多操作将继续使用I/O线程池。这意味着即使请求串行执行,仍然可能发生一些并行性。

Jemalloc后台线程#

当使用jemalloc分配器时,jemalloc会创建少量后台线程来管理内存池。这些线程应该影响很小,但在运行Valgrind等分析工具时可能会显示为内存泄漏。这是无害的,可以安全地抑制,或者Arrow C++可以不使用jemalloc进行编译。

异步工具#

Future#

Arrow C++使用arrow::Future在线程之间传递结果。通常,当操作需要执行某种长时间运行的任务(会阻塞一段时间)时,会创建arrow::Futurearrow::Future对象主要用于内部使用,并且任何返回arrow::Future的方法通常也都有同步变体。