Thread.start の引数

Ruby だと Thread.start にブロックを渡すだけで手軽にスレッドを立てられるので、必要になった時につい既存のコードの一部を Thread.start do ... end で括ってしまう。これで変数を共有してしまっているのに気がつかずにはまってしまいました。もう何度もこの手の失敗はやっているのに、またやらかしてしまった。

まあネットワーク回りだとして、たとえばこんなコードを

while(packet = sock.read(RECORD_SIZE))
  case packet.unpack("N")
  when 0
    do_something(packet[4..-1])
  when 1
    ...
  end
end

do_somthing のところを非同期処理したくなって(その中でリプライして、そのまた応答を待つ……とか)、うっかりこんな風にしてしまうと、時々 do_something に渡される引数が予想したものと違ってしまって焦ることになります。

while(packet = sock.read(RECORD_SIZE))
  case packet.unpack("N")
  when 0
    Thread.start{ do_something(packet[4..-1]) }
  when 1
    ...
  end
end

リファレンスマニュアルの Thread.start の項にも書いてありますけど、これだと変数 packet が親と共有されてしまっているので、do_something の呼び出しの引数として評価される前に次の入力が代入されてしまう可能性があります。こうしなくちゃだめ。

    Thread.start(packet){|pkt| do_something(pkt) }

あまりに悔しかったので戒めのために記録。