Ruby 3 MRI and GIL

Probably you have heard from someone that Ruby doesn't scale, they are probably referring to the thread-safety of MRI. At the beginning, Ruby hasn't support native threads, Ruby 1.8.7 MRI version supports green threads— lightweight threads that run in user space. So, if it supports green threads it could be good, but not exactly. Green threads still run on a single native thread, which means you are not really running things in parallel. At Ruby 1.9 + it starts supporting native threads.

When MRI version was released supporting native threads… It was awesome! Until everyone know this version implemented over what's known as a Global Interpreter Lock(GIL). Its purpose is to ensure that just one thread can run for one process. Basically, it keeps a “global lock”. We have some approaches to prevent race conditions, deadlocks and some concurrent problems. One coarse-grained approach for solving those problems is GIL.

It’s important to mention that on Ruby MRI, GIL only applies to Ruby Operations, it makes sense because it exists to protect the integrity of the Ruby Virtual Machine.

Why we need to use GIL?

  • It makes developer’s lives easier(it’s harder to corrupt data)
  • it avoids race conditions within C extensions (it improves compatibility with C libraries that aren't thread-safe)
  • Easy of implementation

In the last few years we are seeing lots of new languages that have concurrency abstractions built-in at the core of the language. It seems, now on Ruby 3 it should come up with some related features. As we can see above:

As I mentioned, the approaches vary related to the granularity. If you use a fine-grained locking, every separated structure has a lock. GIL is a coarse-grained locking where one lock protects everything.

There are pros and cons related to each of these approaches. Fine-grained locking is good for parallelism(for example, two threads can run at the same time when they don’t share any resource), however there is a much bigger administrative overhead. Corse grained approach there is no much overhead, but two threads cannot run at the same time(even they don’t share any resource).

Let’s see some approaches and see how it may be the Ruby 3.0 approach.

Actors

One implementation: https://celluloid.io/ here you can see a basic usage.

Ownership model

You don’t have to worry about how to share the memory. Just the own object can destroy itself. Some examples of Ownership model in Rust.

Stream / Pipeline

Stream model is pretty simple model, however it doesn’t cover 100% of concurrency(According to Matz).

According to the Ruby language Creator, he will remove GIL and it looks like Ruby 3 will have the Stream/Pipeline approach with the introduction of a new operator: |>. It’s not sure.

The streem language is a proof of concept from the Stream approach. You should take a look on this:

References:

Show your support

Clapping shows how much you appreciated franzejr’s story.