вызов операций ввода-вывода из потока в расширении 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 ​​перестанет отвечать после выполнения обратного вызова. Поток все еще работает и может реагировать на сигналы и ставит «тест» каждый раз, когда поток получает сообщение.

Может кто подскажет, как это исправить?

Большое спасибо


person Racer    schedule 16.09.2014    source источник
comment
Я не думаю, что это поддерживается, у MRI есть причина иметь GIL: я думаю, что это, по крайней мере, мешает GC. Вы можете попробовать обратиться в список рассылки Ruby, мне кажется, это более подходящее место.   -  person mdesantis    schedule 17.09.2014
comment
Эй, насколько я понял GIL, речь идет о том, что только один поток может выполнять ввод-вывод за раз. Это было бы совершенно нормально для меня. Но почему-то после того, как IO в моем потоке был сделан, основная программа не продолжает работать. Ничего страшного, если основной процесс перестанет работать на время, пока другой поток выполняет ввод-вывод, но после этого он должен продолжать работать. Так что, может быть, мне нужно выпустить GIL после того, как я выполнил рубиновый код в потоке?   -  person Racer    schedule 17.09.2014
comment
То, что вы пытаетесь сделать, интересно, но это далеко от моего понимания внутренностей Ruby, и я не думаю, что вы найдете на SO кого-то, кто настолько специализируется на Ruby, чтобы ответить вам; Я бы посоветовал попробовать в списке рассылки Ruby Дайте мне знать если вы это сделаете, я хотел бы следить за обсуждением ;)   -  person mdesantis    schedule 17.09.2014


Ответы (1)


Из документации Ruby C API:

Начиная с Ruby 1.9, Ruby поддерживает нативную многопоточность 1:1 с одним потоком ядра на каждый объект Ruby Thread. В настоящее время существует GVL (глобальная блокировка виртуальной машины), которая предотвращает одновременное выполнение кода Ruby, который может быть выпущен функциями rb_thread_call_without_gvl и rb_thread_call_without_gvl2. Эти функции сложны в использовании и задокументированы в файле thread.c; не используйте их, пока не прочитаете комментарии в thread.c.

TLDR; виртуальная машина Ruby в настоящее время (на момент написания) не является потокобезопасной. Прочтите эту замечательную статью о Ruby Threading, чтобы лучше понять, как работать в этих рамках.

Вы можете использовать native_thread_create(rb_thread_t *th) Ruby, который использовать 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);
person codenamev    schedule 06.12.2018