What happens if we remove GIL from ruby.
ruby GIL
'gil_test.rb'
array = []
threads = []
5.times do
thread << Thread.new do
1000.times { array << nil }
end
end
threads.each(&:join)
puts array.size
with GIL
if we use ruby with GIL
$ ruby -v
# => ruby 3.2.2 (2023-03-30 revision e51014f9c0)
$ ruby gil_test.rb
# => 5000
It returns 5000 as expected: 5 * 1000 = 5000 .
without GIL
But if we use ruby without GIL, we can install truffleruby:
TruffleRuby is a high-performance implementation of Ruby on the GraalVM and does not have a GIL.
$ ruby -v
# => truffleruby 23.1.0, like ruby 3.2.2, Oracle GraalVM Native
$ ruby gil_test.rb
# => 3780
$ ruby gil_test.rb
# => 3995
$ ruby gil_test.rb
# => 4494
Yt returns different result each time.
The reason behind this chaotic result is because this code array << nil
is not atomic.
Lets do another experience with a mutex
array = []
threads = []
mutex = Mutex.new
5.times do
threads << Thread.new do
1000.times do
mutex.synchronize do
array << nil
end
end
end
end
threads.each(&:join)
puts array.size
now even with ruby without GIL
$ ruby gil_test.rb
# => 5000
$ ruby gil_test.rb
# => 5000
Conclusion, GIL garantiee us a safe thread when we use multi-thread.
PS: as we are curious, we can take a look at <<
‘s source code
# https://github.com/ruby/ruby/blob/master/array.c
VALUE
rb_ary_push(VALUE ary, VALUE item)
{
long idx = RARRAY_LEN((ary_verify(ary), ary));
VALUE target_ary = ary_ensure_room_for_push(ary, 1);
RARRAY_PTR_USE(ary, ptr, {
RB_OBJ_WRITE(target_ary, &ptr[idx], item);
});
ARY_SET_LEN(ary, idx + 1);
ary_verify(ary);
return ary;
}