Closed59

Rubyでプロセスとスレッドを学ぶ

ハガユウキハガユウキ

ActiveRecordのデーターベースアクセス

さっきのRubyのmapをデータベースアクセスの際に使うデメリットをいまいち言語化できてなかったのでまとめる。

  • mapはそもそもRubyの世界におけるArrayクラスのメソッド
  • ActiveRecordは、SQLにおけるデータベースアクセスとデータマッピングを抽象化したメソッドがたくさん定義されているライブラリ。これらのメソッドの内部でデータベースアクセスとデータベースから取得したレコードをRubyのオブジェクトに変換している(おそらく)。ActiveRecordはRubyで使える。
  • DBにある10万件の投稿から1つの特定の投稿を取得する際に、①10万件の中から1つの特定の投稿をActiveRecordのメソッド(またはSQL)のみを用いて取得するか、もしくは、②10万件のデータをActiveRecordのメソッドで取得してからmapメソッドで1つの特定の投稿を取得するかは、同じように見えて意外と違う。
  • 前者は1件の投稿だけDBから取得して、その1件の投稿オブジェクトをメモリに展開する。後者は10万件の投稿をDBから取得して、10万件の投稿オブジェクトをメモリに展開してから1件の投稿オブジェクトを取得している。前者のメモリ使用量は大したことないが、後者のメモリ使用量は多い。後者の場合データが増えるごとに比例してメモリ使用量も多くなる
  • メモリ使用量がとんでもなく増えると、OOMエラー(Linuxカーネルがメモリリソースを多く消費しているプロセスを強制的に殺すエラー)が発生したり、アプリケーションの応答速度が下がったりする可能性がある。例えばAPIサーバーのプロセスが死ぬと、ユーザーに適切なデータが返されなくてデータを扱うフロントの画面がうまく表示されないので、せっかくのビジネスチャンスを失ったりする。プロセスが死んでいる間、ビジネス的な損失を受ける可能性がある。この場合、コードを最適なものに修正するか、サーバのメモリのスペックを上げるか(金で殴る)が選択肢として挙げられると個人的には思う。
ハガユウキハガユウキ

プロセス

プロセスは、OSから見た際のプログラムの実行単位。OSから見てプログラムはプロセスとして管理されている。もっと厳密にいうと、「1つまたは複数のスレッドが実行されるアドレス空間と、これらのスレッドの実行に必要なシステムリソースのこと」
プロセスは自分が独占したメモリの中で動いている。その中で何をしても他のプロセスには影響を与えない。
OSは限られたCPUでプロセスをうまく実行するために、OSは適切にプロセスを切り替えている。
CPUコアの数だけ、並列処理ができる

ハガユウキハガユウキ

プロセスは必ず実行スレッドを持っている。スレッドとは、プログラムの実行の流れである。
並列処理をしたいなら、
プロセスを複製して増やすか、一つのプロセスにあるスレッドを複製すれば良い。

ハガユウキハガユウキ

OSは「スケジューリング」という機能を用いて、どのプロセスを先にCPUで取り扱うかを制御してくれる。プロセスはOSから見た際の「1つまたは複数のスレッドが実行されるアドレス空間と、これらのスレッドの実行に必要なシステムリソースのこと」。スレッドはCPUから見た際の処理するプログラムの単位。プロセスは最低1つのスレッドを持っている。

ハガユウキハガユウキ

psコマンドで実行中のプロセスを見れる

ps
  PID TTY           TIME CMD
  638 ttys000    0:03.51 -zsh -g --no_rcs
64897 ttys003    0:01.03 -zsh -g --no_rcs
65703 ttys004    0:00.91 -zsh -g --no_rcs
ハガユウキハガユウキ

プロセスのライフサイクル

プロセスが何らかの方法で生成される

処理中(実行中、待ち状態、ブロック中の3パターンがある)

終了

待ち状態はCPUでいつでも処理できる状態
ブロック中はIO待ちだから、CPUで処理できんよって状態

ハガユウキハガユウキ

fork

通常、プロセスは、親プロセスがforkというシステムコールをOSに送ることで、生成される。forkを実行するとOSは親プロセスを複製して子プロセスを生成する。つまり、この時、メモリ上のデータが複製されている。
forkによってプロセスは生成されるため、基本的に全てのプロセスには「自分を生んだ親プロセス」が存在する。

forkした子プロセスはforkを実行した位置以降の処理を実行するので、そこは注意する。
https://www.tenkaiken.com/short-articles/linuxプロセスの生成と実行-fork-exec/

