【Rails】Controllerで重い処理を実行する

(Controllerに限らないですが)重い処理があると、htmlレンダリングはその処理後に行われるためユーザにはフリーズしたように見えてしまいます。(´・ω・`)


〜コントローラで〜

def heavyTask
  #重い処理
  sleep(1000)
end

、、、1000秒待たないと画面が表示されないよ(´・ω・`)



重い処理はThread.start()で別スレッド化して、同時にhtmlレンダリング出来るようにしましょう。

def heavyTask
  Thread.start do
    #重い処理
    sleep(1000)
  end
  #重い処理を待たずにhtmlレンダリングできるよ(`・ω・´)
end

以上のコードで、重い処理と画面表示を並列化出来ます。ぱちぱち。

ん、重い処理が終わったらその通知を画面に表示したいのですか?
それはまた別のおはなし。(←ホントは知らな(ry

【注意】
Threadを使う場合は、同時に同じ処理をしても平気か検討が必要です。
ウェブアプリだと何十、何百も同時リクエストがありますよね。
上記コードだと、リクエストごとにスレッドが立ち上がり、同時に動作します。
この際に、想定外のデータになってしまわないかよく検討しましょう。

  • 想定外のデータにならない場合はおkです。(「スレッドセーフ」と言います)
  • 想定外のデータになってしまう場合は「排他制御」を行わなくてはなりません。

排他制御ミューテックスという機能で実現出来ます。
平たく言うと、スレッド間で

「今わたしが処理してるから邪魔しないでね(・A・)」
「じゃあ待ってるから終わったら教えてね(´・ω・`)

というのを実現する仕組みです。

def heavyTask
  #スレッド間で同じミューテックスを使うため、グローバル変数($)を使う
  if($mycontrollerMutex==nil)
    $mycontrollerMutex = Mutex.new
  end
	
  tid = Thread.start do 
    p "Thread start. ID:#{tid}"
    $mycontrollerMutex.synchronize do
    #重い処理
  end
  p "Thread end. ID:#{tid}"
end

以上のようにすれば、heavyTask()内の重い処理は、リクエスト順に順番に処理されるようになります。


、、ちなみにモデル層でこれを実現するには

ActiveRecord::Base.allow_concurrency = true

とすればいいみたい。