55579


ruby でマルチスレッドプログラミングを行う場合、どの ruby のメソッドが GVL
を外すのかを意識することが重要になります。というのも、それがないと、マルチスレッド環境で実装したとしても、処理が平行して実行されないからです。

自分の理解としては、以下の ruby のメソッドたちは、 GVL が外れると理解しています。

  * `Kernel#sleep`
  * `IO#read`, `IO#write`

ふと、マルチスレッド環境で `system` 系のコマンド実行を実装しようとして、これは GVL 的に大丈夫なのだろうか、と疑問に思いました。

### 質問

  * ruby のメソッドの中で、 GVL が外れるものはどれですか？ 
    * 例えば、(ないとは思いますが)ありとあらゆるシステムコールに対して GVL が外れたりしますか？


CC BY-SA 4.0
2019-02-12T02:26:45.670
52710
2019-06-06T10:43:02.130
2019-02-12T02:39:08.730
754
754
question
ruby マルチスレッド
GVL が外れる ruby のメソッドはどれですか？


ひとまず、コマンド実行系であるバッククォート置換と、 `Kernel#system` コマンドについては、 GVL
が外れている様子であるという結論を得ました。

[code]

    SLEEP_S = 5
    
    def sleep_by_backtick
      puts 'going to kick shell'
      puts `sleep #{SLEEP_S}; echo finished sleep`
    end
    
    def sleep_by_system
      puts 'going to kick shell'
      system('sleep', SLEEP_S.to_s)
      puts 'finished sleep'
    end
    
    def print_hello_with_sleep
      (0...SLEEP_S).each do |i|
        puts "hello(#{i})"
        sleep 1
      end
    end
    
    def concurrent_exec(method)
      t = Thread.new do
        send(method)
      end
      print_hello_with_sleep
      t.join
    end
    
    concurrent_exec(:sleep_by_backtick)
    puts '======='
    concurrent_exec(:sleep_by_system)
    
[/code]

### 出力

[code]

    hello(0)
    going to kick shell
    hello(1)
    hello(2)
    hello(3)
    hello(4)
    finished sleep
    =======
    hello(0)
    going to kick shell
    hello(1)
    hello(2)
    hello(3)
    hello(4)
    finished sleep
    
[/code]


CC BY-SA 4.0
2019-02-15T02:52:17.500
52787
2019-02-15T02:52:17.500
754
52710
answer


Cレベルでは`rb_thread_call_without_gvl`または`rb_thread_call_without_gvl2`(違いはinterruptのありなし)を呼び出すことでGVLを外したスレッドで実行されることになります。逆に言うとこの二つの関数を呼び出しているところでは一時的にGVLが外れた処理がされていると言うことです。

2.6系のソースを軽く見た程度ですが、次のような所で上の関数が呼ばれていました。

  * Bignumの商・剰余
  * Dir.new/Dir.open
  * Dir.chdir
  * Dir.rmdir
  * Dir.empty?
  * Dir.glob
  * File.chown
  * Fileのstatといったファイルの状態を見に行く多くのメソッド
  * File.rename
  * File.truncate
  * File.mkfifo
  * IO.copy_stream
  * IO.#openおよびIO#closeに関わるほぼ全て？(ちょっと全ては追い切れなかった)
  * Kernel.#system
  * Kernel.#exec 

他にも標準ライブラリではfiddle, openssl, readline, socket, zlibで使用されています。

IO周りはopen時にGVLを外している場合があるようです。read/writeの処理がどうなのかはそこまで詳しくないのでよくわかりませんでした。

全てを確認したわけではありません。これ以外にも内部で上のメソッド呼び出している場合があります。また、メソッドの全ての処理についてGVLが外れるのではなく、処理の一部でGVLが外れることになります。環境や処理内容によっては外れないままの場合もあります。

なお、Kenrel.#sleepはスレッド処理の一部として組み込まれているようです。

* * *

GVLが外れた場合の恩恵を受けるのはマルチコアな環境です。シングルコアな環境ではGVLが外れても処理全体の速度が向上するわけではありません。通常の処理のほとんどでGVLを外すことは(CRubyの実装上)不可能なので、IO処理や余程重いライブラリ処理のみでGVLを外しているようです。マルチコアをフルに使いたいというのであれば、マルチフォークにしたほうが良いでしょう。マルチスレッドのままマルチコアをフルに使いたいというのであれば、JRubyを検討した方が良いでしょう。


CC BY-SA 4.0
2019-06-06T10:30:08.583
55579
2019-06-06T10:43:02.130
2019-06-06T10:43:02.130
7347
7347
52710
answer