ハガユウキハガユウキ

全ての祖先となる最初のプロセスをinitプロセスと呼ぶ。プロセスは木構造の親子関係を持っている。この親子関係を「プロセスツリー」と呼ぶ。

macだとlaunchedというプロセスがinitプロセスと同等のプロセスである。
プロセスidが1であることが分かる。

ps ax | grep launchd
    1   ??  Ss    58:58.94 /sbin/launchd
ハガユウキハガユウキ

exec

あるプロセスがexecというシステムコールを実行すると、execの内容でそのプロセス自体の内容を書き換えて実行することができます。

  1. forkで親プロセスを複製したプロセスを作成
  2. そのプロセスでexecを実行して、別の内容のプロセスとして書き換えて実行する

この手順を踏むことで、親プロセスとは違うプロセスをどんどん生成することができる。

ハガユウキハガユウキ

Kernel.#fork

プロセスの複製を作れる。親プロセスでは子プロセスのプロセスidを、子プロセスではnilを返す。

puts "forking..."

# forkメソッドを呼び出す
# 親プロセスでは子プロセスのpidが取得できる。
# 複製された子プロセスでは、pidはnilである
pid = Process.fork
p pid

# ここに来てるということは、正常にプロセスが複製された。
# この時点で親プロセスと子プロセスが *別々の環境で*
# 同時にこのプログラムを実行していることになる。
puts "forked!"

if pid.nil?
  # 子プロセスはこっちを実行する

  # execメソッドで、Rubyのプロセスを無限ループでsleepするプロセスに置き換える
  # ここで子プロセスをexecしている
  exec "ruby -e 'loop { sleep }'"
else
  # 親プロセスはこっちを実行する

  # 子プロセスが終了するのを待つ
  # 親プロセスだからpidはnilではない
  Process.waitpid(pid, 0)
end

https://docs.ruby-lang.org/ja/latest/method/Kernel/m/fork.html

ハガユウキハガユウキ

実行結果

ruby /Users/yuuki_haga/repos/learning/rails/rails-n-plus-1/src/sample.rb
forking...
81598
forked!
nil
forked!
ハガユウキハガユウキ

psを実行した結果
確かに子プロセスが実行されていることが確認できた

ps
  PID TTY           TIME CMD
64897 ttys003    0:01.52 -zsh -g --no_rcs
81585 ttys003    0:00.12 ruby /Users/yuuki_haga/repos/learning/rails/rails-n-plus-1/src/sample.rb
81598 ttys003    0:00.10 ruby -e loop { sleep }
65703 ttys004    0:01.19 -zsh -g --no_rcs
ハガユウキハガユウキ

Process.pidでそのプロセスのプロセスidを出力することができた。

puts "forked!"
puts Process.pid
ruby /Users/yuuki_haga/repos/learning/rails/rails-n-plus-1/src/sample.rb
forking...
forked!
84073
forked!
84086
ハガユウキハガユウキ

pstreeコマンドを使うと、そのプロセスからフォークされたプロセスを知ることができる。
調査対象のプロセスidを指定する

pstree 84073
-+= 84073 yuuki_haga ruby /Users/yuuki_haga/repos/learning/rails/rails-n-plus-1/src/sample.rb
 \--- 84086 yuuki_haga ruby -e loop { sleep }

https://webkaru.net/linux/pstree-command/

ハガユウキハガユウキ

execを実行しないと、同じコマンドを実行している複製プロセスとして、OSによって管理されている。

puts "forking..."

# forkメソッドを呼び出す
pid = Process.fork

# forkに失敗すると返り値はnil
# raise "fork failed." if pid.nil?
# p pid

# ここに来てるということは、正常にプロセスが複製された。
# この時点で親プロセスと子プロセスが *別々の環境で*
# 同時にこのプログラムを実行していることになる。
puts "forked!"
puts Process.pid

if pid.nil?
  # 子プロセスはこっちを実行する

  # execメソッドで、Rubyのプロセスを無限ループでsleepするプロセスに置き換える
  # exec "ruby -e 'loop { sleep }'"
  puts "child process"
  sleep
else
  # 親プロセスはこっちを実行する

  # 子プロセスが終了するのを待つ
  # 親プロセスだからpidはnilではない
  Process.waitpid(pid, 0)
end
ps
  PID TTY           TIME CMD
