[C言語] プロセス生成(fork)に使うwait()について解説
C言語におけるプロセス生成では、親プロセスが子プロセスの終了を待つためにwait()
関数を使用します。
wait()
は、子プロセスが終了するまで親プロセスをブロックし、終了した子プロセスのプロセスIDと終了ステータスを取得します。
この関数は、子プロセスのリソースを解放し、ゾンビプロセスの発生を防ぐために重要です。
また、複数の子プロセスがある場合、どの子プロセスが終了したかを特定するためにwaitpid()
関数を使用することもあります。
プロセス生成とwait()の基本
プロセス生成とは
プロセス生成は、オペレーティングシステムが新しいプロセスを作成することを指します。
C言語では、fork()関数
を使用してプロセスを生成します。
プロセス生成は、親プロセスが子プロセスを作成し、並行して処理を行うために重要です。
これにより、複数のタスクを同時に実行することが可能になります。
fork()関数の役割
fork()関数
は、現在のプロセスを複製し、新しいプロセス(子プロセス)を作成します。
以下は、fork()関数
の基本的な使い方を示すサンプルコードです。
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork(); // プロセスを生成
if (pid < 0) {
// fork()が失敗した場合
perror("fork failed");
return 1;
} else if (pid == 0) {
// 子プロセスの処理
printf("This is the child process.\n");
} else {
// 親プロセスの処理
printf("This is the parent process.\n");
}
return 0;
}
このコードでは、fork()
を呼び出すことで、親プロセスと子プロセスが生成されます。
fork()
は親プロセスでは子プロセスのPIDを返し、子プロセスでは0を返します。
wait()関数の役割
wait()関数
は、親プロセスが子プロセスの終了を待つために使用されます。
子プロセスが終了するまで親プロセスをブロックし、終了した子プロセスの情報を取得します。
以下は、wait()関数
を使用したサンプルコードです。
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
return 1;
} else if (pid == 0) {
printf("Child process is running.\n");
sleep(2); // 子プロセスが2秒間実行
printf("Child process is exiting.\n");
} else {
wait(NULL); // 子プロセスの終了を待つ
printf("Parent process continues after child process exits.\n");
}
return 0;
}
このコードでは、親プロセスがwait()
を呼び出すことで、子プロセスの終了を待ちます。
子プロセスが終了すると、親プロセスは続行します。
wait()とwaitpid()の違い
wait()
とwaitpid()
はどちらも子プロセスの終了を待つために使用されますが、いくつかの違いがあります。
特徴 | wait() | waitpid() |
---|---|---|
子プロセスの指定 | 特定の子プロセスを指定できない | 特定の子プロセスを指定可能 |
非同期処理 | 非同期処理には不向き | 非同期処理に適している |
オプション | オプションなし | オプションを指定可能 |
waitpid()
は、特定の子プロセスを指定して待つことができ、非同期処理に適しています。
また、オプションを指定することで、非ブロッキングモードでの動作も可能です。
wait()とプロセスの終了
子プロセスの終了を待つ理由
子プロセスの終了を待つことは、システムリソースの管理やプロセスの整合性を保つために重要です。
親プロセスが子プロセスの終了を待たずに進行すると、子プロセスの終了ステータスが回収されず、システムに不要なリソースが残る可能性があります。
これにより、システムのパフォーマンスが低下することがあります。
ゾンビプロセスとは
ゾンビプロセスとは、子プロセスが終了した後も、その終了ステータスが親プロセスによって回収されていない状態のプロセスを指します。
ゾンビプロセスは、プロセステーブルにエントリを残し続けるため、システムリソースを無駄に消費します。
ゾンビプロセスが増えると、プロセステーブルがいっぱいになり、新しいプロセスを生成できなくなる可能性があります。
ゾンビプロセスを防ぐ方法
ゾンビプロセスを防ぐためには、親プロセスが子プロセスの終了を適切に待ち、終了ステータスを回収する必要があります。
以下の方法でゾンビプロセスを防ぐことができます。
wait()
またはwaitpid()
を使用して、子プロセスの終了を待つ- シグナルハンドラを設定して、
SIGCHLD
シグナルを受け取ったときに子プロセスの終了を処理する
wait()を使ったゾンビプロセスの回避
wait()関数
を使用することで、ゾンビプロセスを回避することができます。
以下は、wait()
を使ってゾンビプロセスを防ぐサンプルコードです。
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
return 1;
} else if (pid == 0) {
printf("Child process is running.\n");
sleep(2); // 子プロセスが2秒間実行
printf("Child process is exiting.\n");
} else {
wait(NULL); // 子プロセスの終了を待ち、ゾンビプロセスを防ぐ
printf("Parent process continues after child process exits.\n");
}
return 0;
}
このコードでは、親プロセスがwait()
を呼び出すことで、子プロセスの終了を待ち、ゾンビプロセスを防いでいます。
wait()
が呼び出されると、子プロセスの終了ステータスが回収され、プロセステーブルからエントリが削除されます。
これにより、システムリソースが無駄に消費されることを防ぎます。
wait()の応用例
複数の子プロセスを管理する
wait()関数
は、複数の子プロセスを管理する際にも役立ちます。
親プロセスが複数の子プロセスを生成した場合、それぞれの子プロセスの終了を待つ必要があります。
wait()
をループ内で使用することで、すべての子プロセスの終了を順次待つことができます。
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
for (int i = 0; i < 3; i++) {
pid_t pid = fork();
if (pid == 0) {
printf("Child process %d is running.\n", i);
sleep(1); // 各子プロセスが1秒間実行
printf("Child process %d is exiting.\n", i);
return 0;
}
}
for (int i = 0; i < 3; i++) {
wait(NULL); // 各子プロセスの終了を待つ
}
printf("All child processes have exited.\n");
return 0;
}
このコードでは、3つの子プロセスを生成し、それぞれの終了を待っています。
wait()
をループ内で呼び出すことで、すべての子プロセスが終了するまで親プロセスは待機します。
非同期処理でのwait()の活用
非同期処理では、親プロセスが他のタスクを実行しながら子プロセスの終了を待つことが求められます。
waitpid()
を非ブロッキングモードで使用することで、親プロセスが他の処理を行いながら子プロセスの終了を確認できます。
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("Child process is running.\n");
sleep(2); // 子プロセスが2秒間実行
printf("Child process is exiting.\n");
return 0;
} else {
int status;
while (waitpid(pid, &status, WNOHANG) == 0) {
printf("Parent process is doing other work.\n");
sleep(1); // 他の作業を1秒間実行
}
printf("Child process has exited.\n");
}
return 0;
}
このコードでは、waitpid()
をWNOHANG
オプションと共に使用し、非ブロッキングで子プロセスの終了を確認しています。
シグナルハンドリングとwait()
シグナルハンドリングを使用して、SIGCHLD
シグナルを受け取ったときに子プロセスの終了を処理することができます。
これにより、親プロセスが子プロセスの終了を即座に検知し、適切にリソースを解放できます。
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
void sigchld_handler(int signo) {
while (waitpid(-1, NULL, WNOHANG) > 0) {
// 子プロセスの終了を処理
}
}
int main() {
signal(SIGCHLD, sigchld_handler); // SIGCHLDシグナルハンドラを設定
pid_t pid = fork();
if (pid == 0) {
printf("Child process is running.\n");
sleep(2); // 子プロセスが2秒間実行
printf("Child process is exiting.\n");
return 0;
}
// 親プロセスの他の処理
while (1) {
printf("Parent process is running.\n");
sleep(1);
}
return 0;
}
このコードでは、SIGCHLD
シグナルを受け取ったときにsigchld_handler
が呼び出され、子プロセスの終了を処理します。
並列処理でのwait()の利用
並列処理では、複数の子プロセスが同時に実行されるため、wait()
やwaitpid()
を使用して各プロセスの終了を適切に管理することが重要です。
これにより、プロセス間の同期を確保し、リソースの無駄を防ぎます。
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
for (int i = 0; i < 3; i++) {
pid_t pid = fork();
if (pid == 0) {
printf("Child process %d is running.\n", i);
sleep(2); // 各子プロセスが2秒間実行
printf("Child process %d is exiting.\n", i);
return 0;
}
}
for (int i = 0; i < 3; i++) {
wait(NULL); // 各子プロセスの終了を待つ
}
printf("All child processes have exited.\n");
return 0;
}
このコードでは、3つの子プロセスが並列に実行され、それぞれの終了を待つことで、プロセス間の同期を確保しています。
まとめ
この記事では、C言語におけるプロセス生成とwait()関数
の基本的な役割から、ゾンビプロセスの回避方法、さらにwait()
の応用例について詳しく解説しました。
プロセス管理の重要性を理解し、適切な関数の選択や実装方法を学ぶことで、効率的なプログラムを構築するための基礎を築くことができます。
これを機に、実際のプログラムでwait()
やfork()
を活用し、より高度なプロセス管理に挑戦してみてはいかがでしょうか。