PostgreSQL 的机制(注册与分配分离)
在 PG 中,RegisterBackgroundWorker在_PG_init()时只做纯内存记录,完全不访问共享内存:
// PG: src/backend/postmaster/bgworker.c static slist_head BackgroundWorkerList = SLIST_STATIC_INIT(BackgroundWorkerList); void RegisterBackgroundWorker(BackgroundWorker *worker) { // 仅 malloc 一个副本,挂到静态链表 BackgroundWorkerList 上 // 完全不碰共享内存!safe to call from _PG_init() } // 之后在 CreateSharedMemoryAndSemaphores 中: void BackgroundWorkerShmemInit() { // 把 BackgroundWorkerList 上的 worker 复制到共享内存 }PG 的设计是两阶段解耦:先注册到静态链表(_PG_init时),后迁移到共享内存(CreateSharedMemoryAndSemaphores时)。
openGauss 的机制(注册即分配)
openGauss 的RegisterBackgroundWorker则完全不同— 它直接操作g_instance.bgw_base,并立即 fork 线程:
// openGauss: src/gausskernel/process/postmaster/bgworker.cpp:406-451 bool RegisterBackgroundWorker(BgWorkerContext *bwc, int flag) { BGW_HDR* bgworker_base = (BGW_HDR *)g_instance.bgw_base; // ← 第 408 行,NULL 就崩! bgw = GetFreeBgworker(); // ← 第 416 行,访问 bgworker_base->free_bgws bgw->bgw_id = pg_atomic_fetch_add_u64( &bgworker_base->bgw_id_seq, 1); // ← 第 423 行,访问原子变量 // ... bgw->bgw_notify_pid = initialize_util_thread(BGWORKER, bwa); // ← 第 436 行,直接 fork 线程! }g_instance.bgw_base只在InitBgworkerGlobal()(bgworker.cpp:82-114)中初始化,而它被调用于CreateSharedMemoryAndSemaphores()(ipci.cpp:364)——在process_shared_preload_libraries()之后。
头文件注释甚至也是误导的
bgworker.h:96-97 写着:
/* Register a new bgworker during shared_preload_libraries */ extern bool RegisterBackgroundWorker(BgWorkerContext *bwc, int flag = 0);这个注释是从 PG 复制过来的,但实现上根本无法在shared_preload_libraries期间工作。
根本原因:openGauss 的 bgworker 用途不同
openGauss 的 background worker 机制主要是为运行时并行操作(如 redistribution 期间的并行索引构建)设计的,调用路径是:
某个 backend 执行 SQL → LaunchBackgroundWorkers(nworkers, ...) // 运行时调用 → RegisterBackgroundWorker(bwc, flag) // 此时共享内存早已就绪而非 PG 那种 postmaster 启动时就注册好、等待 postmaster 来拉起 worker 进程的模式。
解决方案:用shmem_startup_hook作为桥梁
既然InitBgworkerGlobal()在 ipci.cpp:364 执行,而shmem_startup_hook()在 ipci.cpp:559-560 执行(在CreateSharedMemoryAndSemaphores的最末尾),此时g_instance.bgw_base已经就绪。所以移植方案是:
// 你的扩展中: static bgworker_main_type prev_shmem_startup_hook = NULL; static void my_shmem_startup(void) { // 先调用链上的前一个 hook(如果有) if (prev_shmem_startup_hook) prev_shmem_startup_hook(); // 此时 g_instance.bgw_base 已初始化,可以安全注册 worker 了! LaunchBackgroundWorkers(nworkers, my_shared_data, my_worker_main, my_worker_exit); } void _PG_init(void) { // 不能在这里调用 RegisterBackgroundWorker!会崩! // 改为:安装 shmem_startup_hook prev_shmem_startup_hook = t_thrd.storage_cxt.shmem_startup_hook; t_thrd.storage_cxt.shmem_startup_hook = my_shmem_startup; }时序对比
PG: _PG_init() → RegisterBackgroundWorker() → 写入静态链表 ✓ ↓ CreateSharedMemoryAndSemaphores() → BackgroundWorkerShmemInit() → 迁移到共享内存 openGauss (当前): _PG_init() → RegisterBackgroundWorker() → g_instance.bgw_base 为 NULL → COREDUMP ✗ openGauss (workaround): _PG_init() → 只安装 shmem_startup_hook ✓ ↓ CreateSharedMemoryAndSemaphores() ├─ InitBgworkerGlobal() ← g_instance.bgw_base 就绪 ├─ ... 所有核心结构初始化 ... └─ shmem_startup_hook() ← 此时调用 RegisterBackgroundWorker ✓总结:这不是 bug,而是 openGauss 对 bgworker 机制的彻底重设计。PG 的RegisterBackgroundWorker是"声明式注册"(先记账,后分配),openGauss 的是"立即分配+立即 fork"(必须在共享内存就绪后)。移植 PG 扩展时,如果用了RegisterBackgroundWorker,需要改为通过shmem_startup_hook延迟到共享内存初始化完成后执行。
如果移植pg扩展,_PG_init()中有RegisterBackgroundWorker调用,直接注释掉,改为内置辅助线程方式启动即可。
对于RegisterDynamicBackgroundWorker调用,如果是临时执行可以调用LaunchBackgroundWorkers->RegisterBackgroundWorker。需要作为后台线程长时间执行,可以调用SendPostmasterSignal给postmaster发消息创建线程执行,参考logicalrep_worker_launch函数。
LaunchBackgroundWorkers适合短期并行计算——如果你的 worker 就是做并行计算(比如并行索引构建、并行 scan),算完就退出,那用它最方便,它帮你把事务上下文、锁组这些都处理好了。
RegisterBackgroundWorker一般不直接使用,除非你想绕过LaunchBackgroundWorkers内置的 BgWorkerContext 构造逻辑,但几乎所有场景都被LaunchBackgroundWorkers覆盖了。
顺便提一下:
initialize_util_thread方案和LaunchBackgroundWorkers并不冲突——它们是两套独立的线程管理机制,服务于不同的场景。持久 daemon 用前者,并行计算用后者。