64897 ttys003    0:01.66 -zsh -g --no_rcs
85275 ttys003    0:00.14 ruby /Users/yuuki_haga/repos/learning/rails/rails-n-plus-1/src/sample.rb
85288 ttys003    0:00.00 ruby /Users/yuuki_haga/repos/learning/rails/rails-n-plus-1/src/sample.rb
65703 ttys004    0:01.47 -zsh -g --no_rcs

https://docs.ruby-lang.org/ja/latest/method/Process/s/exec.html

ハガユウキハガユウキ

&をつけるとバックグラウンドプロセスとして起動できる。

ruby -e "sleep" &
[1] 76342

ターミナルを通して入力を受け付けることができなくなる。
fgを実行すればフォアグラウンドプロセスに戻せる

ハガユウキハガユウキ

ジョブはシェルが管理するプログラムのグループのこと
基本は1プログラム1ジョブ

ハガユウキハガユウキ

シェルからコマンドを叩いてプロセスを生成する場合、親プロセスはシェルのプロセスである。

ps
  PID TTY           TIME CMD
64897 ttys003    0:01.74 -zsh -g --no_rcs
86122 ttys003    0:00.13 ruby /Users/yuuki_haga/repos/learning/rails/rails-n-plus-1/src/sample.rb
86135 ttys003    0:00.10 ruby -e loop { sleep }
65703 ttys004    0:01.67 -zsh -g --no_rcs
pstree 64897
-+= 64897 yuuki_haga -zsh -g --no_rcs
 \-+= 86122 yuuki_haga ruby /Users/yuuki_haga/repos/learning/rails/rails-n-plus-1/src/sample.rb
   \--- 86135 yuuki_haga ruby -e loop { sleep }
ハガユウキハガユウキ

ps axはシステム全体のプロセス。psはターミナルに関連づけられたプロセスを表示する

ハガユウキハガユウキ

プロセスとファイル入出力

プロセスに外から何かを入力したり、プロセスが外に何かを出力する方法として、「ファイルの入出力」というのがあります。たとえば、ファイルに書かれたデータをプロセスがメモリー上に読み込んでなんか処理をするとか、処理を行った結果をテキストファイルに書き込みをするとか。例を見てみましょう。

自分の中だけで完結しているプロセスだとあんま意味ない。プロセスに外から何かを入力して、プロセスが外に何かを出力するのを観察してみる

ハガユウキハガユウキ

ディスクにあったデータを、プロセス内部のメモリ上に展開して、
メモリ上に展開されたデータを別のファイルとしてディスクに出力した。

file = File.open("nyan.txt", "r")
# ファイルのデータはもともとディスクに存在している。プロセスがもともとメモリー内に持っているものではない
# このディスクに存在しているファイルのデータを、readlinesで変数に代入することで、
# プロセスの外部に存在しているディスクのデータを、プロセスの内部のメモリーに読み込んでいる
lines = file.readlines # ファイルの中身を全部読み込む
file.close

copy_file = File.open("nyan_copy.txt", "w")
copy_file.write(lines.join)
file.close
ハガユウキハガユウキ

Linuxでは全てがファイル

Linuxでは、プロセスに関する全ての入出力をファイルと同じインターフェースで扱うことができる。プロセスがターミナルからの入力を受け取りたかったり、ネットワーク越しに入力をもらって、ネットワーク越しに出力したりなどをファイルと同じインターフェースで扱える。OS側で用意してくれる。

ハガユウキハガユウキ

プログラム上で、ファイルをIOする際に一瞬プロセスは「ブロック中」になっている。

こんな感じで、「実際はdisk上のファイルじゃないもの」も、「disk上のファイルとおなじように」扱える。そういう仕組みがLinuxには備わっています。今はそれが「すべてがファイル」の意味だと思ってください。

ハガユウキハガユウキ

標準入力、標準出力

プロセスへの入力元は標準入力を設定することで変更することができる。標準入力はデフォルトではターミナルに設定されている。
プロセスへの出力元は標準出力を設定することで変更することができる。標準出力はデフォルトではターミナルに設定されている。
標準入力も標準出力(どちらもデフォルトでターミナルが設定されている)もファイルと同じインターフェースでプロセスから操作できる。標準入力も標準出力もファイルディスクリプタが設定されていて、プロセスはそのファイルディスクリプタとシステムコールを使うことで、書き込んだり読み込んだりできる。

リダイレクト

