Rack、WSGIサーバのThread処理の実装について(1)

はじめに

本記事では、あえてRack,WSGIサーバと言っているが、いわゆるWEBアプリケーションサーバ以外のサーバソフトウェアの実装でも共通の話題である「クライアントからの接続ー通信処理」の実装方式として、NativeThreadのパターンとGreenThreadのパターンの2つの違いを自分用に2回にわたってメモ

事前知識

ソケットプログラミング等で、チャットプログラム等を作ったりしたことがある人は、ご存知かもしれないが。サーバの通信処理がどのようなフローで行われるかを少しここでおさらいする。

  1. サーバソケット(通信要求を受け付けるためのソケット)を作成する
  2. サーバソケットを指定のIP(通常、0.0.0.0でどのIFのIPでも受けれるようにする)、指定のportで通信要求を待つ
  3. クライアントからの通信要求をacceptする
  4. クライアントと通信路が確立される

見て分かると思うが、1プロセス1スレッドでサーバを実装してしまうと、同時に1つのクライアントしかさばけないため、3と4の間で各クライアントごとにスレッド、プロセスを生成して4以降のクライアントごとの処理はスレッドやプロセスに任せるのが一般的である。

NativeThreadで実装されている例(WEBrick

どんなNativeThreadがいるのか

ここでは、WEBrick(Rackサーバ)を題材にする

下記のような、テスト用のRackアプリケーションを用意する

#!/usr/bin/env ruby
require 'rubygems'
require 'rack'
include Rack

class TestApp
  def call(env)
    sleep(1000)
    [200, {"Content-Type" => "text/plain"}, ["test"]]
  end
end

Handler::WEBrick.run TestApp.new, :Port => 3000

上記を実行し、psでスレッドの数を見る(当方Macなので、-Mオプションを使用しています)

$ruby test.rb
[2015-01-03 14:46:16] INFO  WEBrick 1.3.1
[2015-01-03 14:46:16] INFO  ruby 1.9.3 (2013-02-22) [x86_64-darwin10.8.0]
[2015-01-03 14:46:16] INFO  WEBrick::HTTPServer#start: pid=76852 port=3000

$ps -M 
ukinau 76852 s002    0.0 S    31T   0:00.04   0:00.16 ruby test.rb
       76852         0.0 S    31T   0:00.00   0:00.00

なんで最初から、ネイティブスレッドが1つ作られているんだ?と思ったけど、後述するnetstatの結果より、ipv4,v6で1つずつ接続待ちのスレッドを作っていると思われる

curlでwebappにアクセスしてみる。

$curl http://localhost:3000

test.rbの中でsleepして、コネクション確立状態をキープしているのでその間にいろいろ確認

$netstat -an | grep 3000
tcp4       0      0  127.0.0.1.3000         127.0.0.1.52542        ESTABLISHED
tcp4       0      0  127.0.0.1.52542        127.0.0.1.3000         ESTABLISHED
tcp46      0      0  *.3000                 *.*                    LISTEN
tcp4       0      0  *.3000                 *.*                    LISTEN

$ps -M
ukinau 76852 s002    0.0 S    31T   0:00.05   0:00.18 ruby test.rb
       76852         0.0 S    31T   0:00.00   0:00.00
       76852         0.0 S    31T   0:00.00   0:00.00
       76852         0.0 S    31T   0:00.02   0:00.03

Threadが2個増えた!1つが先ほどのcurlからのリクエストを担当し、もう1つは次のクライアントのためにあらかじめスレッドを作成しているものだと思われる。

curlのリクエストが終了したら、1つ減った。

$ps -M
ukinau 76852 s002    0.0 S    31T   0:00.07   0:00.20 ruby test.rb
       76852         0.0 S    31T   0:00.00   0:00.00
       76852         0.0 S    31T   0:00.07   0:00.11

もう一度、curlを送ると

$ps -M
ukinau 76852 s002    0.0 S    31T   0:00.08   0:00.21 ruby test.rb
       76852         0.0 S    31T   0:00.00   0:00.00
       76852         0.0 S    31T   0:00.08   0:00.13
       76852         0.0 S    31T   0:00.00   0:00.00

スレッドが一つ増えた! う〜ん、もしかしたら、1つ多いスレッドは次のクライアントのためのものではないかもしれない。。。CPU使用時間が増えるのが上から3番目のスレッドだけなので、なんかユーティリティ用のスレッドなのかもしれない・・・・

現状分かる範囲でまとめると、

  • ipv6listen用スレッド = 1
  • ipv4listen用スレッド = 1
  • クライアント処理用スレッド × クライアント数
  • 謎のスレッド = 1

の4種類のネイティブスレッドでWEBrickは実現しているようです。

クライアントの処理がNativeThreadだと何がいいのか

NativeThreadのスケジューリングは、OSのプロセススケージューラによってスケジューリングされるため各スレッドは、コアの分だけ同時に動くことができる。もう少し端的に言うと各ThreadがOSに認識される。そのため、先ほどのsampleプログラムであるTestAppのcallメソッドの中に、sleep(1000)などのブロック関数があっても別のクライアントに影響を与えないのだ。

題材である、WEBアプリだとあまりないシチュエーションかもしれないが、各スレッドが独立して動くのでスレッド同士影響を与えないよう、スレッドセーフであることをきちんと確認する必要がある。