crystalruby共享实例教程:让Ruby和Crystal安全持有同一个对象 📅 2026/8/20 18:14:45 crystalruby共享实例教程让Ruby和Crystal安全持有同一个对象【免费下载链接】crystalrubyEmbed Crystal code directly in Ruby项目地址: https://gitcode.com/gh_mirrors/cr/crystalruby想让 Ruby 与 Crystal 共享同一个对象而不发生数据拷贝、不被垃圾回收误杀crystalruby 共享实例Shared Instance就是为你准备的答案。crystalruby 是一个可以把 Crystal 代码直接内联到 Ruby 中的开源 gem而共享实例是它最巧妙的功能之一通过引用计数与互斥锁Ruby 和 Crystal 可以安全地持有同一个对象既能双向读写又不会互相踩踏内存。本教程将用最通俗的语言带你从零理解并跑通这一机制。为什么需要共享同一个对象默认情况下crystalruby 在 Ruby 与 Crystal 之间传参是按值传递的——每次跨越语言边界都会发生一次隐式拷贝。对于整数、布尔这种小数据没问题但当你传递一个 100 万元素的数组、一个复杂的 Person 结构体时反复拷贝的开销会非常可观。共享实例解决的正是在这类场景下**如何不拷贝又能安全共用**的问题把对象的内存交给一套统一的引用计数机制管理双方只传递内存指针谁在用就加引用用完了就减引用从而避免对象被过早回收。共享实例的核心原理引用计数 互斥锁 crystalruby 实现了一套共享引用计数器保证同一对象在两种语言间安全使用、不会被 GC 提前回收。实现上主要依赖三个部件引用计数与内存分配内存的分配与释放集中在 allocator.rb 中管理它通过malloc/free分配共享内存并维护一份存活对象跟踪表便于调试与统计。互斥锁所有类型的引用计数操作都通过一个全局互斥锁串行化该锁定义在 arc_mutex.rb基于 pthread mutex 实现并在 type.rb 中以ARC_MUTEX暴露给所有类型使用。Reactor 调度为避免多线程下 Crystal 的 Fiber 调度器与 GC 出错所有 Ruby/Crystal 互操作调用都被复用到一个单线程的 reactor.rb 上执行GC 也会按操作次数、时间间隔与内存增量三个维度自动触发。一句话总结引用计数决定什么时候能回收互斥锁保证回收时没人正在用Reactor 保证大家都在同一条线程上操作。三者配合共享实例才真正安全。第一步用 CRType 定义一个可共享的类型 ️创建共享实例的入口是CRTypeDSL它允许你用纯 Ruby 语法定义 Crystal 类型。以 README 中的经典 Person 为例require crystalruby class Person CRType{ NamedTuple(name: String, age: Int32) } def greet_rb Hello from Ruby. My name is #{self.name.value} end crystallize :string def greet_cr Hello from Crystal, My name is #{self.name.value} end end person Person.new(name: Bob, age: 30) puts person.greet_rb # Ruby 方法读取对象 person.name Alice # Ruby 侧修改 puts person.greet_cr # Crystal 方法读取同一对象这里Person继承了CRType定义的类型同时还能像普通 Ruby 类一样定义实例方法。CRType的定义位于 adapter.rb 的CRType方法中底层由 typebuilder.rb 完成类型构建。NamedTuple 等复合类型的读写实现可以看 named_tuple.rb——注意它会在类型上自动生成name、name这类读写方法。第二步让实例方法跨界——crystallize 与 expose_to_crystal 共享实例的价值在于双向调用。在同一个类里你可以同时拥有两种方法crystallize把 Ruby 方法体编译成 Crystal 执行享受编译语言的速度expose_to_crystal把 Ruby 方法暴露给 Crystal 侧调用让 Crystal 也能回头用 Ruby 的灵活性。完整的交叉调用示例在测试文件 test_instance.rb 中例如capitalize_full_name_crCrystal 方法内部调用了first_name_crCrystal setter与last_name_rbRuby setter实现一个方法里同时驱动两种语言修改同一个对象crystallize def capitalize_full_name_cr self.first_name_cr first_name_cr.capitalize self.last_name_rb last_name_rb.capitalize end也就是说Crystal 改的字段Ruby 立刻能看到Ruby 改的字段Crystal 也随时能读。因为底层是同一个内存块没有拷贝所以天然一致。第三步大型容器也能共享吗试试 Array 共享实例不仅适用于结构体。以 array.rb 为代表的容器类型注释里就写着它是reference counted manually managed memory type. Shareable between Crystal and Crystal引用计数、手动管理内存、可在语言间共享的类型。通过命名类型实例化后数组也能按引用传递IntArray CRType{ Array(Int32) } crystallize def array_doubler(a: IntArray) a.map! { |x| x * 2 } end big_array Array.new(1_000_000) { rand(100) } int_array IntArray.new(big_array) # 包装成共享实例 array_doubler(int_array) # 按引用传递无拷贝IntArray.new之后传入 Crystal 侧时不再发生转换与拷贝而是直接共享内存。元素读写时非原始类型会自动进行引用计数的增减参见 array.rb 中[]与[]的注释保证元素也不会被提前回收。共享实例的安全保障你需要知道的边界 ⚠️内存由 Ruby 侧统一管理如 type.rb 顶部注释所述所有结构体的内存在 Ruby 中管理Crystal 侧通过 setter 拷贝值避免跨语言所有权混乱。多线程场景务必理解 Reactor默认所有 Crystal 调用都会经过单线程 Reactor 复用如果只是单线程程序可以在配置中开启single_thread_mode去掉同步开销见 reactor.rb。需要清理缓存时执行bundle exec crystalruby clean可重置生成的编译产物详见 CHANGELOG.md 与项目配置文档。性能收益共享到底值不值README 中的基准测试很能说明问题对一个 100 万元素数组做map!翻倍操作按值传递需要先拷贝整个数组而按引用传递直接操作共享内存。对于 CPU 密集、数据结构庞大的任务按引用传递往往能带来数量级的提升这正是共享实例最值得使用的场景——把传数据的成本降到接近零同时把重活交给编译后的 Crystal 完成。总结什么时候该用共享实例✅对象较大、需要频繁在 Ruby 与 Crystal 之间传递 → 用共享实例省去拷贝希望 Crystal 方法能直接读写 Ruby 侧创建的对象 → 用共享实例保证一致性想在类里混编crystallize与expose_to_crystal方法 → 共享实例是天然载体。crystalruby 的共享实例机制本质上是一套精心设计的跨语言内存所有权方案引用计数管生命周期互斥锁管并发Reactor 管调度。理解了这三点你就能放心地让 Ruby 和 Crystal 共同持有同一个对象各取所长、互不干扰。想动手试试克隆项目后运行rake testtest_instance.rb里还有更多跨语言交互的用例等你解锁【免费下载链接】crystalrubyEmbed Crystal code directly in Ruby项目地址: https://gitcode.com/gh_mirrors/cr/crystalruby创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考