リダイレクトを使うと、標準入出力に別のファイルを指定できる。
シェル上では、標準入力は0、標準出力は1、標準エラー出力は2という数字で表される。

↓標準出力のリダイレクト

# 標準出力にファイルを指定
# ファイルを生成しつつ。プロセスからの出力をファイルにアウトプットする
# 1を省略することもできる
ruby /Users/yuuki_haga/repos/learning/rails/rails-n-plus-1/src/print_mew.rb 1>hina.txt

↓標準入力のリダイレクト

file = $stdin
# IOを待っているので、プロセスが「ブロック中」になっている。
lines = file.readlines
file.close

# rubyの組み込みグローバル変数 $stdout には、「標準出力」と言われるものが、
# すでにFile.openされた状態で入っています。この「標準出力」の出力先は、デフォルトではターミナルをさします
file = $stdout
file.write(lines.join)
file.close
# 0を省略することもできる
ruby /Users/yuuki_haga/repos/learning/rails/rails-n-plus-1/src/stdout.rb 0<hina_hina.txt
mew
ハガユウキハガユウキ
stdin_file = $stdin
# IOを待っているので、プロセスが「ブロック中」になっている。
lines = stdin_file.readlines
stdin_file.close

# rubyの組み込みグローバル変数 $stdout には、「標準出力」と言われるものが、
# すでにFile.openされた状態で入っています。この「標準出力」の出力先は、デフォルトではターミナルをさします
stdout_file = $stdout
stdout_file.write(lines.join)
stdout_file.close
# プロセスがファイルを標準入力としていて、プロセスの標準出力を設定することで、プロセスのアウトプット先がファイルになる
ruby std_in_out.rb 0<hina.txt 1>hoge.txt
ハガユウキハガユウキ

標準エラー出力

プロセスからアウトプットされたエラーは、プロセスの標準エラー出力に設定されたファイルに出力される。

puts "this is stdout"
warn "this is stderr" # warnは標準エラー出力に引数を出力する
ruby stdout_stderr.rb 1>out.txt 2>err.txt
# こうもかける
# &をつけることで、この1は1っていう名前のファイルじゃなくて、標準出力を表す数字だ!」と表すことができる
ruby stdout_stderr.rb 1>out.txt 2>&1
ハガユウキハガユウキ

パイプ

command_a | command_b

command_aのプロセスの出力結果が、commnad_bのプロセスの入力になる。
この際に、comnnad_aのプロセスの処理が完全に終わらなくても、出力があるなら、comnnad_bのプロセスに入力される。commnad_bのプロセスは、commnad_aからの入力が来るまで、プロセスの状態が「ブロック中」になる。

ちなみに、このように入力と出力をパイプでつないで、「ファイルの終わりを待たずにきたデータから順々に」なにか処理をするのを、パイプライン処理、とか、ストリーム処理、と言います。

へー

ハガユウキハガユウキ

command_a.rb

puts "start command_a"

stdin_file = File.open("hoge.txt", "r")
lines = stdin_file.readlines
stdin_file.close

stdout_file = $stdout
stdout_file.write(lines.join)
stdout_file.close

command_b.rb

puts "start command_b"

stdin_file = $stdin
lines = stdin_file.readlines
stdin_file.close

stdout_file = $stdout
stdout_file.write([*lines, "command_b"].join)
stdout_file.close

確かに、最初は、並列実行されて、あとは、ruby commnad_b.rbのプロセスが、IOがあるからブロック状態になったな。

ruby command_a.rb | ruby command_b.rb
start command_b
start command_a
mew
hinana
command_b%      
ハガユウキハガユウキ

システムコール

システムコールとは、アプリケーションプログラムがカーネルを通してハードウェアにアクセスするためのインターフェースである。システムコールはアプリケーションプログラムとカーネルの間に存在する。

https://zenn.dev/yukihaga/scraps/49a22736e00c56

ハガユウキハガユウキ

ファイルディスクリプタ

実はプロセスは自分自身で実際にファイルを開いたりディスクに書き込んだりディスクからデータを読み出したりすることはありません。そういう低レイヤーの処理は、プロセスがシステムコールをOSに送ることで、OSが代わりに行ってくれます。

プロセスは低レイヤーの処理を自分でするわけではなくて、プロセスがシステムコールを実行することで、OSに低レイヤーの処理を実行させている。

