谨慎在多进程中使用Go生成的动态库

博客讲述了在OpenSIPS中调用Go语言编译的动态库时遇到的死锁问题。原因是Go的动态库在多进程环境中导致的,由于Go runtime在子进程中变为多线程,而fork时只复制了当前线程,引发死锁。解决方案是在实际调用动态库时才显式加载,避免在父进程中加载。

之前写过一篇《C语言调用Go生成的动态库中的函数》,实际在OpenSIPS中使用时,却发现程序经常会“陷入”动态库函数的调用。

使用GDB附加到进程,函数调用block时堆栈如下:

#0  runtime.futex () at /usr/local/go/src/runtime/sys_linux_amd64.s:520
#1  0x00007f13340e7d16 in runtime.futexsleep (addr=0xfffffffffffffe00, val=0, ns=-1) at /usr/local/go/src/runtime/os_linux.go:44
#2  0x00007f13340c5367 in runtime.notesleep (n=0xfffffffffffffe00) at /usr/local/go/src/runtime/lock_futex.go:160
#3  0x00007f13340f10ea in runtime.mPark () at /usr/local/go/src/runtime/proc.go:1439
#4  0x00007f13340f2d65 in runtime.stoplockedm () at /usr/local/go/src/runtime/proc.go:2602
#5  0x00007f13340f483d in runtime.schedule () at /usr/local/go/src/runtime/proc.go:3299
#6  0x00007f13340f4f8d in runtime.park_m (gp=0xc000000680) at /usr/local/go/src/runtime/proc.go:3516
#7  0x00007f1334115ec5 in runtime.mcall () at /usr/local/go/src/runtime/asm_amd64.s:307
#8  0x00007ffc3c0d4398 in ?? ()
#9  0x00007ffc3c0d43b0 in ?? ()
#10 0x0000000000000000 in ?? ()

看起来像是发生死锁了。为什么单元测试中表现正常的Go动态库,在OpenSIPS调用时却发生死锁了呢?

第一时间就怀疑和OpenSIPS的多进程架构有关,打开google搜索关键字:"go library"、"fork"、"process"。很好,立马找到github上三个golang的issue:

Golang Shared library behavior in forked processes · Issue #16855 · golang/go · GitHub

Goroutines cause deadlocks after `fork()` when run in shared library · Issue #15538 · golang/go · GitHub

https://github.com/golang/go/issues/15556

总结一下:

1. 首先,在多线程中使用fork是一件很危险的事情,Linux Man Page中对fork的说明:

The child process is created with a single thread--the one that called fork(). The entire virtual address space of the parent is replicated in the child, including the states of mutexes, condition variables, and other pthreads objects; the use of pthread_atfork(3) may be helpful for dealing with problems that this can cause.

fork的时候只复制当前线程到子进程。假设在fork之前,一个线程对某个互斥锁进行lock操作,即持有了该锁,然后另外一个线程调用了fork创建子进程。可是在子进程中持有那个锁的线程却”消失”了,从子进程的角度来看,这个锁被“永久”的上锁了,因为它的持有者“蒸发”了。这是导致死锁问题的根源。

2. Go的动态库是隐式加载的,也就是说单线程的OpenSIPS加载动态库后由于Go的runtime变成了多线程,随后fork出各个工作进程,导致了在业务调用时runtime的死锁问题。

我采用的解决方法是这样的:在业务实际调用到动态库时,再显式加载动态库。对于我的场景,是在HTTPD这个工作进程中加载Go动态库。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值