Вызов операций ввода-вывода из потока в расширении ruby c приведет к зависанию ruby
У меня проблема с использованием потоков в расширении C для запуска асинхронного кода ruby.
У меня есть следующий код C:
struct DATA {
VALUE callback;
pthread_t watchThread;
void *ptr;
};
void *executer(void *ptr) {
struct DATA *data = (struct DATA *) ptr;
char oldVal[20] = "1";
char newVal[20] = "1";
pthread_cleanup_push(&threadGarbageCollector, data);
while(1) {
if(triggerReceived) {
rb_funcall(data->callback, rb_intern("call"), 0);
}
}
pthread_cleanup_pop(1);
return NULL;
}
VALUE spawn_thread(VALUE self) {
VALUE block;
struct DATA *data;
Data_Get_Struct(self, struct DATA, data);
block = rb_block_proc();
data->callback = block;
pthread_create(&data->watchThread, NULL, &executer, data);
return self;
}
Я использую это, потому что я хочу предоставить код ruby в качестве обратного вызова, который будет выполнен, как только поток получит сигнал.
В целом, это работает нормально, если обратный вызов выглядит примерно так:
1 + 1
Но, если обратный вызов ruby-кода выглядит так:
puts "test"
чем главный процесс ruby перестанет отвечать, как только будет выполнен обратный вызов. Поток все еще работает и способен реагировать на сигналы и ставит "тест" каждый раз, когда поток получает сообщение.
Может кто-нибудь может сказать мне, как это исправить?
большое спасибо
0 ответов
Начиная с Ruby 1.9, Ruby поддерживает потоковую передачу 1:1 с одним потоком ядра на объект Ruby Thread. В настоящее время существует GVL (Global VM Lock), который предотвращает одновременное выполнение кода Ruby, который может быть освобожден функциями rb_thread_call_without_gvl и rb_thread_call_without_gvl2. Эти функции сложны в использовании и документированы в thread.c; не используйте их до прочтения комментариев в thread.c.
TLDR; В настоящее время Ruby VM не является (на момент написания) потокобезопасным. Посмотрите эту хорошую статью о Ruby Threading, чтобы лучше понять, как работать в этих рамках.
Вы можете использовать Ruby's native_thread_create (rb_thread_t * th), который будет использовать pthread_create
за кулисами. Есть некоторые недостатки, о которых вы можете прочитать в документации выше определения метода. Затем вы можете запустить обратный вызов с помощью метода Ruby rb_thread_call_with_gvl. Кроме того, я не делал этого здесь, но было бы неплохо создать метод-обертку, чтобы вы могли использовать rb_protect
для обработки исключений, которые может вызвать ваш обратный вызов (иначе они будут проглочены виртуальной машиной).
VALUE execute_callback(VALUE callback)
{
return rb_funcall(callback, rb_intern("call"), 0);
}
// execute your callback when the thread receives signal
rb_thread_call_with_gvl(execute_callback, data->callback);