OSはプロセスから「ファイルを開け」というシステムコールを受け取ると、実際にファイルを開いて、そのファイルを表す識別子(ファイルディスクリプタ)を作成してプロセスに返す。プロセスはファイルディスクリプを使って、「このファイルディスクリプタで表されるファイルにこれを書き込め」というシステムコールを送る。OSはファイルディスクリプタで表された、既に開いているファイルに対して書き込みを行う。
書き込みが終了したら、プロセスは不要になったファイルディスクリプタをcloseというシステムコールでOSに返却する。OSはファイルディスクリプタが返却されたので、「もうこのファイルは使わないのか」と判断して、ファイルを閉じる。

つまり、ファイルディスクリプタとは、OSが開いているファイルの識別子である。

ハガユウキハガユウキ
file = File.open("kuga.txt", "w")
puts file.fileno # => 9
file.close

1行目では、openシステムコールをOSに対して送っている。正常にopenされるとファイルディスクリプタを内部に持ったfileオブジェクトが生成される
2行目では、fileオブジェクトが保持しているファイルディスクリプタを取得してターミナルに出力する
3行目では、fileを閉じているが、これはRubyが内部でfileオブジェクトが保持しているファイルディスクリプタを使って、OSにcloseシステムコールを送っている。

ハガユウキハガユウキ

プロセスから標準入出力が指し示すファイルのファイルディスクリプタを見てみる。

puts $stdin.fileno
puts $stdout.fileno
puts $stderr.fileno

# => ruby std_fds.rb
# 0
# 1
# 2
ハガユウキハガユウキ

オープンファイル記述

プロセスに「ファイル開いて」って言われたら開いてあげる
ファイルを開いたら、そのファイル専用の「ファイルの状況どうなってるっけメモ」を作る
OSは、「ファイル開いて」っていうシステムコールを受け取ると、オープンファイル記述を作り出して自分で保持しておきます。さらに、システムコールを送ってきたプロセスのidに対して、新しい番号札(ファイルディスクリプタ)を返します。このとき、オープンファイル記述とプロセスidと番号札の関連も、自分の中に保持しておきます。

このオープンファイル記述子がファイルの状況を管理するメモのようなもの。

ハガユウキハガユウキ

プロセスをフォークした場合、ファイルディスクリプタは複製されて、複製されたファイルディスクリプタは、同一のオープンファイル記述を参照する(オープンファイル記述は複製されない)
複製されたファイルディスクリプタってことは、同じ番号で複製されたってことか。フォークで複製したプロセスはpidはもちろん違う

ハガユウキハガユウキ

ソケットもファイルと同じインターフェースで操作することができる。

プロセスからソケットに対して書き込んだり、ソケットからプロセスに入力したい場合、ソケットを表すfdと、read, writeのシステムコールをプロセスから実行すれば良い。

ハガユウキハガユウキ
require "socket"

# 12345 portで待ち受けるソケットを開く
listening_socket = TCPServer.open(12345)

# ソケットもファイルなので、fdがある
puts listening_socket.fileno # => 10

# とりあえず閉じる
listening_socket.close
ハガユウキハガユウキ

プロセスが死ぬ(プロセスが終了する)とは、そのプロセスの実行が終了することを指します。プロセスは、プログラムが実行された際に作成され、そのプログラムの実行が完了したり、エラーが発生して強制的に終了したりすると、そのプロセスは終了します。

ハガユウキハガユウキ

親プロセスが死んだら、残された子プロセスはinitプロセス(macだとlaunchdプロセス)を親として、紐付けられることが分かった

pid = Process.fork

if pid.nil?
  # exec 'ruby -e p "hello"'
  # 子プロセス
  # 親プロセスのidを取得する
  puts "親プロセスid: #{Process.ppid}"

  # 親が死ぬまで2秒まつ
  sleep 2

  # 親プロセスが死んだ後のppid
  puts "親プロセスid: #{Process.ppid}"
  sleep
else
  # 親プロセス
  sleep 1

  # rubyプログラムを終了させる
  # つまり、実行中のプロセスがなくなるので、プロセスが死ぬ
  exit
end
pstree 1
-+= 00001 root /sbin/launchd
 \--- 11837 yuuki_haga ruby kill_parent.rb
ps
  PID TTY           TIME CMD
11837 ttys003    0:00.00 ruby kill_parent.rb
64897 ttys003    0:04.37 -zsh -g --no_rcs
ハガユウキハガユウキ

ゾンビプロセス

子プロセスが実行終了しているにもかかわらず、親プロセスに wait されないとプロセスが回収されず、ゾンビプロセスとして残ってしまう

