C언어에서 시그널 핸들링과 멀티프로세스 프로그래밍은 시스템 프로그래밍의 핵심적인 주제입니다. 시그널은 운영 체제와 프로세스 간 통신을 가능하게 하는 기본 메커니즘으로, 프로세스가 특정 이벤트에 반응하도록 설계됩니다. 동시에, 멀티프로세스 프로그래밍은 여러 프로세스를 병렬로 실행하여 성능을 극대화하고 효율적인 자원 활용을 돕습니다. 본 기사는 시그널과 멀티프로세스의 개념과 구현 방법을 탐구하고, 실질적인 문제 해결 사례와 실습 예제를 통해 시스템 프로그래밍의 기초와 응용력을 배양합니다.
시그널 핸들링의 개요
시그널은 운영 체제에서 프로세스 간 또는 시스템과 프로세스 간에 비동기적 이벤트를 전달하는 메커니즘입니다. 이를 통해 프로세스는 특정 이벤트가 발생했음을 알리고, 해당 이벤트에 반응할 수 있습니다.
시그널의 역할
시그널은 다음과 같은 상황에서 중요한 역할을 합니다:
- 프로세스 종료: 예를 들어,
SIGTERM
이나SIGKILL
시그널을 통해 프로세스를 종료할 수 있습니다. - 프로세스 중단 및 재개:
SIGSTOP
과SIGCONT
시그널로 프로세스를 일시 중지하거나 다시 실행할 수 있습니다. - 알림 전달:
SIGALRM
을 사용하여 특정 시간 후 이벤트를 발생시킬 수 있습니다.
일반적인 시그널 종류
SIGHUP
: 연결이 끊겼을 때 전달됨SIGINT
: 키보드 입력(Ctrl+C)으로 프로세스를 중단SIGSEGV
: 잘못된 메모리 접근 시 발생SIGPIPE
: 파이프가 끊겼을 때 발생
시그널 핸들링은 이러한 시그널에 대한 사용자 정의 동작을 설정하여, 프로세스가 종료되지 않고 원하는 방식으로 동작하도록 만드는 데 유용합니다.
C언어에서 시그널 핸들링 구현 방법
C언어에서는 signal()
함수와 sigaction()
함수를 사용하여 시그널 핸들러를 설정할 수 있습니다. 두 함수 모두 시그널이 발생했을 때 특정 동작을 정의하는 데 사용되지만, 기능과 유연성에서 차이가 있습니다.
`signal()` 함수
signal()
함수는 간단하게 시그널 핸들러를 설정할 때 사용됩니다.
#include <signal.h>
#include <stdio.h>
void handler(int signum) {
printf("Received signal %d\n", signum);
}
int main() {
signal(SIGINT, handler); // SIGINT 시그널에 대한 핸들러 설정
while (1); // 무한 루프
return 0;
}
위 예제에서 Ctrl+C
를 누르면 SIGINT 시그널이 발생하며, 등록된 핸들러가 실행됩니다.
`sigaction()` 함수
sigaction()
함수는 더 세부적인 제어를 제공합니다.
#include <signal.h>
#include <stdio.h>
void handler(int signum) {
printf("Caught signal %d\n", signum);
}
int main() {
struct sigaction sa;
sa.sa_handler = handler; // 핸들러 지정
sigemptyset(&sa.sa_mask); // 핸들러 실행 중 차단할 시그널 설정
sa.sa_flags = 0; // 추가 옵션
sigaction(SIGINT, &sa, NULL); // SIGINT 시그널 핸들링 설정
while (1); // 무한 루프
return 0;
}
sigaction()
은 시그널 핸들러 실행 중 추가적으로 차단할 시그널을 설정하거나, 다양한 동작 플래그를 제어할 수 있어 더 안전하고 유연합니다.
핸들러 등록 시 주의사항
- 시그널 핸들러는 짧고 신속해야 합니다. 복잡한 작업은 지양해야 합니다.
- 재진입 문제가 발생할 수 있으므로, 안전한 함수만 사용해야 합니다.
sigaction()
을 사용하는 것이signal()
보다 더 안전하고 권장됩니다.
이와 같이 C언어에서는 두 가지 방법으로 시그널을 처리하여, 원하는 동작을 설정할 수 있습니다.
시그널 트러블슈팅 사례
시그널 핸들링은 강력한 도구이지만, 잘못 사용하면 예기치 못한 동작이나 시스템 오류를 초래할 수 있습니다. 여기서는 시그널 처리 중 흔히 발생하는 문제와 이를 해결하는 방법을 설명합니다.
1. 시그널 중첩 문제
같은 시그널이 반복적으로 발생하면 이전 시그널 처리 과정이 완료되지 않은 상태에서 새로운 시그널이 발생해 문제가 될 수 있습니다.
- 문제 상황: 긴 작업이 포함된 시그널 핸들러 실행 중에 동일한 시그널이 발생하여 무한 루프에 빠짐.
- 해결 방법:
sigaction()
함수에서sa_mask
를 사용하여 동일한 시그널을 차단합니다.
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGINT); // SIGINT 차단
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
2. 비재진입 함수 사용
시그널 핸들러에서 printf
나 malloc
같은 비재진입 함수를 사용하면 예상치 못한 동작이 발생할 수 있습니다.
- 문제 상황:
printf
호출 도중 또 다른 시그널이 발생해 데이터 충돌 발생. - 해결 방법:
- 시그널 안전 함수(예:
write
)만 사용하거나, 최소한의 작업만 수행합니다.
void handler(int signum) {
const char msg[] = "Signal received\n";
write(STDOUT_FILENO, msg, sizeof(msg) - 1);
}
3. 프로세스 종료 방지
시그널을 처리하지 않거나 올바르게 설정하지 않으면 프로세스가 예상치 못하게 종료될 수 있습니다.
- 문제 상황:
SIGTERM
발생 시 프로세스가 종료됨. - 해결 방법:
SIGTERM
시그널에 대한 핸들러를 정의하여 종료를 방지하거나 안전하게 처리합니다.
void terminate_handler(int signum) {
printf("Termination signal ignored\n");
}
signal(SIGTERM, terminate_handler);
4. 좀비 프로세스 생성
SIGCHLD
시그널을 처리하지 않으면 자식 프로세스가 종료되더라도 부모 프로세스가 이를 수집하지 않아 좀비 프로세스가 생성됩니다.
- 문제 상황: 부모 프로세스가
wait()
호출을 누락하여 좀비 프로세스가 계속 존재. - 해결 방법:
SIGCHLD
시그널 핸들러에서 자식 프로세스의 상태를 수집합니다.
void child_handler(int signum) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}
signal(SIGCHLD, child_handler);
시그널 핸들링에서 이러한 문제를 사전에 인지하고 적절한 방법으로 해결하면 안정적인 프로그램을 구현할 수 있습니다.
멀티프로세스 프로그래밍의 개요
멀티프로세스 프로그래밍은 여러 프로세스를 병렬로 실행하여 성능과 응답성을 향상시키는 프로그래밍 기법입니다. 멀티프로세스는 프로세스 간의 독립성을 유지하면서 각 프로세스가 별도의 작업을 수행하도록 설계됩니다.
멀티프로세스와 멀티스레드의 차이점
멀티프로세스와 멀티스레드는 비슷한 목적을 가지지만 동작 방식과 장단점에서 차이가 있습니다.
- 멀티프로세스
- 각 프로세스는 독립된 메모리 공간을 사용합니다.
- 한 프로세스의 충돌이 다른 프로세스에 영향을 주지 않습니다.
- 프로세스 간 통신(IPC)이 필요하며, 비교적 복잡하고 느릴 수 있습니다.
- 멀티스레드
- 동일한 메모리 공간을 공유하여 빠르게 통신할 수 있습니다.
- 스레드 간 충돌이 발생할 가능성이 있으며, 동기화가 필요합니다.
멀티프로세스의 장단점
- 장점
- 안정성: 한 프로세스의 오류가 전체 시스템에 영향을 주지 않습니다.
- 보안성: 프로세스 간 독립적인 메모리 공간을 사용하므로 데이터 침범 위험이 적습니다.
- 단점
- 자원 소모: 각 프로세스가 별도의 메모리와 리소스를 필요로 하므로, 메모리 사용량이 증가합니다.
- 속도 저하: IPC를 통한 데이터 교환은 스레드 간 통신보다 느립니다.
멀티프로세스 활용 사례
멀티프로세스는 다음과 같은 상황에서 주로 사용됩니다.
- 서버 개발: 다중 클라이언트 요청을 처리하기 위해 각 요청을 별도 프로세스로 처리합니다.
- 대규모 연산: 복잡한 연산을 병렬로 분할하여 처리 속도를 높입니다.
- 분리된 작업 처리: GUI와 백그라운드 작업을 별도의 프로세스로 처리하여 사용자 경험을 개선합니다.
멀티프로세스는 시스템의 안정성과 독립성을 유지하면서도 높은 성능을 제공해야 하는 환경에서 유용하게 사용됩니다.
C언어에서 멀티프로세스 구현 방법
C언어에서는 fork()
함수를 사용하여 멀티프로세스를 구현할 수 있습니다. fork()
는 새로운 자식 프로세스를 생성하며, 부모와 자식 프로세스는 거의 동일한 코드에서 실행됩니다.
`fork()` 함수의 동작
fork()
함수는 호출한 프로세스를 복제하여 새로운 프로세스를 생성합니다.
- 반환값:
- 부모 프로세스에는 자식 프로세스의 PID(양의 정수)를 반환합니다.
- 자식 프로세스에는 0을 반환합니다.
- 오류 발생 시 -1을 반환합니다.
다음은 간단한 예제입니다.
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork(); // 새로운 프로세스 생성
if (pid > 0) {
// 부모 프로세스
printf("Parent process, PID: %d\n", getpid());
} else if (pid == 0) {
// 자식 프로세스
printf("Child process, PID: %d\n", getpid());
} else {
// 오류 발생
perror("fork failed");
}
return 0;
}
프로세스 간 데이터 공유
부모와 자식 프로세스는 독립된 메모리 공간을 가지므로, 데이터 공유를 위해서는 IPC(Inter-Process Communication) 기법이 필요합니다. 대표적인 방법은 다음과 같습니다.
1. 파이프
파이프를 사용하여 데이터를 한쪽에서 쓰고, 다른 쪽에서 읽습니다.
#include <stdio.h>
#include <unistd.h>
int main() {
int pipefd[2];
pipe(pipefd);
pid_t pid = fork();
if (pid == 0) {
// 자식 프로세스: 데이터를 읽음
close(pipefd[1]); // 쓰기 닫기
char buffer[20];
read(pipefd[0], buffer, sizeof(buffer));
printf("Child received: %s\n", buffer);
} else {
// 부모 프로세스: 데이터를 씀
close(pipefd[0]); // 읽기 닫기
write(pipefd[1], "Hello, child!", 14);
}
return 0;
}
2. 공유 메모리
shmget()
와 shmat()
를 사용하여 프로세스 간 공유 메모리를 설정합니다.
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main() {
int shmid = shmget(IPC_PRIVATE, 1024, IPC_CREAT | 0666);
char *shared_memory = (char *)shmat(shmid, NULL, 0);
pid_t pid = fork();
if (pid == 0) {
// 자식 프로세스
strcpy(shared_memory, "Hello from child!");
} else {
// 부모 프로세스
wait(NULL); // 자식 프로세스 완료 대기
printf("Parent read: %s\n", shared_memory);
}
shmdt(shared_memory);
shmctl(shmid, IPC_RMID, NULL); // 공유 메모리 제거
return 0;
}
멀티프로세스 프로그래밍에서의 주의점
- 자원 관리: 자식 프로세스가 종료되지 않으면 좀비 프로세스가 생성됩니다.
wait()
를 사용하여 처리해야 합니다. - IPC 동기화: 데이터 충돌을 방지하기 위해 동기화 메커니즘을 고려해야 합니다.
- 성능 고려: 많은 프로세스를 생성하면 시스템 리소스를 과도하게 소모할 수 있습니다.
위의 방법을 통해 C언어로 멀티프로세스를 구현하고, 효율적인 데이터 교환과 자원 관리를 설계할 수 있습니다.
시그널과 멀티프로세스의 상호작용
멀티프로세스 환경에서 시그널은 프로세스 간 통신 및 제어를 위해 중요한 역할을 합니다. 부모와 자식 프로세스는 시그널을 통해 서로 상호작용하며, 특정 이벤트에 따라 동작을 조율할 수 있습니다.
멀티프로세스에서의 시그널 처리
멀티프로세스 환경에서는 각 프로세스가 독립적으로 시그널을 처리할 수 있습니다. 부모와 자식 프로세스는 각각 별도의 시그널 핸들러를 설정하거나, 공통된 핸들러를 사용할 수 있습니다.
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handler(int signum) {
printf("Process %d received signal %d\n", getpid(), signum);
}
int main() {
signal(SIGUSR1, handler);
pid_t pid = fork();
if (pid == 0) {
// 자식 프로세스
sleep(1);
kill(getppid(), SIGUSR1); // 부모 프로세스에 시그널 전송
} else if (pid > 0) {
// 부모 프로세스
pause(); // 시그널 대기
}
return 0;
}
위 코드에서는 자식 프로세스가 부모 프로세스에 시그널을 전송하며, 부모는 해당 시그널을 수신하고 처리합니다.
멀티프로세스 환경에서 시그널 차단
멀티프로세스에서는 특정 프로세스에서 시그널을 차단해야 하는 경우가 종종 있습니다. sigprocmask()
함수를 사용하여 특정 시그널을 차단하거나 복원할 수 있습니다.
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler(int signum) {
printf("Signal %d received in process %d\n", signum, getpid());
}
int main() {
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGUSR1, &sa, NULL);
pid_t pid = fork();
if (pid == 0) {
// 자식 프로세스: 시그널 차단
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
sigprocmask(SIG_BLOCK, &set, NULL);
printf("Child process blocking SIGUSR1\n");
sleep(3); // 차단 중
sigprocmask(SIG_UNBLOCK, &set, NULL); // 차단 해제
printf("Child process unblocked SIGUSR1\n");
} else {
// 부모 프로세스: 자식 프로세스에 시그널 전송
sleep(1);
kill(pid, SIGUSR1);
printf("Parent sent SIGUSR1 to child\n");
}
return 0;
}
위 코드에서 자식 프로세스는 SIGUSR1
시그널을 일시적으로 차단하고, 차단 해제 후 시그널을 처리합니다.
좀비 프로세스 방지와 시그널
SIGCHLD
시그널을 사용하면 부모 프로세스가 자식 프로세스의 종료를 감지하고, 좀비 프로세스를 방지할 수 있습니다.
#include <stdio.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
void child_handler(int signum) {
int status;
while (waitpid(-1, &status, WNOHANG) > 0); // 종료된 자식 프로세스 수집
printf("Child process terminated\n");
}
int main() {
signal(SIGCHLD, child_handler);
if (fork() == 0) {
// 자식 프로세스
printf("Child process running\n");
sleep(2);
return 0;
} else {
// 부모 프로세스
sleep(5); // 자식 종료 후 SIGCHLD 처리
}
return 0;
}
SIGCHLD
시그널을 활용하면 자식 프로세스의 종료를 효율적으로 관리할 수 있습니다.
멀티프로세스에서의 시그널 활용 요령
- 적절한 시그널 핸들링: 각 프로세스에 맞는 시그널 핸들러를 설정합니다.
- 시그널 차단과 동기화: 중요한 작업 중 시그널 차단을 통해 불필요한 중단을 방지합니다.
- 부모-자식 간 상호작용 설계: 시그널을 활용하여 작업 완료, 상태 보고 등의 이벤트를 전달합니다.
시그널과 멀티프로세스의 상호작용은 병렬 환경에서 효율적인 이벤트 관리와 제어를 가능하게 합니다. 이를 통해 안정적이고 효과적인 프로그램을 설계할 수 있습니다.
병렬 처리와 성능 최적화
멀티프로세스 프로그래밍에서 병렬 처리는 시스템 리소스를 효율적으로 활용하여 실행 속도를 향상시키는 핵심 기법입니다. 특히, CPU 코어를 최대한 활용하고, 프로세스 간의 작업 부하를 균등하게 분배하는 것이 중요합니다.
병렬 처리의 필요성
병렬 처리는 다음과 같은 상황에서 성능 개선을 위해 필요합니다:
- 대규모 데이터 처리: 데이터 분석이나 머신 러닝 작업에서 대량의 데이터를 병렬로 처리.
- 실시간 응답성 개선: 사용자 요청을 빠르게 처리하기 위해 병렬 실행.
- CPU 활용 극대화: 다중 코어 환경에서 모든 코어를 활용하여 처리 속도 증가.
멀티프로세스 환경에서 병렬 처리 구현
멀티프로세스 병렬 처리를 구현하려면 프로세스 간 작업 분배와 결과 수집이 중요합니다.
1. 작업 분배
fork()
를 사용하여 작업을 병렬로 처리하도록 프로세스를 생성합니다.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void perform_task(int process_num) {
printf("Process %d is performing its task\n", process_num);
sleep(2); // 작업 수행 시뮬레이션
}
int main() {
int num_processes = 4;
for (int i = 0; i < num_processes; i++) {
pid_t pid = fork();
if (pid == 0) {
perform_task(i + 1);
exit(0);
}
}
for (int i = 0; i < num_processes; i++) {
wait(NULL); // 자식 프로세스가 모두 종료될 때까지 대기
}
printf("All tasks completed.\n");
return 0;
}
2. 프로세스 간 데이터 교환
데이터 교환을 위해 파이프, 공유 메모리, 메시지 큐 등을 활용합니다.
#include <stdio.h>
#include <unistd.h>
int main() {
int pipefd[2];
pipe(pipefd);
pid_t pid = fork();
if (pid == 0) {
// 자식 프로세스
close(pipefd[0]);
int result = 42; // 계산 결과
write(pipefd[1], &result, sizeof(result));
close(pipefd[1]);
} else {
// 부모 프로세스
close(pipefd[1]);
int received;
read(pipefd[0], &received, sizeof(received));
close(pipefd[0]);
printf("Parent received result: %d\n", received);
}
return 0;
}
성능 최적화 기법
병렬 처리에서 성능을 최적화하려면 다음 기법을 적용해야 합니다:
- 작업 부하 균등 분배: 모든 프로세스가 유사한 작업량을 가지도록 설계합니다.
- 프로세스 간 통신 최소화: 데이터 교환을 최소화하여 오버헤드를 줄입니다.
- I/O 병목 해결: 비동기 I/O 또는 I/O 작업과 계산 작업을 병렬로 처리합니다.
멀티코어 활용 사례
멀티프로세스를 통해 다중 코어 환경에서 효율적으로 병렬 처리를 수행할 수 있습니다. 예를 들어, 큰 배열의 요소 합계를 구하는 작업을 프로세스별로 분리하면 실행 속도가 크게 향상됩니다.
예: 배열 합계 병렬 처리
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#define SIZE 100
#define NUM_PROCESSES 4
void calculate_partial_sum(int *array, int start, int end, int *pipefd) {
int sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
write(pipefd[1], &sum, sizeof(sum));
close(pipefd[1]);
}
int main() {
int array[SIZE];
for (int i = 0; i < SIZE; i++) {
array[i] = i + 1;
}
int pipefd[2];
pipe(pipefd);
int chunk = SIZE / NUM_PROCESSES;
for (int i = 0; i < NUM_PROCESSES; i++) {
pid_t pid = fork();
if (pid == 0) {
calculate_partial_sum(array, i * chunk, (i + 1) * chunk, pipefd);
exit(0);
}
}
close(pipefd[1]);
int total_sum = 0, partial_sum;
for (int i = 0; i < NUM_PROCESSES; i++) {
read(pipefd[0], &partial_sum, sizeof(partial_sum));
total_sum += partial_sum;
}
close(pipefd[0]);
printf("Total sum: %d\n", total_sum);
for (int i = 0; i < NUM_PROCESSES; i++) {
wait(NULL);
}
return 0;
}
병렬 처리에서의 주의점
- 리소스 사용량 관리: 과도한 프로세스 생성은 성능 저하를 초래할 수 있습니다.
- 데드락 방지: 프로세스 간 동기화 문제가 발생하지 않도록 설계합니다.
- 효율적 스케줄링: 작업량에 따라 적절한 병렬화를 설계합니다.
병렬 처리와 성능 최적화 기법을 통해 멀티프로세스 프로그래밍의 잠재력을 극대화할 수 있습니다.
실습 예제: 시그널과 멀티프로세스 구현
이번 실습에서는 시그널을 활용하여 멀티프로세스 환경에서의 프로세스 간 통신과 작업 조율을 구현합니다. 이 예제는 부모 프로세스가 자식 프로세스를 생성하고, 시그널을 사용하여 작업을 시작하고 종료하는 구조를 보여줍니다.
예제 설명
- 부모 프로세스는 여러 자식 프로세스를 생성합니다.
- 부모는 각 자식 프로세스에
SIGUSR1
시그널을 보내 작업을 시작하도록 지시합니다. - 작업 완료 후 자식 프로세스는 부모에게
SIGUSR2
시그널을 보내 작업 종료를 알립니다.
구현 코드
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#define NUM_CHILDREN 3
// 자식 프로세스의 작업 수행
void child_task(int signum) {
printf("Child %d received start signal\n", getpid());
sleep(2); // 작업 수행 시뮬레이션
printf("Child %d completed task\n", getpid());
kill(getppid(), SIGUSR2); // 부모에게 작업 완료 알림
exit(0);
}
// 부모가 자식의 작업 완료를 처리
void parent_task(int signum) {
static int completed = 0;
completed++;
printf("Parent received completion signal (%d/%d tasks completed)\n", completed, NUM_CHILDREN);
if (completed == NUM_CHILDREN) {
printf("All tasks completed. Parent exiting.\n");
}
}
int main() {
pid_t pids[NUM_CHILDREN];
// 부모가 SIGUSR2를 처리하도록 설정
signal(SIGUSR2, parent_task);
// 자식 프로세스 생성
for (int i = 0; i < NUM_CHILDREN; i++) {
pid_t pid = fork();
if (pid == 0) {
signal(SIGUSR1, child_task); // 자식이 SIGUSR1 처리
pause(); // 시그널 대기
} else {
pids[i] = pid;
}
}
sleep(1); // 자식이 대기 상태에 들어갈 시간 확보
// 부모가 자식들에게 작업 시작 신호를 보냄
for (int i = 0; i < NUM_CHILDREN; i++) {
printf("Parent sending start signal to child %d\n", pids[i]);
kill(pids[i], SIGUSR1);
}
// 부모는 모든 자식 프로세스가 완료될 때까지 대기
for (int i = 0; i < NUM_CHILDREN; i++) {
wait(NULL);
}
printf("Parent process exiting.\n");
return 0;
}
실습 코드 설명
- 시그널 처리 설정:
- 부모는
SIGUSR2
를 처리하도록 설정하여 자식의 작업 완료를 감지합니다. - 자식은
SIGUSR1
을 처리하여 작업을 시작합니다.
- 자식 프로세스 생성:
fork()
를 사용하여 여러 자식 프로세스를 생성합니다.- 각 자식 프로세스는
pause()
로 대기 상태에 들어갑니다.
- 작업 조율:
- 부모는 자식들에게
SIGUSR1
을 보내 작업을 시작하도록 지시합니다. - 자식은 작업 완료 후 부모에게
SIGUSR2
를 전송합니다.
- 작업 완료 확인:
- 부모는 자식들의 작업 완료를 순차적으로 확인하며, 모든 작업이 완료되면 종료합니다.
실행 결과 예시
Parent sending start signal to child 12345
Parent sending start signal to child 12346
Parent sending start signal to child 12347
Child 12345 received start signal
Child 12345 completed task
Parent received completion signal (1/3 tasks completed)
Child 12346 received start signal
Child 12346 completed task
Parent received completion signal (2/3 tasks completed)
Child 12347 received start signal
Child 12347 completed task
Parent received completion signal (3/3 tasks completed)
All tasks completed. Parent exiting.
Parent process exiting.
학습 포인트
- 시그널을 통한 비동기 통신: 시그널을 사용해 비동기 이벤트를 처리하는 방법을 학습합니다.
- 멀티프로세스 동기화: 부모와 자식 프로세스 간의 동기화와 작업 조율을 구현합니다.
- 확장 가능성: 작업 분배를 쉽게 확장할 수 있는 구조를 설계합니다.
이 실습 예제는 시그널과 멀티프로세스를 활용하여 시스템 프로그래밍의 주요 개념을 익히는 데 유용합니다.
요약
본 기사에서는 C언어에서의 시그널 핸들링과 멀티프로세스 프로그래밍의 기본 개념과 구현 방법을 다뤘습니다. 시그널의 정의와 역할, 멀티프로세스 환경에서의 활용, 병렬 처리 기법, 그리고 실습 예제를 통해 실무에 적용 가능한 기술을 익힐 수 있었습니다. 시그널과 멀티프로세스를 효과적으로 활용하면 시스템 자원을 효율적으로 관리하고, 안정적이며 성능 최적화된 프로그램을 개발할 수 있습니다.