본문 바로가기
Study/Python

Python os.fork, os.wait 멀티프로세싱 3

by 개발새-발 2021. 11. 19.
반응형

학부에서 Linux 시스템 프로그래밍을 배울 때 멀티프로세싱을 다루었었다. 이때 fork, wait의 system call을 사용하였었다. 그런데 이 system call들을 python에서도 사용할 수 있다. 이 systam call들은 os 모듈을 사용하여 접근할 수 있다. 이를 이용하면 파이썬에서도 c에서와 유사하게 multi-processing을 할 수 있다. 단, Ms Windows처럼 이 system call들을 지원하지 않는 운영체제에서는 사용할 수 없다.

fork

c에서 forkfork를 사용한 시점에서 새로운 프로세스를 생성하는 system call이다. fork 이후에는 기존 프로세스와 새롭게 생성된 프로세스 모두에서 이후 코드들이 실행되게 한다. python에서의 fork 도 이와 동일하다.

  • fork
    • 인자 : 없음
    • 반환 값
      • 0 : 새롭게 생성된 자식 프로세스에서 코드가 실행되는 경우
      • pid > 0 : 자식 프로세스를 생성한 부모 프로세스에서 코드가 실행되는 경우 자식 프로세스의 pid 값을 반환한다.

아래 예제 코드를 보자.

import os

print("Started");

if os.fork() == 0:
    # child process
    print("Child Process pid : ", os.getpid(), " ppid : ", os.getppid())
else :
    # parent process
    print("Parent Process pid : ", os.getpid(), " ppid : ", os.getppid())

print("END")

이 코드를 실행하면 아래와 같은 출력을 얻을 수 있다.

Started
Parent Process pid :  278  ppid :  93
END
Child Process pid :  279  ppid :  278
END

Parent process의 pid값이 Child Process pidgetppid()로 얻어온 값과 같은 것을 볼 수 있다. 여기서 코드상에서는 print("END")는 한 줄인데 출력에는 두 번 출력된 것을 볼 수 있다. 이는 아까 언급하였듯이 fork() 실행 이후 기존 프로세스와 새로 생성된 프로세스에서 모두 아래의 코드들을 실행하기 때문이다. child process에서는 fork()0을 반환하였고, parent process에서는 fork가 양수값을 반환하였기 때문에 두 프로세스가 fork 이후의 코드를 실행한다고 하더라도 이 반환 값을 기준으로 다른 행동을 하게 할 수 있다.

fork가 발생하면 process의 메모리 영역을 복사하여 새로운 프로세스를 만들게 된다. 코드상에서 보았을 때 변수명이 같더라도 각 프로세스에 존재하는 다른 변수가 되는 것이다. 아래 예제를 보자.

import os

print("Started");

x = 0

if os.fork() == 0:
    # child process
    print("Child Process pid : ", os.getpid(), " ppid : ", os.getppid())
    x += 5;
else :
    # parent process
    print("Parent Process pid : ", os.getpid(), " ppid : ", os.getppid())
    x -= 5;

print("END x : ",x," pid : ",os.getpid())

분명 두 프로세스에서 x를 조작하고 있다. 한 프로세스에서는 x5를 더하고, 다른 곳에서는 5를 빼고 있다. 하지만 x는 두 어느 프로세스에서도 0이 아니다. 아래는 위 코드의 출력 결과이다.

Started
Parent Process pid :  512  ppid :  93
END x :  -5  pid :  512
Child Process pid :  513  ppid :  512
END x :  5  pid :  513

하나의 프로세스에서는 5, 다른 프로세스에서는 -5x가 변경되어있는 것을 볼 수 있다. fork가 진행된 이후에는 변수명이 같아도 한 프로세스의 변수를 건드려서 다른 프로세스의 동일 이름의 변수의 값이 변하게 할 수 없다. 만약 이를 원한다면 IPC를 이용하여 두 프로세스 간 통신을 하여야 한다.

아래 다른 예제를 보자.

import os

print("A")
os.fork()
print("B")
os.fork()
print("C")
os.fork()
print("D")

A, B, C, D는 각각 몇 번 출력될까?

이 python 코드의 실행 결과는 다음과 같다.

A
B
B
C
C
C
D
C
D
D
D
D
D
D
D

A,B,C,D 가 각각 1, 2, 4, 8번 출력되었다. fork() 실행 후 기존 프로세스와 새로 생성된 프로세스 둘 다 뒤에 나오는 코드들을 모두 실행하기 때문이다. A 출력 후 프로세스가 하나 더 생겨 2개가 되고, 이 두 프로세스가 B를 출력한다. 이 두 프로세스가 fork를 실행하면 각각의 프로세스가 새로운 프로세스를 생성하게 되어 2개가 더 생길 것이다. 총 4개의 프로세스가 실행하게 되고 이들은 C를 출력하게 될 것이다. 이후 이 4개의 프로세스가 fork를 만나 각각 새로운 프로세스를 하나 더 만들게 되고 이후 총 프로세스는 8개가 된다. 이 8개의 프로세스는 각각 D를 출력하게 된다.

wait

child process가 끝나기 전에 parent process가 먼저 끝날 수 있다. 이를 원치 않을 때 우리는 child process를 기다려 줄 수 있다.

  • wait
    • 인자 : 없음
    • 반환 값 : (종료된 프로세스의 pid, exit status를 유추할 수 있는 정수 값)을 담은 tuple이다.

아래와 같은 코드가 있다고 하자.

import os
import time

print("Started")

if os.fork() == 0:
    # child process
    print("Child process")
    time.sleep(3)
    print("child ended")
    os._exit(0)

else :
    print("Parent process")
    print("Parent ended")

child process의 실행 시간은 3초 이상이고, parent process의 실행 시간은 그보다 짧을 것이다. 이 때문에 이 코드에서는 parent process가 먼저 종료되어 아래와 같은 출력 결과를 볼 수 있다.

Started
Parent process
Parent ended
Child process
$ child ended

parent가 먼저 끝나 shell의 $가 뜨고 약간의 시간이 지난 뒤에 "child ended" 문구가 출력 된다.

wait를 사용하여 child process를 기다리게 해보자.

import os
import time

print("Started")

if os.fork() == 0:
    # child process
    print("Child process")
    time.sleep(3)
    print("child ended")
    os._exit(0)

else :
    print("Parent process")
    os.wait()
    print("Parent ended")
Parent process
Child process
child ended
Parent ended
$ 

"child ended" 이후에 "Parent ended" 문구가 출력되는 것을 볼 수 있다.

다른 system call들

forkwait 이외에도 다른 system call들과 관련 기능을 사용할 수 있다. os 모듈의 waitpid도 사용할 수 있으며, WIFEXITED, WEXITSTATUS, WIFSIGNALED 등을 통하여 프로세스 종료에 관한 정보를 얻는 것 또한 python에서 사용할 수 있다. 이외에도 다양한 것들을 python에서 사용할 수 있다.

반응형

댓글