マルチプロセス化
1つのnodeプロセスはシングルスレッドで動き、CPU1個をフルに使うことができます。そのため、複数のCPUを持つシステムでnodeプロセスを1つだけ稼働させても、システムの性能を最大限発揮することはできません。しかし、nodeプロセスを複数起動すると、1プロセスにつきCPU1個をフルに使うことでシステムの性能を最大限活用できます。標準APIのClusterというモジュールを使うと、プログラムを簡単にマルチプロセス化できます。
ClusterはAPIの安定度が執筆時点で「1(実験的)」となっており、将来のバージョンでAPIが大幅に変更される可能性があります。
子プロセスを起動してメッセージをやりとりする
Clusterを使って子プロセスを複数起動させ、親プロセスと子プロセスとの通信機能を利用して、子プロセスを関数実行サーバーのように利用できます。リスト2ではマスタ(親プロセス)がCPUの数だけワーカー(子プロセス)を起動しています。それぞれのワーカーが計算した結果はマスタが受け取ります。この例でワーカーが実行する処理は非常に単純ですが、重い処理を子プロセスで手分けして処理させるとうまくCPU資源を活用することができます。
cluster.fork()を呼ぶごとにワーカーが1つ作成されます。ワーカーのプロセスはマスタと同じコマンドライン引数で起動されます。ワーカーからcluster.fork()を呼ぶことはできません。
cluster = require 'cluster'
# CPUの数を取得し、それをワーカー(子プロセス)の作成数とする
numWorkers = require('os').cpus().length
if cluster.isMaster # マスタ(親プロセス)の場合だけ実行する処理
# ワーカーのプロセスが終了した時に呼ばれる
cluster.on 'death', (worker) ->
console.log "worker #{worker.pid} が終了した"
for i in [0...numWorkers]
# ワーカーを作成する
worker = cluster.fork()
console.log "worker #{worker.pid}"
# ワーカーからメッセージが来た時にコールバックが呼ばれる。
# msgはワーカーから来たオブジェクト。
worker.on 'message', (msg) ->
# ここでのthisはメッセージの送信元のworkerオブジェクトを表す。
# 外側のスコープのworker変数を誤って使わないように注意。
if msg.cmd is 'ready' # ワーカーの準備が完了した
# ワーカーにメッセージを送信
@send cmd:'add', num1:@pid, num2:100000
else if msg.cmd is 'reply' # ワーカーから計算結果が来た
console.log "result from #{@pid}: #{msg.result}"
@kill() # ワーカーのプロセスを終了させる
else # ワーカーの場合だけ実行する処理
# マスタからメッセージが来た時に呼ばれる。
# msgはマスタから来たオブジェクト。
process.on 'message', (msg) ->
if msg.cmd is 'add'
result = msg.num1 + msg.num2
# マスタに計算結果を送信
process.send cmd:'reply', result:result
# 準備が完了したことをマスタに知らせる
process.send cmd:'ready'
実行結果は以下のようになります。
worker 66024 worker 66025 worker 66026 worker 66027 result from 66026: 166026 worker 66026 が終了した result from 66024: 166024 worker 66024 が終了した result from 66027: 166027 worker 66027 が終了した result from 66025: 166025 worker 66025 が終了した
複数の子プロセスを同じポートで待機させる
Clusterを使って複数の子プロセスを同一のポート番号で待機させることができます。
cluster = require 'cluster'
http = require 'http'
# CPUの数を取得し、それをワーカーの作成数とする
numWorkers = require('os').cpus().length
if cluster.isMaster # 親プロセスの場合
for i in [0...numWorkers]
# 子プロセスを作成する。
cluster.fork()
else # 子プロセスの場合
# TCPの3000番ポートで待ち受ける
http.createServer (req, res) ->
res.writeHead 200
res.end "Hello World by #{process.pid}\n"
.listen 3000
このプログラムを起動すると、クライアントがアクセスしてきた時に手の空いているワーカーが処理を引き受けます。
エラー処理
プログラム内でthrow文により例外がスローされ、どこにもキャッチされなかった場合、プロセスはスタックトレースを出力して終了してしまいます。しかし、uncaughtExceptionイベントにリスナを登録しておくと、そういった例外をキャッチしてプログラムの終了を防ぐことができます(リスト4)。常時起動させるプログラムを作成する場合は、意図せずプログラムが終了してしまうのを防ぐ保険の意味合いで入れておきましょう。
ただしあくまでも最後の保険なので、uncaughtExceptionに頼って例外処理をすることは避けましょう。例外が発生しそうな箇所にはあらかじめtry/catchを入れておくのがベストです。
process.on 'uncaughtException', (err) ->
console.log "例外をキャッチ: #{err}"
console.log "name: #{err.name}"
console.log "message: #{err.message}"
console.log "stack: #{err.stack}"
process.nextTick ->
console.log obj.prop
例外をキャッチ: ReferenceError: obj is not defined
name: ReferenceError
message: obj is not defined
stack: ReferenceError: obj is not defined
at Array.0 (/Users/nao/git/coffeebook/exception/test.coffee:11:24)
at EventEmitter._tickCallback (node.js:192:40)
なお、Errorオブジェクトはname、message、stackというプロパティを持っており、エラーの詳細について個別に取得できます。それぞれのプロパティの内容は次の通りです。
- name:エラーの名前
- message:エラーの内容
- stack:スタックトレース
