プロセスとは
OS が実行ファイルを読み込んで実行するためにメモリや CPU をまとめたプログラムの実行単位
がプロセスです。
僕は普段 GCP
の中でも GAE
をよく使います。golang で API を開発するときに GAE は相性がいいです。個人的には golang+GAE の場合はデプロイの速度がかなり早いところが地味に気に入っていたりします。GAE にデプロイした golang で作成したサーバーは GAE 上ではプロセスを立ち上げる事でリクエストを受け付ける事ができる状態になります。スピンアップと言うのですが、これが爆速なのも良い点です。
余談はさておき、本記事ではプロセスの中身について覗いてみることにします。golang の視点や OS の視点からみたプロセスについてコードを示しながら考えていくことにしたいと思います。
golang からみたプロセス
プロセスには必ず プロセス ID
という識別子が存在します。コメントアウト部分は例ですけど、このように一意に識別される値で表現されます。 go run main.go
で実行したプロセスは今回の例の場合だと 50927
というプロセス ID で実行されていることになります。
fmt.Printf("プロセスID: %d\n", os.Getpid()) // プロセスID: 50927
fmt.Printf("親プロセスID: %d\n", os.Getppid()) // 親プロセスID: 50916
プロセスは実行するたびに識別子が変わります。上記のコードを何回か実行してみたら識別子が毎回変わる事がわかると思います。プロセスは起動した後、実行ファイルの EOF に到達したらプロセスを停止しているので、再度起動したときには空いているプロセス ID を割り当てるのでしょう。
次にプロセスを実行するユーザを表示してみましょう。
fmt.Printf("ユーザID: %d\n", os.Getuid()) // ユーザID: 500
fmt.Printf("グループID: %d\n", os.Getgid()) // グループID: 20
この場合であればユーザ ID が 500 のユーザーがファイルを実行してプロセス ID が 50927 としてプロセスを起動したことになります。また、グループ ID というのも一緒に出力していますが、わかりやすい例としては、パイプで繋げたコマンドなどです。
cat hoge.txt | echo "hoge"
cat
と echo
は同じプロセスグループになります。
プロセスの入出力
プロセスは 3 つの入出力データを持っています。
- コマンドライン引数
- 環境変数
- 終了コード
コマンドライン引数
以下のようなコードを用意しておいて、
func main() {
fmt.Printf("コマンドライン引数: %s\n", os.Args[1])
}
以下を実行すると、100
が os.Args[1]
渡されます。
go run main.go 100
環境変数
golang を扱う上で GOPATH
を設定したかと思いますが、
func main() {
fmt.Println("GOPATH: " + os.Getenv("GOPATH")) // GOPATH: / ~ some path/Go
}
環境変数を bash で表示させた時と同じ値が取得できていると思います。
echo $GOPATH
環境変数はとても頻繁に用いる概念?なので使えるようになっておいて無駄になることはないと思います。例えば、Git に含ませたくない秘匿情報は環境変数として設定しておいてアプリ上では、 os.Getenv()
関数で利用できるようにするというのは常套手段です。
終了コード
プロセスを終了させるときは終了コード(os.Exit()
関数)を呼びます。
func main() {
os.Exit(1) // exit status 1
}
慣習として、 0=正常終了
、 1=エラー終了
という扱いになっています。
OS から見たプロセス
OS からプロセスを見てみると、数多くのプロセスを CPU 時間を使って効率よく動作させるタスクと考えられます。
golang のプログラムから他のプロセスを起動する
golang で実行しているプログラムから、他のプロセスを起動するためには exec.Command
を使う事ができます
exec.Command
では早速、exec.Command で他のプロセスを扱ってみましょう。OS 標準で使える ls
コマンドを実行してみましょう。
func main() {
out, _ := exec.Command("ls").Output()
fmt.Println(string(out))
out, _ = exec.Command("sleep", "3").Output()
fmt.Println(string(out))
out, _ = exec.Command("ls", "-la").Output()
fmt.Println(string(out))
}
せっかくなので、第二引数も設定したコードも実行できるようにしておきました。出力された文字列は、console 上で直接 ls
や ls -la
と実行した時と同じ結果だったのではないでしょうか。ついでに sleep 3
も実行してみました。 sleep
コマンドは返り値があるわけではないので、画面上は何も表示されていないと思いますが、最初の ls
から 3 秒待った後に ls -la
が実行されているはずです。
まとめ
本記事ではシステムコールを呼び出しながらプロセスの ID やグループについて確認し、環境変数やプロセスを閉じるようなサンプルコードを載せてみました。golang の強みである goroutine(並列処理)でまた出てくると思いますので、その記事を書くときさらに詳細に考えてみようと思います。