You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何暂停嵌入式Ruby解释器并在模拟器回调触发后恢复?

解决嵌入式Ruby解释器异步回调场景下的线程暂停/恢复问题

问题根源

rb_thread_stop()报错「ThreadError: stopping only thread」的核心原因是:Ruby不允许终止当前运行的最后一个线程(通常是主线程),因为这会直接导致整个解释器进程退出。你的场景中,调用rb_thread_stop()时当前线程是唯一活跃线程,因此触发该错误。

可行解决方案

方案1:使用纤程(Fiber)实现协作式暂停/恢复

纤程是Ruby的轻量级协作式并发机制,适合这种需要主动暂停、等待外部回调恢复的场景,无需多线程开销,也不会触发主线程停止的错误。

实现步骤:

  • 在Ruby层创建一个纤程,封装模拟器交互逻辑
  • 在C API中获取当前纤程的指针,保存到模拟器回调的上下文结构中
  • 当需要暂停Ruby执行时,调用rb_fiber_yield()让纤程让出执行权
  • 模拟器回调触发时,从上下文取出纤程指针,调用rb_fiber_resume()恢复纤程执行

C代码示例:

// 定义回调上下文,保存纤程指针和需要传递的数据
typedef struct {
    VALUE fiber;
    int simulator_result;
} CallbackContext;

// 模拟器回调函数
void simulator_callback(int result, void *ctx) {
    CallbackContext *context = (CallbackContext*)ctx;
    // 恢复纤程执行,传递模拟器结果
    rb_fiber_resume(context->fiber, INT2NUM(result), Qnil);
    // 释放上下文内存
    free(context);
}

// 封装的C API函数,供Ruby调用
VALUE set_input_and_pause(VALUE self, VALUE input_val) {
    // 检查是否在纤程内部调用
    if (rb_fiber_current() == rb_fiber_main()) {
        rb_raise(rb_eRuntimeError, "Must be called inside a fiber");
    }
    // 获取当前纤程
    VALUE current_fiber = rb_fiber_current();

    // 创建回调上下文
    CallbackContext *ctx = malloc(sizeof(CallbackContext));
    ctx->fiber = current_fiber;

    // 设置模拟器输入值(你的现有逻辑)
    set_simulator_input(NUM2INT(input_val));

    // 注册模拟器回调并启动模拟器
    register_simulator_callback(simulator_callback, ctx);
    start_simulator();

    // 让出纤程执行权,等待回调恢复
    return rb_fiber_yield(Qnil);
}

Ruby层调用示例:

fiber = Fiber.new do
  loop do
    # 设置输入并等待模拟器回调
    result = set_input_and_pause(100)
    puts "模拟器返回结果:#{result}"
    # 在这里设置下一轮输入,循环执行
  end
end

fiber.resume
# 主线程保持存活,避免解释器退出
loop { sleep 1 }

方案2:创建后台线程处理模拟器交互

通过创建一个后台线程专门处理模拟器的异步流程,主线程保持活跃,避免触发唯一线程停止的错误。

实现步骤:

  • 在Ruby层或C API中创建新线程
  • 在新线程内调用模拟器相关逻辑,使用rb_thread_stop()暂停线程
  • 模拟器回调触发时,调用rb_thread_wakeup()恢复线程执行

C代码示例:

// 回调上下文,保存线程指针
typedef struct {
    VALUE thread;
} ThreadCallbackContext;

void simulator_thread_callback(int result, void *ctx) {
    ThreadCallbackContext *context = (ThreadCallbackContext*)ctx;
    // 将结果存储到线程局部变量
    rb_thread_local_set(context->thread, rb_intern("simulator_result"), INT2NUM(result));
    // 唤醒暂停的线程
    rb_thread_wakeup(context->thread);
    free(context);
}

// 封装的C API函数,创建后台线程
VALUE start_simulator_thread(VALUE self, VALUE input_val) {
    // 创建后台线程,传入输入值
    VALUE thread = rb_thread_create(thread_proc, (void*)input_val);
    return thread;
}

// 线程执行的核心逻辑
VALUE thread_proc(void *input_ptr) {
    VALUE input_val = (VALUE)input_ptr;
    ThreadCallbackContext *ctx = malloc(sizeof(ThreadCallbackContext));
    ctx->thread = rb_thread_current();

    // 设置输入、注册回调、启动模拟器
    set_simulator_input(NUM2INT(input_val));
    register_simulator_callback(simulator_thread_callback, ctx);
    start_simulator();

    // 暂停线程,等待回调唤醒
    rb_thread_stop();

    // 获取回调传递的结果
    VALUE result = rb_thread_local_get(rb_thread_current(), rb_intern("simulator_result"));
    printf("模拟器返回结果:%d\n", NUM2INT(result));

    // 可在这里继续处理下一轮输入逻辑
    return Qnil;
}

关键注意事项

  • 无论使用纤程还是线程,都必须保证至少有一个活跃线程(通常是主线程)在运行,避免解释器退出
  • 回调函数中调用Ruby API时,必须确保处于Ruby的全局解释器锁(GIL)保护下,必要时调用rb_thread_call_with_gvl()包裹逻辑
  • 上下文内存的分配和释放要谨慎,避免内存泄漏

内容的提问来源于stack exchange,提问作者nPn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 22:43:13