Ruby's GIL in a nutshell
Tags: ruby, gil, concurrency
Published: 2017-09-11 07:12:20

---
title: Ruby's GIL in a nutshell
published: true
description: What is this dreaded GIL everybody is talking about?
tags: ruby, gil, concurrency
---
*Note: Even though they are technically __not__ the same, I use* interpreter *and* runtime *interchangeably. Know I am referencing [MRI](https://en.wikipedia.org/wiki/Ruby_MRI)*.

Maybe you've heard that there is this one thing that is stopping our beloved language from being faster. The GIL (Global Interpreter Lock) is a feature of the Ruby interpreter that prevents data from being accessed by multiple threads at once; in other words - it forbids parallelism with shared memory, a popular strategy for speeding up software.

# But wait, what are threads?
A thread is the context of a program's execution, keeping track of what state it's currently in. This allows you to assign a task to a thread, switch to another task (on another thread), finish that task and resume the original one.  Your operating system can put threads on different CPU cores, allowing them to run simultaneously and significantly improving the performance of your program.
By default, Ruby runs in one thread. No switching of tasks takes place.

So, say we have the following code
```ruby
number = 0
(1..5000).each do |num|  # loop #1
  number = number + 1
end
puts 'Loop #1 is finished!'
(1..5000).each do |num|  # loop #2
  number = number + 1
end
puts 'Loop #2 is finished!'

puts number
```

We know that the first loop will finish before the second for sure. Nothing gets run at the same time and nothing happens in between the loops.
```shell
> enether$ ruby loops.rb
Loop #1 is finished!
Loop #2 is finished!
10000
```
<img src="https://i.imgur.com/aSHBRms.png" />
*A sample diagram of what's going on*

Okay, how could we make use of that idle core? As one thread cannot be run on two cores at the same time, we'll need to create another thread! Ruby's `Thread` class is an abstraction of a thread that gets mapped to a native thread ran by your operating system.

```ruby
number = 0

(1..5000).each do |num|  # loop #1 
  number = number + 1
end
puts 'Loop #1 is finished!'

another_thread = Thread.new do
  (1..5000).each do |num|  # loop #2
    number = number + 1
  end
  puts 'Loop #2 is finished!'
end

puts number
```
Now we have moved our second loop into `another_thread` and everything else is left in the main thread. Loop #2 retains access to the number variable and is left to increment it alongside loop #1.
<img src="https://i.imgur.com/3CA3KEj.png"></img>
*What we __want__ to happen*

Cool, let's run this and make sure it works!
```shell
> enether$ ruby fast_loops.rb
Loop #1 is finished!
5000
```
*What the hell?*

Okay, I did not mention one other fact.
As soon as the main thread finishes, so does our program; all other threads are forcefully terminated without waiting for them to finish. To make sure our program waits for `another_thread` to do its thing, we introduce the `ThreadsWait` class by adding `require 'thwait'` and `ThreadsWait.all_waits(another_thread)` to our file. This tells Ruby to not end our program until all the threads given to the `all_waits` method finish execution.

```shell
> enether$ ruby fast_loops.rb
Loop #1 is finished!
Loop #2 is finished!
10000
```

Much better. Our code should now be running on two CPU cores at the same time, hence it should be twice as fast!

But actually it is not doing that.
This is where the dreaded GIL steps in. The GIL limits the amount of threads that can access the runtime to 1! That's right, only one thread can access the Ruby interpreter at any time. This essentially means that parallelism with shared memory (e.g both code having access to the `number` variable) is forbidden.
<img src="https://i.imgur.com/FFgzBtf.png"></img>

At this point you may be asking yourself - *"How does `another_thread` get run if the GIL limits access to one thread at a time?"*
Easy, the threads take turns in accessing the runtime. The currently running thread holds the GIL and releases it once it's appropriate.

This is called (voluntary) **context switching**.
As we said, threads store at what point of the program they're at and they can easily pause/resume their work. It would be unfair (and defeat the purpose of threads) if one thread hogged the CPU until it finished all of its work.

<img src="https://i.imgur.com/evhjKN3.png" />
*Voluntary context switches*

As you can see, our code is ran consecutively, no two lines are actually ever ran at the very same time. These is no way around this except for removing the lock.
In this scenario, our program is actually slower than it would have been without the threads, as switching from thread to thread is unnecessary overhead which does not serve us any purpose. 
We couldn't care less if we ran 200 iterations in loop #1, ran 5000 iterations in loop #2 and came back to finish loop #1. Regardless, there are scenarios where it makes sense to use threads but we will not touch them here.

# A GIL-less world
We know our standard Ruby interpreted ([MRI](https://github.com/ruby/ruby)) has the GIL but some other implementations do not! JRuby, implemented in Java, does not have such a thing. Let's run our code in there and see what happens.
```shell
> enether$ jruby faster_java_loops.rb
Loop #1 is finished!
Loop #2 is finished!
5454
```
*What the hell?*
```shell
> enether$ jruby faster_java_loops.rb
Loop #1 is finished!
Loop #2 is finished!
8580
> enether$ jruby faster_java_loops.rb
Loop #1 is finished!
Loop #2 is finished!
9162
```
**It produces a different result each time**

This code is not *thread-safe*. But what does that mean? What is actually happening here?

We have both our threads
<img src="https://i.imgur.com/LphV5nO.png" />

And we start running them, follow the diagram carefully
<img src="https://i.imgur.com/LvUOA99.png" />
As you can see, our thread can get paused **right** before an assignment. This is an OS context switch (involuntary context switch) scheduled by the kernel's scheduler and is unpredictable as well as unavoidable. The other thread can do as much work as it likes but it can get overwritten if a context switch happens at the wrong time.

This is what can happen with a lack of a GIL as JRuby shows.
With a GIL, our main thread would have taken the lock and even if the OS switched to the other thread, it would have been denied access to the interpreter, essentially forcing it to give up the CPU to another thread.
This is what the GIL is, an implementation detail of the Ruby runtime that guards it against problems introduced by multithreaded code. By having threads hold a lock on who can access their data, involuntary context switches do not do anything but slow our code down (which is better than breaking it).

It is extremely important to note that this does **not** make all Ruby code you write thread-safe. The sole purpose of the GIL is to protect the interpreter's internals from situations like the one depicted above.

Other benefits of the GIL include:
* Faster single-threaded programs
* It makes wrapping C libraries easier. You don't have to worry about thread-safety. If the library is not thread-safe, you simply keep the GIL locked while you call it.
* Simple garbage collection


# Summary
We saw that the GIL is a lock that prevents other threads from accessing the interpreter while another thread has it and got a vague idea of how it works with multiple threads - namely by sequential acquire/release operations.
We showed a simple example of why it is needed to protect MRI against OS context switches and how things might go wrong if it did not exist.
Hopefully this leaves you with a vague idea on why we need the GIL and a little bit less resentment, if not some respect, for it.

{% comment %}
## Bonus
If you'd like to learn a bit more on why we would use threads at all and see some common pitfalls with multithreaded programming, take a look at my other article [Working with Multithreaded Ruby](http://dev.to)
{% endcomment %}