cgo获取errno

By | 2014 年 08 月 30 日

在go中使用cgo调用c函数是非常方便的,但是如果要获取调用的函数产生的错误却并不容易。虽然go语言支持多个返回值,但是c却不能。因此一般来讲要获取errno只能按照c的方法,取出errno的值,来做下一步操作。
而如果采用c的方法,那么就会产生一个问题,如果调用的c方法,和取得errno之间产生了goroutine的调度,更有甚者,这个goroutine被调度到了另一个系统thread上运行,那么,errno的值可能就不准确了。因此需要一个完美的方法将c函数的调用和取得errno的操作安排在同一个系统thread上并原子性地完成,则才能满足取出errno的任务。

最开始的时候,本人想通过runtime.LockOSThread()将两个操作限定在同一个线程中来完成。但是当找到下面的方法后,这个方法被证明并不一定能够确保两个操作限定在同一个线程内执行。

下面说的方法是本人在看zeromq的cgo(https://github.com/pebbe/zmq4)调用后,经测试而形成的。
具体的代码非常简单

其中最主要的是调用sleep函数的时候,可以获取到两个返回值,第一个是c函数的返回值,而第二个就是我们想要的errno。而且经过后面的代码也能看出来,这个errno确实是真正的errno。这样就通过一次c调用,就获取到了两个我们想要的值。

那么为什么第一种方法有问题呢?这个需要去看go的cgo调用代码(src/pkg/runtime/cgocall.c)。其中runtime·cgocall函数中有一句runtime·lockOSThread();会将当前goroutine和系统thread绑定,这和第一种方法的思想一样。当执行到最后一步的时候,cgo会执行endcgo,而其中有runtime·unlockOSThread();。这个时候问题来了,这会把之前两次的lock操作unlock掉(一次是我们的lock,而另一次是cgocall的lock,lock仅仅将g和m做了绑定,具体可以查看c代码)。因此当c函数执行完毕,轮到我们获取errno的时候,goroutine已经不和thread绑定了,故非常有可能获取到错误的errno值。也就说明了第一种办法是有问题的。

总结起来就是,如果要获取cgo的errno,直接使用两个返回值即可,非常简单~~

2014.8.31 update:

发表评论