MoTLab -Mobility Technologies Engineering Blog-MoTLab -Mobility Technologies Engineering Blog-

プロセスとスレッドについての勉強会

行灯Labo低レイヤ
May 25, 2018

💁🏻
※本記事は Mobility Technologies の前身である JapanTaxi 時代に公開していたもので、記事中での会社やサービスに関する記述は公開当時のものです。
An image from Notion

JapanTaxiではソフトウェアエンジニアの未経験者採用を行っており、 その方々を対象に日々メンタリングを実施したり、不定期で勉強会を開催しています。 今回は、プロセスとスレッドについての勉強会を行いましたので、その内容を紹介したいと思います。

経緯

先日開催されたメドピアさんとの合同勉強会(JapanTaxi x MedPeer Ruby/Rails勉強会)で、メドピアの村上さんによる、UnicornでActionCableを使おうとしてハマりかけたことの話の中で、WebサーバーであるUnicornとPumaの違いについてプロセスベースとスレッドベースという説明がされており、これについて社内で勉強会を開催する事になりました。

勉強会の実施方法

ハンズオン形式でサンプルコードを使用してプロセスとスレッドの違いについて学習することにしました。前半は、座学で一般的に言われているプロセスとスレッドの違いについて学習し、 後半で、サンプルコードを使用して実際にどのような動作をするのか確認します。

座学

まずは一般的にプロセスとスレッドの違いで説明されている事を確認しました。カーネルスレッドやライトウェイトプロセス等を考慮してないため正確な説明ではありませんが割愛させていただきます。

プロセス

  • OSから見える処理の単位
  • 1プロセス1CPUの関係
  • メモリ空間を共有していない
  • マルチプロセッサの場合、(一般的に)シングルプロセスマルチスレッドプログラムよりもマルチプロセスプログラムの方が高速

スレッド

  • プロセスの中で並列的に処理を行う仕組み
  • メモリ空間を共有している
  • 非同期に実行できる処理がある場合は、(一般的に)シングルスレッドプログラムよりもマルチスレッドプログラムの方が高速

演習

マルチプロセス、マルチスレッドプログラミングのサンプルコードを用いて、下記の内容を確認します。

目的

  • メモリ空間を共有する、しないとは
  • OSからどのように見えるのか
  • メモリ効率の比較
  • 生成速度の比較
  • 最大生成可能数の比較

環境

  • macOS 10.12.6
  • 筆者の独断により、C言語を使用します。
  • サンプルコードはGitHubに公開しています。
  • コンパイラにはclangを使用して動作確認を行っています。
$ 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を行っていますが、これはプロセスの状態を確認しやすくするためです。

  1. プログラムを起動
  2. 変数nを1で初期化
  3. 10秒Sleepプロセスをfork
  4. (子プロセス)変数nのアドレスと値を出力
  5. (子プロセス)10秒Sleep
  6. (子プロセス)変数nを+1
  7. (子プロセス)変数nのアドレスと値を出力
  8. (子プロセス)終了
  9. (親プロセス)変数nのアドレスと値を出力
  10. (親プロセス)子プロセスの終了を待ち合わせる
  11. (親プロセス)変数nを+1
  12. (親プロセス)変数nのアドレスと値を出力
  13. (親プロセス)終了

出力結果を説明します。 parentが親プロセスの出力、childが子プロセスの出力です

  1. (親プロセス)変数nのアドレスと値を出力
  2. (子プロセス)変数nのアドレスと値を出力
  3. (子プロセス)変数nを+1
  4. (子プロセス)変数nのアドレスと値を出力
  5. (親プロセス)変数nを+1
  6. (親プロセス)変数nのアドレスと値を出力

マルチスレッド

プログラムを実行すると、下記の結果が出力されます。

$ ./multi_thread_sample.out
main n[0x7ffee483e964]=1
sub  n[0x7ffee483e964]=1
sub  n[0x7ffee483e964]=2
main n[0x7ffee483e964]=3

このプログラムが何を行っているかを説明します。マルチプロセスとほぼ等価の処理です。

  1. プログラムを起動
  2. 変数nを1で初期化
  3. 10秒Sleep
  4. スレッドを生成
  5. (サブスレッド)変数nのアドレスと値を出力
  6. (サブスレッド)10秒Sleep
  7. (サブスレッド)変数nを+1
  8. (サブスレッド)変数nのアドレスと値を出力
  9. (サブスレッド)終了
  10. (メインスレッド)変数nのアドレスと値を出力
  11. (メインスレッド)サブスレッドの終了を待ち合わせる
  12. (メインスレッド)変数nを+1
  13. (メインスレッド)変数nのアドレスと値を出力
  14. (メインスレッド)終了

出力結果を説明します。 mainがメインスレッドの出力、subがサブスレッドの出力です

  1. (メインスレッド)変数nのアドレスと値を出力
  2. (サブスレッド)変数nのアドレスと値を出力
  3. (サブスレッド)変数nを+1
  4. (サブスレッド)変数nのアドレスと値を出力
  5. (メインスレッド)変数nを+1
  6. (メインスレッド)変数nのアドレスと値を出力

OSからどのように見えるのか

マルチプロセス

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)
multi_process_sample2,544
multi_thread_sample1,724

生成速度の比較

multi_process_benchmark.outとmulti_thread_benchmark.outを実行し、生成時間を比較してみてください。 このプログラムは標準出力を行うだけのプロセス、もしくはスレッドを100個生成し、全てが終了するまでの時間を計測しています。 筆者の環境では下記の結果になりました。 この事から、スレッドのほうが生成速度が速いことがわかります。

実際のプログラムでは、プロセスやスレッドを都度生成する事は稀です。 高コスト、メモリリーク、生成できない可能性がある等の問題があるからです。 起動時に必要数を確保しプログラム終了時まで破棄しないスレッドプール等の仕組みが使われます。

プログラム処理時間(µs)
multi_process_benchmark19,081
multi_thread_benchmark8,389

最大生成可能数の比較

multi_process_challenge.outとmulti_thread_challenge.outを実行し、生成時間を比較してみてください。 このプログラムは標準出力を行うだけのプロセス、もしくはスレッドの生成を失敗するまで繰り返します。 筆者の環境では下記の結果になりました。この事から、スレッドのほうが同時に大量に生成できることがわかります。 最大数はOSが決めています。 プロセッサ数以上は同時に処理できないため、単純に数が多いほうが速いということではありません。

プログラム同時生成数
multi_process_challenge1,193
multi_thread_challenge4,095

振り返り

今回は、プロセスとスレッドの勉強会でしたが、メモリと深い関係があるところだったため、 ついメモリやレジスタに話が脱線しがちになったので、機会があればメモリとレジスタの勉強会を開催したいと思います。 (勉強会中つい説明に熱が入り、lldbを使ったライブデバッグにまで発展してしまいました。)

JapanTaxiでは、全国タクシー開発部エンジニアとして一緒に働く仲間を募集しています。まずは一度話を聞きに来てみませんか?

💁🏻
※本記事は Mobility Technologies の前身である JapanTaxi 時代に公開していたもので、記事中での会社やサービスに関する記述は公開当時のものです。

Mobility Technologies では共に日本のモビリティを進化させていくエンジニアを募集しています。話を聞いてみたいという方は、是非 募集ページ からご相談ください!