FIVETEESIXONE

Thread.exclusive is deprecated, use Mutex


Ruby 2.3.0 で Thread.exclusive が deprecated になっていて、使っていると警告が出力されるようになった。

Thread.exclusive is deprecated, use Mutex

で、使っているライブラリでもバンバンこの警告が出まくっていたので、「よーしじゃあ修正しとこうかな」と思ったけど、よく考えたらそもそもこの Thread.exclusive のことをよく知らなかった。

Thread.exclusive とは何だったのか

短いので、コードを見るのが一番早い。

prelude.rb

class Thread
  MUTEX_FOR_THREAD_EXCLUSIVE = Mutex.new # :nodoc:

  # call-seq:
  #    Thread.exclusive { block }   => obj
  #
  # Wraps the block in a single, VM-global Mutex.synchronize, returning the
  # value of the block. A thread executing inside the exclusive section will
  # only block other threads which also use the Thread.exclusive mechanism.
  def self.exclusive
    warn "Thread.exclusive is deprecated, use Mutex", caller
    MUTEX_FOR_THREAD_EXCLUSIVE.synchronize{
      yield
    }
  end
end

ここからわかる通り、Thread.exclusive というのは、Thread クラスに MUTEX_FOR_THREAD_EXCLUSIVE という名前で定義された Mutex に対して synchronize を呼び出してブロックを実行している。つまり、(少なくとも 1.9 以降では) Thread.exclusiveMutex を使った排他制御に他ならず、各スレッドが Thread.exclusive を呼び出しているクリティカルセクションにおいてのみスレッドセーフに実行することができる。言い変えると、かつての 1.8 の Thread.exclusive のように、その実行中はスレッドのコンテキストスイッチを一切行わない、というものではない。

Thread.exclusive はなぜ deprecated になったのか

バグトラッカーの「Why was Thread.exclusive deprecated?」というチケットを見ると、

  1. グローバルに作用する Thread.exclusive は予期せぬ競合を引き起こす可能性があるため
  2. 定義した Mutex の初期化を require を利用して行えば、読み込みは1度に限られスレッドセーフであるため

などの点が挙げられている。実際の経緯は知らないけど、確かにおっしゃる通りですと感じる。
ただ、「Thread.exclusive が暗黙的な Mutex である」と知ってか知らずか、Thread.exclusive は色んなところで使われているようなので、それを一般的な Mutex に置き換える PR が捗りそうではある。これ、るりまを参照してる分にはハッキリと「他の排他制御の方法を検討してください」と書かれてるけど、ruby-doc.org のリファレンス では 1.9 以降もしばらくミスリードっぽい記載になっていたようなので、その影響もあって広く使われてしまったのだろうか。
で、修正は素直に Mutex に置き換えればいいだけだろうけど、この修正のように、いくら 1 インスタンスしか生成されないクラスであったとしても、シングルトンではないインスタンス変数で Mutex を定義するのはちょっと落ち着かない気がするなぁ。