Чего я боюсь, так это того, что я вызываю подпрограмму C, затем после возврата подпрограммы C, но перед тем, как скопировать строку ошибки, планировщик Go решает, поменять текущую программу на другую. ...
Это разумная проблема?
Да. Оболочки cgo "call C code" блокируются на один поток POSIX / OS на время каждого вызова, но поток, который они блокируют, не фиксируется на все время; на самом деле он перебирает как бы для нескольких разных потоков с течением времени, пока ваши подпрограммы работают нормально. (Поскольку в текущих реализациях Go запланировано совместно, вы можете, в некоторых случаях, быть осторожными, чтобы не делать ничего, что могло бы позволить вам переключаться между потоками ОС, но это, вероятно, не очень хороший план.)
Вы можете использовать runtime.LockOSThread
здесь, но я думаю, что лучший план в противном случае:
как я могу обойти это?
Получите ошибку до того, как Go возобновит свой обычный алгоритм планирования (т. Е. Перед разблокировкой программы из потока C / POSIX).
cgo каким-то образом удается распространить значения errno ...
Он получает значение errno до разблокировки goroutine из потока POSIX.
Мой первоначальный план состоял в том, чтобы вызвать C через cgo, проверить, вернул ли вызов ошибку, и если да, обернуть строку ошибки, используя C.GoString
, чтобы преобразовать ее из необработанного символьного указателя в Go строка. Это будет выглядеть примерно так: C.GoString(C.get_error())
.
Если есть вариант, который принимает ошибку number (вместо того, чтобы вылавливать ее из переменной TLS), то план должен все еще работать: просто убедитесь, что ваши подпрограммы C предоставляют и возвращаемое значение, и номер ошибки.
Если нет, напишите свою собственную оболочку C, как вы и предлагали:
ftype wrapper_for_realfunc(char **errp, arg1type arg1, arg2type arg2) {
ftype ret = realfunc(arg1, arg2);
if IS_ERROR(ret) {
*errp = get_error();
} else {
*errp = NULL;
}
return ret;
}
Теперь ваша оболочка Go просто вызывает оболочку, которая заполняет указатель на C память дополнительным аргументом *C.char
, устанавливая его равным nil, если нет ошибки, и устанавливая его на что-то по который вы можете использовать C.GoString
в случае ошибки.
Если по какой-либо причине это невозможно, рассмотрите возможность использования runtime.LockOSThread
и его аналога runtime.UnlockOSThread
.