Вызов операций ввода-вывода из потока в расширении 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 C API:

Начиная с 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);
Другие вопросы по тегам