What is Global Interpreter Lock in Relation to Ruby?
A few days ago, I came across something about Global Interpreter Lock (GIL) limitations in Ruby and I had no idea what it was. So naturally, I decided to research it. Similar to my last blog post, it’s important to know what goes on in the background when we execute code. Please note that GIL is not exclusive to the Ruby engine but simply something that Ruby MRI (Matz’ Ruby Interpreter) implements. For example, CPython uses GIL as well.
Before we cover what GIL is, let’s address some concepts first. Please keep in mind, when I discuss these concepts, I will be talking in the context of Ruby MRI.
Concepts
1) Thread(s)
2) Race condition
Thread(s)
Think of threads as an execution path of statements as per your code.
You probably also heard of the term single threaded or multi-threaded before. Single threaded means one thing can run at a time while multi-threaded means you can run multiple things at a time. The picture above shows us how threads in Ruby MRI run concurrently but not parallel. To provide an example, think about this. You just executed your code and you have two threads. Those two threads are “fighting” with each other to finish executing themselves. Each one would execute for a certain amount of time and it would go back and forth between each other.
Race Condition
Race condition refers to when the result of a thread or threads is dependent on the sequence of events. This can be quite problematic and can cause bugs. I provided an example down below.
@counter = 0100.times.map do
Thread.new do
10000.times do
value = @counter
value = value + 1
@counter = value
end
end
end.each(&.join)puts @counter
Picture A
If you were to run the code above in Picture A several times, you’d get the same result every time which is 1,000,000. (Keep in mind, I’m running this on Ruby 2.5.5.)
@counter = 0def read_counter
@counter
enddef add_value(value)
@counter = value
end100.times.map do
Thread.new do
10000 times do
value = read_counter
value = value + 1
add_value(value)
end
end
end.each(&:join)puts @counter
Picture B
Although I rearranged the code in Picture B, it’s essentially the same. Try running the code I have in Picture B several times. You’d get several different outputs. This is an example of race condition. Because of this, this makes the code unreliable.
Global Interpreter Lock (GIL)
GIL or global interpreter lock makes it so only one thread runs at a time, preventing threads from running concurrently. Hence, the “lock” portion of the name. This can prevent race conditions from occurring, making it harder for data to become corrupt. Keep in mind that GIL is not perfect and can work depending on how your code is written and the version of Ruby you’re using. GIL has limitations. GIL was not created for us, but for developers.