マルチスレッドでプログラムを実行するとメモリ空間は同じものを使う。では、Rubyにおいて複数スレッドを立てて同じリソースに同時にアクセスするとどうなるのか?
IOを伴わない場合
Rubyでスレッドの生成はThreadクラスを使う。
以下のコードを実行してみる。
1000個スレッドを立ててそれぞれのスレッドから変数countを書き換える。
count = 0
threads = []
# 1000スレッドが同時に変数countにアクセスするとどうなるか?
1000.times do
threads << Thread.new do
count +=1
end
end
threads.each(&:join)
puts count
結果は、スレッドセーフではないコードなのに、何度実行しても正常にインクリメントされ結果は1000
になる。
これは、Ruby(ここではMRIに限る)ではGiant VM lock (GVL)(別名 Global Interpreter Lock(GIL))が実装されているためいくつスレッドを立てようと複数のスレッドが同時に実行されることはないため。
IOを伴う場合
前の例ではGVLによる排他制御が効いたが、IOを伴う場合はこのロックが解放されるので注意が必要。
IO 関連のブロックする可能性があるシステムコールを行う場合には GVL を解放します。その場合にはスレッドは同時に実行され得ます。https://docs.ruby-lang.org/ja/latest/class/Thread.html
インクリメントの前に標準出力処理を入れて試してみる。
count = 0
threads = []
flag = true
1000.times do
threads << Thread.new do
if flag
puts 'hoge' # インクリメントの前に標準出力をはさむ
count +=1
flag = false
end
end
end
threads.each(&:join)
puts count
結果は1000
だったり1
だったり999
だったり、毎回異なる。
IOを伴う処理で、スレッド間で共通のリソース(メモリ・ファイルなど)にアクセスする場合にはスレッド間の競合に注意する必要がある。
なお競合を回避するためにはMutexクラスが用意されている。