JapanTaxiではソフトウェアエンジニアの未経験者採用を行っており、 その方々を対象に日々メンタリングを実施したり、不定期で勉強会を開催しています。 今回は、プロセスとスレッドについての勉強会を行いましたので、その内容を紹介したいと思います。
先日開催されたメドピアさんとの合同勉強会(JapanTaxi x MedPeer Ruby/Rails勉強会)で、メドピアの村上さんによる、UnicornでActionCableを使おうとしてハマりかけたことの話の中で、WebサーバーであるUnicornとPumaの違いについてプロセスベースとスレッドベースという説明がされており、これについて社内で勉強会を開催する事になりました。
ハンズオン形式でサンプルコードを使用してプロセスとスレッドの違いについて学習することにしました。前半は、座学で一般的に言われているプロセスとスレッドの違いについて学習し、 後半で、サンプルコードを使用して実際にどのような動作をするのか確認します。
まずは一般的にプロセスとスレッドの違いで説明されている事を確認しました。カーネルスレッドやライトウェイトプロセス等を考慮してないため正確な説明ではありませんが割愛させていただきます。
マルチプロセス、マルチスレッドプログラミングのサンプルコードを用いて、下記の内容を確認します。
$ clang -v
Apple LLVM version 9.0.0 (clang-900.0.39.2)
Target: x86_64-apple-darwin16.7.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
$ git clone https://github.com/JapanTaxi/mpmt
$ cd mpmt
$ make all
プロセスの状態を確認するのにpsコマンドを使用しますが、定期的に更新したいのでwatchをインストールしてください。
$ brew install watch
プログラムを実行すると、下記の結果が出力されます。
$ ./multi_process_sample.out
parent n[0x7ffee36e2954]=1
child n[0x7ffee36e2954]=1
child n[0x7ffee36e2954]=2
parent n[0x7ffee36e2954]=2
このプログラムが何を行っているかを説明します。プログラム中でSleepを行っていますが、これはプロセスの状態を確認しやすくするためです。
出力結果を説明します。 parentが親プロセスの出力、childが子プロセスの出力です
プログラムを実行すると、下記の結果が出力されます。
$ ./multi_thread_sample.out
main n[0x7ffee483e964]=1
sub n[0x7ffee483e964]=1
sub n[0x7ffee483e964]=2
main n[0x7ffee483e964]=3
このプログラムが何を行っているかを説明します。マルチプロセスとほぼ等価の処理です。
出力結果を説明します。 mainがメインスレッドの出力、subがサブスレッドの出力です
Terminalを2つ起動しmulti_process_sample.outを実行しつつ、下記のコマンドを実行してください。
$ watch -n 1 ps cMo ppid,vsz,rss
注目していただきたいのは、COMMAND(コマンド名)、PID(プロセスID)、PPID(親プロセスID)、RSS(物理メモリサイズ)です。 まず、プログラム起動直後はmulti_process_sampleが一つしか確認できないと思います。 この時点ではプロセスが一つしか存在しないためです。
USER PID TT %CPU STAT PRI STIME UTIME COMMAND PPID RSS
yoshimitsudaiki 27912 s004 0.0 S 31T 0:00.00 0:00.00 multi_p 17990 1712
その後(10秒Sleep解除後)、プロセスが二つ確認できると思います。 二つ目に作成されたmulti_process_sampleのPPID(27912)が、最初に作成されたmulti_process_sampleのPIDになっていると思います。 これは一つ目のプロセスから二つ目のプロセスが生成されたという事を意味しています。 このようにPPIDをたどる事によって、プロセスの親子関係を確認することができます。
USER PID TT %CPU STAT PRI STIME UTIME COMMAND PPID RSS
yoshimitsudaiki 27912 s004 0.0 S 31T 0:00.00 0:00.00 multi_p 17990 1712
yoshimitsudaiki 27931 s004 0.0 S 31T 0:00.00 0:00.00 multi_p 27912 832
最後にRSSですが、これは物理的に使用しているメモリのサイズです。 二つのプロセスが起動することによって1,712KB+832KBのメモリが使用されている事が確認できます。
Terminalを2つ起動しmulti_thread_sample.outを実行しつつ、下記のコマンドを実行してください。
$ watch -n 1 ps cMo ppid,vsz,rss
注目していただきたいのは、COMMAND(コマンド名)、PID(プロセスID)、RSS(物理メモリサイズ)です。 まず、プログラム起動直後はmulti_thread_sampleと同じPIDを持つプロセスは一つしか確認できないと思います。 この時点ではスレッドが一つしか存在しないためです。
USER PID TT %CPU STAT PRI STIME UTIME COMMAND PPID RSS
yoshimitsudaiki 35216 s004 0.0 S 31T 0:00.00 0:00.00 multi_t 17990 1724
その後(10秒Sleep解除後)、同一のプロセス(PID:35216)が二つ確認できると思います。 これは一つ目のプロセス中に二つのスレッドが存在している事を意味しています。 本来であればtid(スレッドID)という概念で識別できるのですが、Macで確認する方法が不明だったため割愛させていただきます。
USER PID TT %CPU STAT PRI STIME UTIME COMMAND PPID RSS
yoshimitsudaiki 35216 s004 0.0 S 31T 0:00.00 0:00.00 multi_t 17990 1724
35216 0.0 S 31T 0:00.00 0:00.00 17990 1724
RSSはプロセス毎に共有されているため、1724KBのメモリが使用されている事が確認できます。
multi_process_sample.outとmulti_thread_sample.outを実行し、 psコマンドでRSS(物理メモリサイズ)を比較してみてください。 筆者の環境では下記の結果になりました。 この事から、マルチスレッドプログラムのほうがメモリ効率が良いことがわかります。
RSS(KB) | プログラム |
---|---|
2,544 | multi_process_sample |
1,724 | multi_thread_sample |
multi_process_benchmark.outとmulti_thread_benchmark.outを実行し、生成時間を比較してみてください。 このプログラムは標準出力を行うだけのプロセス、もしくはスレッドを100個生成し、全てが終了するまでの時間を計測しています。 筆者の環境では下記の結果になりました。 この事から、スレッドのほうが生成速度が速いことがわかります。
実際のプログラムでは、プロセスやスレッドを都度生成する事は稀です。 高コスト、メモリリーク、生成できない可能性がある等の問題があるからです。 起動時に必要数を確保しプログラム終了時まで破棄しないスレッドプール等の仕組みが使われます。
処理時間(µs) | プログラム |
---|---|
19,081 | multi_process_benchmark |
8,389 | multi_thread_benchmark |
multi_process_challenge.outとmulti_thread_challenge.outを実行し、生成時間を比較してみてください。 このプログラムは標準出力を行うだけのプロセス、もしくはスレッドの生成を失敗するまで繰り返します。 筆者の環境では下記の結果になりました。この事から、スレッドのほうが同時に大量に生成できることがわかります。 最大数はOSが決めています。 プロセッサ数以上は同時に処理できないため、単純に数が多いほうが速いということではありません。
同時生成数 | プログラム |
---|---|
4,095 | multi_thread_challenge |
1,193 | multi_process_challenge |
今回は、プロセスとスレッドの勉強会でしたが、メモリと深い関係があるところだったため、 ついメモリやレジスタに話が脱線しがちになったので、機会があればメモリとレジスタの勉強会を開催したいと思います。 (勉強会中つい説明に熱が入り、lldbを使ったライブデバッグにまで発展してしまいました。)
JapanTaxiでは、全国タクシー開発部エンジニアとして一緒に働く仲間を募集しています。まずは一度話を聞きに来てみませんか?
Mobility Technologies では共に日本のモビリティを進化させていくエンジニアを募集しています。話を聞いてみたいという方は、是非 募集ページ からご相談ください!