Thread Unsafeな

Railsアプリを作る

自己紹介

名前:

瀬戸 啓一朗

 

経歴:

東大法学部卒業

2019年 楽天入社

 

k8sを触ることが多いですが

業務ではRuby (Rails)を使っています

Thread Safe?

とあるAさんのBさんのコードに対するReviewにて

「このコード、スレッドセーフじゃないですね」

  • スレッドは知っていた
  • スレッドセーフはなんとなくしかわからない

 

 

Rubyでどんなコードを書くと

スレッドセーフじゃなくなるかが分からなかった

 

 

 

そもそもスレッドセーフとは

プロセス

別のプロセスは別のプロセスのメモリを直接参照できない

スレッド

メモリを共有しながら処理を行う

 

スレッドセーフとは

スレッドを同時並行で実行しても問題がない状態

 

じゃあ逆にスレッドセーフじゃないと何が起きる?

 

メモリ

スレッド1

スレッド2

  1. スレッド1が変数xに値を代入
  2. スレッド2が1が知らない間にxを変更
  3. スレッド1で取り出すときにいつの間にか値が変わっていて問題発生

スレッドセーフじゃないときの例

イマイチ実感できなかったので

Thread Unsafeな

Rails Applicationを作ってみた

本題

仕様

クライアントは果物をランダムな数だけ籠に入れてサーバーにpost

 

サーバーのレスポンスと自ら計算した値が合っているか計算し、

合っていたら 

間違っていたら

を標準出力

require 'net/http'
require 'json'

begin

prices = {
  orange: 100,
  apple: 200,
  peach: 400
}
http = Net::HTTP.new('localhost', '3000')

loop do
  items = [
    { name: :orange, count: rand(10) },
    { name: :apple,  count: rand(10) },
    { name: :peach,  count: rand(10) }
  ]
  right_answer = items.sum do |item|
    prices[item[:name]] * item[:count]
  end

  req = Net::HTTP::Post.new('/calc/index')
  req.body = { items: items }.to_json
  res = http.request(req)
  
  print res.body.to_i == right_answer ? "\e[32m" : "\e[31m"
  print "■\e[0m"
end

rescue Interrupt
  puts 'good bye'
end

仕様 (Rails内のコードを抜粋)

class CalcController < ApplicationController
  protect_from_forgery
  def index
    Basket.init
    JSON.parse(request.body.read)["items"].each do |item|
      Basket.add(item["name"], item["count"])
    end
    render json: Basket.result
  end
end
class Basket
  @@total = 0
  
  def self.init
    @@total = 0
  end

  def self.add(name, count)
    @@total += Item.find_by(name: name).price * count
  end

  def self.result
    @@total
  end
end

クライアント側から受け取ったリクエストをパースして籠に追加して結果を返す

@@totalというクラス変数にDBから取得した価格を用いて合計額を計算

 

(なんか怪しい実装w)

app serverは

マルチスレッドのpuma

DEMO

あれ、そんなに問題なくない?

RubyのGIL

GIL(グローバルインタプリタロック)

 

CRubyのスレッドはGILという機能により1つのプロセスでは複数のスレッドが同時に動かない

 

※JRubyとかだとガチでマルチスレッドで動きます

GILがあるならマルチスレッドの意味ねーじゃん?

GILはIO時に解放されるそうで外部コマンドを呼び出したり、DBを叩いたり、HTTPでアクセスしたり等の間はマルチスレッドが有効です。

 

例えば、1回10秒かかるリクエストを4回したいときに、シングルスレッドだと40秒かかりますが、マルチスレッドだと10秒で終わります。

DEMO

(sleepあり)

まとめ

  • スレッドはメモリを共有している
  • マルチスレッドはIO待ちがあるときに有効
  • クラス変数は共有資源なので注意
Made with Slides.com