cgo Взаимодействие с C Библиотека, которая использует локальное хранилище потоков - PullRequest
2 голосов
/ 25 апреля 2020

Я нахожусь в процессе упаковки библиотеки C с cgo, чтобы ее можно было использовать с нормальным кодом Go.

Моя проблема в том, что я хотел бы распространять ошибку строки до API Go, но рассматриваемая библиотека C делает строки ошибок доступными через локальное хранилище потока; есть глобальный get_error() вызов, который возвращает указатель на локальные символьные данные потока.

Мой первоначальный план состоял в том, чтобы позвонить в C через cgo, проверить, вернул ли вызов ошибку, и если это так, оберните строку ошибки, используя C.GoString, чтобы преобразовать ее из необработанного символьного указателя в строку Go. Это выглядело бы примерно как C.GoString(C.get_error()).

Проблема, которую я здесь предвидел, заключается в том, что TLS в C работает на уровне собственных потоков ОС, но в моем понимании вызывающий код Go будет исходить из одной из потенциально N подпрограмм, которые мультиплексируются через некоторое количество базовых собственных потоков в пуле потоков, управляемых планировщиком Go.

Я боюсь, что могу столкнуться с ситуацией, когда я вызываю в процедуру C, затем после возврата процедуры C, но перед тем, как скопировать строку ошибки, планировщик Go решает заменить текущую программу на другую. Когда исходная программа будет возвращена обратно, она может быть в другом нативном потоке, насколько я знаю, но даже если она будет переключена обратно в тот же поток, любые выполняемые там процедуры за прошедшее время могли изменить состояние TLS, в результате чего я загружаю строку ошибки для несвязанного вызова.

У меня такие вопросы:

  • Это разумная проблема? Не понимаю ли я что-то о планировщике go или о том, как он взаимодействует с cgo, что может привести к тому, что это не станет проблемой?
  • Если это разумная проблема, как я могу это сделать? обойти это?
    • cgo каким-то образом удается передать значения errno обратно в вызывающий код Go, которые также хранятся в TLS, что заставляет меня думать, что для этого должен быть безопасный способ.
    • Я не могу придумать, каким образом сам код C мог бы быть вытеснен планировщиком go, поэтому я должен ввести функцию-оболочку C и заставить ИТ сделать необходимый вызов, а затем условно скопировать строка ошибки перед возвратом обратно в goland?

Меня интересует любое решение, которое позволило бы мне распространить строки ошибок до остальной части Go, но я надеюсь избежать любого решения, которое потребовало бы от меня сериализации доступа к TLS, так как добавление блокировки только для захвата строки ошибки кажется мне очень неудачным.

Заранее спасибо!

1 Ответ

2 голосов
/ 26 апреля 2020

Чего я боюсь, так это того, что я вызываю подпрограмму 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...