【问题标题】:calling IO Operations from thread in ruby c extension will cause ruby to hang从 ruby​​ c 扩展中的线程调用 IO 操作将导致 ruby​​ 挂起
【发布时间】:2014-09-16 21:46:10
【问题描述】:

我在使用 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​​-code 作为回调,一旦线程接收到信号就会执行。

一般来说,如果回调是这样的 ruby​​-code:

1 + 1

但是,如果回调 ruby​​ 代码如下所示:

puts "test"

一旦回调被执行,主 ruby​​ 进程将停止响应。 线程仍在运行并且能够对信号做出反应并每次进行“测试”,线程接收到消息。

谁能告诉我,如何解决这个问题?

非常感谢

【问题讨论】:

  • 我认为它不受支持,MRI 有 GIL 是有原因的:我猜它至少会干扰 GC。您可以尝试向 Ruby 邮件列表询问,在我看来这是一个更合适的地方
  • 嘿,据我了解 GIL,每次只有一个线程能够进行 IO。这对我来说完全没问题。但不知何故,在我的线程中的 IO 完成后,主程序无法继续工作。没关系,如果主进程暂时停止工作,另一个线程正在做 IO,但之后,它应该继续工作。所以也许我必须在线程中执行 ruby​​ 代码后释放 GIL?
  • 您尝试做的事情很有趣,但这与我对 Ruby 内部原理的理解相去甚远,而且我认为您不会在 SO 上找到如此专精于 Ruby 的人来回答您;我建议尝试Ruby mailing list 如果你这样做,请告诉我,我想关注讨论;)

标签: c ruby multithreading ruby-c-extension


【解决方案1】:

From the Ruby C API docs:

从 Ruby 1.9 开始,Ruby 支持一个内核的原生 1:1 线程 每个 Ruby 线程对象的线程。目前,有一个 GVL(Global VM Lock) 防止同时执行 Ruby 代码,这可能是 由 rb_thread_call_without_gvl 和 rb_thread_call_without_gvl2 函数。这些功能是 难以使用并记录在 thread.c 中;以前不要使用它们 在 thread.c 中读取 cmets。

TLDR; Ruby VM 当前(在撰写本文时)不是线程安全的。查看 this nice write-up on Ruby Threading 以更好地全面了解如何在这些范围内工作。

您可以使用 Ruby 的 native_thread_create(rb_thread_t *th),它将在幕后使用 pthread_create。您可以在方法定义上方的文档中了解一些缺点。然后,您可以使用 Ruby 的 rb_thread_call_with_gvl 方法运行回调。另外,我还没有在这里做,但是创建一个包装器方法可能是个好主意,这样您就可以使用rb_protect 来处理您的回调可能引发的异常(否则它们将被 VM 吞没)。

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);

【讨论】:

    猜你喜欢
    • 2011-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-30
    相关资源
    最近更新 更多