pid = Process.fork

if pid.nil?
  # 子プロセス
  # 子プロセスは即死する
  exit
else
  # 親プロセス
  # 子プロセスのpidを出力
  puts pid
  loop do
    sleep
  end
end
ハガユウキハガユウキ

子プロセスが即死しているのに、親プロセスがそれに気づかないでずっと動いていると、子プロセスが成仏できずにゾンビ化する。STATがZだと、ゾンビプロセスを意味する

ps 13232
  PID   TT  STAT      TIME COMMAND
13232 s003  Z+     0:00.00 <defunct>

ゾンビプロセスの問題点はメモリなどのコンピュータリソースを無駄に消費してしまうこと

ハガユウキハガユウキ

親プロセスと子プロセスがどちらも実行終了したら、どっちも消えるだけか。

ハガユウキハガユウキ

シグナル

プロセスはファイルディスクリプタを通じて外界と入出力のやり取りをする以外に、シグナルを利用して外界とやりとりする

ハガユウキハガユウキ
ruby -e "loop { sleep }" &
kill -INT 15663
Traceback (most recent call last):
	3: from -e:1:in `<main>'
	2: from -e:1:in `loop'
	1: from -e:1:in `block in <main>'
-e:1:in `sleep': Interrupt
[1]  + 15663 interrupt  ruby -e "loop { sleep }"

killコマンドでSIGINTというシグナルをrubyプロセスに送ったことで、rubyのプロセスが死んだことが確認できる。
SIGINTシグナルをプロセスが受け取ると、デフォルト値では、そのプロセスは実行を停止する

ハガユウキハガユウキ

SIGINTシグナルをプロセスが受け取った時に、挙動を変更したいなら、Signalモジュールのtrapメソッドを使う

trap("INT") do
  warn "ぬわーーーーっっ!!";
end

loop do
  sleep;
end
ハガユウキハガユウキ

SIGINTシグナルは、プロセスの実行に対して割り込みをかけるシグナル。
SIGTERMシグナルは、プロセスの実行を終了するシグナル
SIGKILLシグナルは、プロセスを強制終了するシグナル

ハガユウキハガユウキ

プロセスグループ

プロセスは、必ず一つのプロセスグループというものに所属している

ps -o pid,pgid,command
  PID  PGID COMMAND
13247 13247 -zsh -g --no_rcs
19375 19375 ruby -e sleep
64897 64897 -zsh -g --no_rcs
ハガユウキハガユウキ

子プロセスは、親プロセスと同じプロセスグループに所属することが分かる。

# fork.rb
Process.fork

sleep
ps -o pid,pgid,command -f
  PID  PGID COMMAND            UID  PPID   C STIME   TTY           TIME
13247 13247 -zsh -g --no_rcs   501   572   0 11:01PM ttys000    0:02.24
20540 20540 ruby fork.rb       501 64897   0 11:42PM ttys003    0:00.14
20553 20540 ruby fork.rb       501 20540   0 11:42PM ttys003    0:00.00
64897 64897 -zsh -g --no_rcs   501   572   0  3:11PM ttys003    0:04.86
ハガユウキハガユウキ

プロセスグループには、リーダーが存在していて、そのリーダーは、PIDとPGIDが同じプロセスである

ハガユウキハガユウキ

プロセスグループのメリットは、killコマンドでプロセスグループを指定することで、プロセスグループに所属しているプロセスを一気に全て消すことができることである。kill で pid を指定する部分に、"-" を付けてあげると、pid ではなくて pgid を指定したことになる。

ps -o pid,pgid,command -f
  PID  PGID COMMAND            UID  PPID   C STIME   TTY           TIME
13247 13247 -zsh -g --no_rcs   501   572   0 11:01PM ttys000    0:02.31
20540 20540 ruby fork.rb       501 64897   0 11:42PM ttys003    0:00.14
20553 20540 ruby fork.rb       501 20540   0 11:42PM ttys003    0:00.00
64897 64897 -zsh -g --no_rcs   501   572   0  3:11PM ttys003    0:04.86
kill -INT -20540
ps -o pid,pgid,command -f
  PID  PGID COMMAND            UID  PPID   C STIME   TTY           TIME
13247 13247 -zsh -g --no_rcs   501   572   0 11:01PM ttys000    0:02.39
64897 64897 -zsh -g --no_rcs   501   572   0  3:11PM ttys003    0:04.87
このスクラップは5ヶ月前にクローズされました