ClickHouse源码初探(shared_mutex与线程同步)
这篇文章从 ClickHouse 自实现 DB::SharedMutex 出发,对比了 std::shared_mutex、libc++ 版本、pthread rwlock 与 futex 机制,核心是解释“实现差异如何影响实测吞吐”。研究动机与问题设定发现 ClickHouse 在 Linux 上使用 futex 读…
1. 事件起因 在 clickhouse 源码中发现他自己实现了 DB::SharedMutex,而在非 Linux 环境才使用标准库的 std::shared_mutex(刚刚查看最新分支代码发现非 Linux 下也使用的 absl 的实现)。而其中自实现的读写锁使用 futex(linux特有的API),这是一个我之前从未听过的名词,我对 futex 以及标准库的 shared_mutex 真的性能这么差吗?感到非常的好奇。于是便有了本篇文章。 有关 futex 是个什么?其全称是 Fast Userspace Mutex,很明显是减少内核陷入次数的同步工具,具体细节请查看 Futex 。 标准库 shared_mutex 的性能真的有那么差吗?1.1 性能测试 这是我实际测试的对比情况(机器是32核,linux 6.6.80 glibc2.38,均使用 clang-19 -O3 编译): 测试代码: source | 场景 | std::shared_mutex | clang::shared_mutex | DB::SharedMutex | 性能领先方 | | ------------- | ----------------------- | ------------------------- | --------------------- | ------------------ | | 64读, 0写 | 2,842,706 | 1,270,939 | 3,047,856 | 🟢 DB高7.2% | | 0读, 64写 | 29,297 | 28,479 | 31,901 | 🟢 DB高8.9% | | 64读, 16写 | 3,028,239 | 730,139 | 122,343 | 🟡 std高24.7倍 | | 16读, 64写 | 1,549,032 | 73,387 | 34,014 | 🟡 std高45.5倍 | | 64读, 1写 | 3,029,552 | 771,498 | 3,027,012 | ⚖️ std与DB持平 | | 1读, 64写 | 30,617 | 29,291 | 31,969 | 🟢 DB高4.4% | 我本来是只测了标准库中的实现和 ClickHouse 中的 DB::SharedMutex,但测下来发现性能反而是标准库的更好,我记得我看过 clang 的 libcxx 实现,就是一个 mutex+两个 cv +一个 state 变量来实现,按理来说应该是不可能比直接基于 futex+atomic 的 DB::SharedMutex 要好啊。于是我查看该项目索引到的 std::shared_mutex 实现,发现不对劲,怎么到了 gcc 的 libcxx 实现了?(我明明用的 llvm19 toolchain,发现是在 llvm 那个目录下既有 clang又有 gcc 的 libcxx) 而 gcc 的 std::shared_mutex 实现如下:#if _GLIBCXX_USE_PTHREAD_RWLOCK_T typedef void* native_handle_type; native_handle_type native_handle() { return _M_impl.native_handle(); } private: __shared_mutex_pthread _M_impl; #else private: __shared_mutex_cv _M_impl; #endif }; #endif // __cpp_lib_shared_mutex 在 Linux 下默认会使用 pthread_rwlock ,而不是另一个基于 cv 实现的版本。 虽然不知道基于什么原因,我这个基于 llvm toolchain 的项目使用的是 gcc 的 libcxx,但不妨碍我模拟 clang libcxx 的实现(直接找到 llvm-project 的源码即可)。 由于直接 copy 对应的文件依赖的头文件太多了,于是直接让 ai 查看源码逻辑生成了一个一样的实现,也放在测试源码的仓库中,测试结果就是上表中的 clang::shared_mutex。 果然 clang libcxx 的实现性能是最差的。但我也还是对为什么 ClickHouse 的实现打不过 pthread_rwlock 感到好奇,当然也不是完全打不过,是在有一些写操作的时候会吞吐量会落后,读操作远大于写操作的时候性能会更好。 我怀疑原因如下:pthread_rwlock 是读者优先的(事实证明分析是正确…
正在初始化 WebAssembly 引擎…
首次编译原生模块可能需要数秒
就绪后,页面交互将以接近原生的速度运行