python 동시성 관리 (1) - 프로세스(Process)와 스레드(Thread)

8

Python 동시성관리

목록 보기
1/3
post-thumbnail

들어가기에 앞서

이번 시리즈는 Python의 동시성 관리에 대해서 알아보도록 하겠습니다.
따라서 Python 동시성 관리에 필요한 여러가지 개념들을 이해하고, 동시성 관리에 대해서 알아보도록 하겠습니다!

먼저 이번 시간에는 프로세스(Process)와 스레드(Thread) 개념에 관하여 알아보겠습니다.
목차는 다음과 같습니다.

  • 프로세스(Process)와 스레드(Thread) 개념
  • Python GIL(Global Interpreter Lock)
  • 코루틴(Coroutine)
  • 동시성 관리 구현에 유용한 모듈 예제(Futures, asyncio)
                [이 글은 Python 언어 기반으로 작성되었습니다.]

Python 동시성 관리를 이해하기 위하여 알아야 할 것들

먼저 동시성 관리를 설명하기 전에, 기본적으로 이해해야 할 것들이 존재합니다.
크게 3가지에 대해서 설명할텐데요! 3가지는 다음과 같습니다.

  • 프로세스와 스레드 개념
  • Python GIL(Global Interpreter Lock)
  • 코루틴(Coroutine)

세가지 개념 중 먼저 프로세스와 스레드 개념에 대해서 이해해보도록 하겠습니다.

프로세스(Process)와 스레드(Thread)

프로세스란 무엇일까요?

먼저 프로세스(Process)가 정확히 무엇인지 알아보도록 하겠습니다.
먼저 프로세스의 정의부터 읽고 가보도록 하죠.

프로세스란, 실행 중에 있는 프로그램(Program) 을 의미합니다. 스케줄링의 대상이 되는 작업(task)과 같은 의미로 쓰입니다. 프로세스 내부에는 최소 하나의 스레드(thread)를 가지고있는데, 실제로는 스레드(thread)단위로 스케줄링을 합니다.

위의 정의를 통해 프로세스에 대한 직관적인 개념은 쉽게 파악할 수가 있는데요,
실행 중에 있는 프로그램을 의미한다고 말하고 있습니다.
실행 중에 있는 프로그램이라고 한다면, 프로그램은 실행 중이 아닌 것을 말하는 걸까요?
그렇다면, "프로그램"은 다음과 같은 APP과 같은 것들이라고 말할 수 있을까요?

프로그램은 다음과 같이 정의하고 있습니다.

프로그램(Program)이란, 파일이 저장장치(하드디스크, SSD)에 저장되어 있지만,
메모리에는 올라가 있지 않은 상태 를 의미합니다.

중요한 포인트는, 아직 실행되지 않은 상태 라는 것입니다!
즉 아직 생명을 불어넣은 상태가 아니라는 뜻이죠.
실행되지 않았다는 것은 컴퓨터 입장에서는 메모리에 올라가 있지 않은 상태로 볼 수 있습니다.
메모리에 올라가 있지 않다는 것은 운영체제(Window, linux, MacOS 등..)가 독립적인 메모리 공간을 할당해주지 않았다는 뜻입니다.
반대로 생각해보면, 운영체제가 프로그램에게 독립적인 메모리 공간을 할당해주어야 실행됨을 의미하겠네요!

즉, 해당 아이콘을 더블클릭하여 실행하는 순간!
해당 프로그램은 프로세스(Process) 로 바뀌게 되는 것입니다.
(프로그램 실행시, 하나 또는 여러 개의 프로세스가 생길 수 있습니다)
또한 프로세스는, 프로그램의 명령어와 데이터가 메모리에 적재되어 있는 상태 라고도 말할 수 있습니다.

동시에 여러 프로세스를 실행시킬 수 있다?

그렇다면, 제 노트북에서 동시에 여러 프로세스를 실행시킬 수 있을까요?
한번 저의 현재 상황을 비유해서 한번 생각해보도록 하겠습니다.
아래 캡처는 현재 저의 노트북 현재 상황입니다.

저는 블로그를 작성하고 있는 동시에 카카오톡, vscode, Intellij 등, 여러 프로그램이 실행되고 있는 상태(= 프로세스)인 것을 확인할 수 있습니다.
그렇다면 제 노트북에는 여러 프로세스가 "동시에" 실행되고 있다고 할 수 있을까요?

결론부터 말씀드리면 모든 프로세스가 동시에 실행되고 있는 것은 아닙니다.
실제로는 여러 개의 프로세스가 동시에 실행되는 것처럼 보이는 것 이라고 정확히 표현할 수 있습니다.

그 이유는 하나의 프로세스는 하나의 프로세서(CPU)에서 실행할 수 있기 때문입니다.
(직관적으로 메모장이라는 프로그램을 동시에 100개 실행시키려면 CPU가 100개 필요하다는 것입니다)

그럼에도 우리는 동시에 여러 프로세스가 동작하고 있는 것처럼 느낄 수 있는 이유는
운영체제(OS)가 정말 빠른 속도로 CPU가 실행할 프로세스를 교체하면서 실행하고 있기 때문이죠.

즉 우리는 운영체제 덕분에 손쉽게 여러 작업을 동시에 실행할 수 있는 것입니다.

제 눈에서는 여러 개의 프로세스가 실행되는 것처럼 보이지만, 실제 노트북 입장에서는 그렇지 않다는 것입니다.
실제로 확인해보면, (순간적으로) 실행 중인 프로세스는 2개가 running 하고 있고, 466개의 프로세스는 sleeping 중이라고 하네요!

또 다른 중요한 포인트 중 하나는,
여러개의 프로세서가 있다면, 여러 프로세스를 동시에 수행 가능하다는 점입니다!
즉 여러 개의 프로세서가 협력적으로 일을 처리 하는 것을 멀티프로세싱(MultiProcessing) 이라고 합니다.
여기서 말하는 협력이라는 것은 하나의 Job을 여러 프로세서가 나눠서 처리하는 것을 말합니다.
즉 하나의 프로그램을 실행하는데 여러 개의 프로세서가 사용된다는 의미입니다.
예상 컨대, 많은 일을 처리해야 한다면, 멀티프로세싱으로 처리하는 것이 빠를 것 같네요.

그렇다면 프로세스가 어떻게 운영체제에 의해서 순식간에 실행되고 교체하는 걸까요?
이를 조금만 더 알아보고 넘어가도록 하죠.

프로세스의 상태

프로세스의 상태의 흐름은 다음과 같이 하나의 그래프 형태로 나타낼 수 있습니다.

프로세스의 상태는 Created, Ready, suspended ready, Running, Suspended blocked, asleep(blocked), terminated 로 총 7가지로 나타낼 수 있습니다.

시간이 조금 걸리더라도 각각의 상태에 대해서 알아두면 많이 도움이 되니 알아보고 넘어가도록 하죠!

created state

말 그대로 프로세스가 생성되는 단계를 의미합니다.
아이콘을 더블클릭하여 프로그램을 실행시키는 순간에 해당되겠네요!
(아직 실행된 것은 아닙니다)
가용 메모리 공간 유무에 따라 메모리가 할당된다면 Ready State 로, 할당받을 수 있는 메모리가 없다면 Suspended Ready State로 대기하게 됩니다.

Ready state

프로세서(CPU) 할당을 제외한 모든 자원(메모리 등)을 할당 받은 상태를 의미합니다.
(마찬가지로 아직 실행된 것은 아닙니다)
즉, 프로세서(CPU) 할당을 받게되면 즉시 실행이 가능한 상태입니다.
CPU 자원 할당을 받으면 Running state 로 가게되며, 이를 dispatch 된다고 합니다.

Running state

필요한 자원 모두(CPU, memory 등..)를 할당 받아 실행 중인 상태를 의미합니다.
중요한 것은 프로세서(CPU) 를 차지하고 있는 상태이며, 이 상태에서 실제 작업이 실행되게 됩니다.
앞서 말씀드렸다시피 항상 프로세스는 Running 상태에 있는 것이 아니라, 상태가 변화하게 되는데요,
이 때 Running State를 빠져나가게 되는 크게 두 가지 경우가 존재합니다.

  • Ready 상태로 바뀌는 경우
  • Asleep(blocked) 상태로 바뀌는 경우

Ready 상태로 바뀌는 경우는 프로세서의 스케줄링 우선순위 등으로 인해 프로세서(CPU) 할당을 반납하고 다시 프로세서(CPU) 할당을 대기하는 상태입니다.
이처럼 Ready 상태로 바뀌는 것을 Preemption 이라고 말합니다.

Asleep(blocked) 상태로 바뀌는 경우는 프로세서(CPU)외 다른 자원을 기다리는 상태를 말합니다. 가장 대표적인 예로 I/O 자원을 할당받기를 기다리고 있는 상태를 말하는데요.
쉽게 생각해보면 어떤 특정 프로그램을 실행시킬 때, 모든 일을 제 노트북에서 수행하지는 않습니다.
예를 들어 다른 외부에 있는 서버에서 열심히 수행된 결과를 전송받기도 합니다.
이러한 시간에는 제 노트북의 프로세서(CPU)나 메모리가 할 일은 없기 때문에 잠시 Asleep 해두는 것이라고 이해하시면 편합니다.
(I/O 자원은 Input/Output의 약자로, 데이터 전송 자원을 말합니다.)

Asleep(blocked) State

프로세서 외 다른 자원(ex) I/O 자원)을 기다리고 있는 상태를 말합니다.
중요한 것은 Asleep 상태에서 다른 자원을 기다리는 것을 완료하면 바로 Running 상태로 가는 것이 아니라, Ready 상태로 가게됩니다. 이를 Wake-up이라고 합니다.

Suspended State

메모리를 할당받지 못한, 혹은 빼앗긴 상태를 의미합니다.
(혹시나 기억하실 때 Suspended 라는 용어가 나오면 memory 할당을 못받았구나라고 기억하시면 좋습니다)

크게 Suspended Ready 상태와 Suspended blocked 상태로 나눌 수 있는데요,
Suspended Ready 는 다른 자원은 할당받았지만 프로세서와 메모리를 할당받지 못한 상태를 말하며 Suspended Blocked 는 프로세스, 메모리, 기타 자원 모두 할당받지 못한 상태를 말합니다.

이 때 앞서 메모리를 빼앗길 수 있다고 이야기를 했는데요,
빼앗기기 전에 지금까지 수행했던 결과를 어딘가에 저장해야 합니다.
즉 빼앗기기 전에 memory image 형태로 저장을 하게 되는데, 이를 swap device(일종의 하드 디스크)에 저장하게 됩니다.

메모리를 빼앗기는 것을 swap out, 복구하는 것을 swap in이라고 합니다.

Terminated / Zombie State

프로세스 수행이 끝난 상태를 의미합니다.
프로세스 수행에 필요한 모든 자원(CPU, 메모리 등)을 반납합니다.
이 때 커널 내에 PCB 라는 정보만 남아있습니다.
이 정보를 남기는 이유는 나중에 커널이 PCB 정보를 수집해 기억하기 위함입니다.

여기까지가 간단한 프로세스의 상태와 흐름이라고 보시면 되는데요!
실제로 동시성 관리를 이해하기 위해서는 이러한 개념정도는 이해하고 넘어가는 것이 좋다고 생각되어 간단하게 정리해 보았습니다.

추가적으로 아래 내용들은 프로세스에 대해서 조금 더 궁금하신 분들을 위해 정리를 할 예정인데요!
시간이 많이 없으신 분들은 여기까지만 읽고 스레드 부분으로 넘어가셔도 동시성 관리를 이해하는데 큰 무리가 없으실 듯 합니다 :)

프로세스의 구성

앞서 프로세스의 정의에서 프로세스란 메모리에 데이터가 적재되어 있는 상태라고 했습니다.
그렇다면 무슨 데이터를, 어디에 적재하고 있는 것일까요?

앞서 프로세스의 상태를 설명드리면서 PCB라는 용어가 나온 것을 보셨을텐데요.
프로세스에 대한 정보는 PCB(Process Control Block) 라는 자료구조에 저장되는데, 다음과 같은 정보들을 담고있습니다.

  1. PID(Process ID)

    운영체제에서 프로세스를 식별하기 위해 프로세스에게 부여하는 번호를 의미합니다.
    참고로 PPID 는 부모 프로세스의 PID를 의미합니다.

  2. 프로세스의 상태

    프로세스가 실행되고 있는지, 대기 상태인지 등의 프로세스 상태를 말합니다.
    New, Ready, Running, Waiting, Suspended Waiting, Suspended Ready, Terminated 등의 상태가 있습니다.

  3. 프로그램 카운터

    CPU가 다음으로 실행할 명령어를 가리키는 값입니다.
    CPU는 기계어를 한 단위씩 읽어서 처리하는데, 프로세스를 실행하기 위해 다음으로 실행할 기계어가 저장된 메모리 주소를 가리키는 값을 저장합니다.

  4. 스케줄링 우선순위

    어떤 프로세스를 먼저 실행할 것인가에 대한 스케줄링 우선순위를 저장하고 있습니다.
    여기서 스케줄링은 운영체제가 여러 개의 프로세스가 CPU에서 실행되는 순서를 결정하는 것을 말합니다.

  5. 권한

    프로세스가 접근할 수 있는 자원을 결정하는 정보를 저장합니다.
    아무나 해당 프로세스에 접근하지 못하도록 하기 위함입니다.

  6. 프로세스의 부모와 자식 프로세스

    최초로 생성되는 init 프로세스를 제외한 모든 프로세스는 부모 프로세스(PPID)를 복제하여 생성되며, 이러한 계층 관계는 트리를 형성하고 있습니다.
    즉 자기 자신 뿐만 아니라 부모, 자식 프로세스의 정보를 저장합니다.

  7. 프로세스의 데이터와 명령어가 있는 메모리 위치를 가리키는 포인터
    프로세스에 할당된 자원들을 가리키는 포인터

    프로세스는 결국 프로그램을 실행 한 상태이므로, 프로그램에 대한 정보를 저장하고 있어야합니다.
    프로그램에 대한 정보는 프로세스가 메모리에 가지는 자신만의 주소 공간에 저장되는데,
    이 공간에 대한 포인터 값을 저장하고 있습니다.

  8. 실행 문맥(Execution Context)

    프로세스가 실행상태에서 마지막으로 실행한 프로세서의 레지스터 내용을 저장하고 있습니다.
    CPU에 의해 실행되는 프로세스는 운영체제에 의해 계속 교체되는데, 교체되었다가 다시 자신의 차례가 되어서 실행될 때 중단된적 없고 마치 연속적으로 실행된 것처럼하기 위해 레지스터 정보를 저장하고 있습니다.

프로세스의 데이터와 명령어가 있는 메모리 구성

바로 위에서 프로세스의 구성과 어떤 데이터를 구체적으로 저장하고 있는지를 알아보았는데요!
이번에는 프로세스의 데이터를 저장하는 메모리는 어떻게 구성되어 있는지를 알아보도록 하겠습니다.

해당 주소 공간은 Text(Code), Data, BSS, Heap, Stack 영역으로 구성됩니다.

  1. Text(Code) 영역

    사용자가 작성한 프로그램 함수들의 코드가 CPU 에서 수행할 수 있는 기계어 명령 형태로 변환되어 저장되는 공간입니다.
    Compile 시점에 결정되며 중간에 코드를 바꿀 수 없도록 Read-only로 되어있습니다.

  2. Data 영역

    전역 변수(global variable) 또는 static 변수 등 프로그램이 사용하는 데이터를 저장하는 공간입니다.
    전역 변수 또는 static 값을 참조한 컴파일이 완료되면 Data 영역의 주소값 가리키도록 바뀝니다.
    전역변수가 바뀔 것을 고려해 Read-Write 로 되어있습니다.

  3. Stack 영역

    호출된 함수의 수행을 마치고 복귀할 주소 및 데이터(지역변수, 매개변수, 리턴값 등)을 임시로 저장하는 공간입니다. 이 영역은 함수의 호출시 기록하고 함수의 수행이 완료되면 사라집니다.
    컴파일시 Stack의 영역의 크기가 결정되기 때문에 무한정 할당할 수 없습니다.
    예를 들어 우리가 재귀(recursive) 함수를 무한히 반복하면 stack overflow가 발생하는 이유이기도 합니다.

  4. Heap 영역

    프로그래머가 필요할 때마다 사용하는 메모리 영역입니다.
    Heap 영역은 런타임 시점에 결정됩니다. java 같은 경우는 객체가 heap 영역에 생성되고 Garbage Collection 에 의해서 정리됩니다.

앞서 설명한 프로세스에 대해서 간단히(?) 설명을 해보았습니다.
이러한 프로세스 단위로 프로그램을 실행할 수 있는데요,
요즘의 프로그램은 매우 복잡해지면서 하나의 프로세스만 가지고 실행하기에는 벅찬 상황이 오게 되었습니다.

그래서 여러가지 고민을 통해 생각해 낸 것이 "한 프로그램을 처리하기 위해 여러 프로세스를 사용하자!" 였습니다.
하지만 이는 한계가 있었는데요.
그 이유는 운영체제는 운영의 안정성을 위하여 각 프로세스마다 자신에게 할당된
메모리 내의 정보에만 접근할 수 있도록 제약을 두고 있었기 때문입니다.

따라서 프로세스 내에 메모리를 공유하면서 여러가지 일을 할 수 있게 하면 어떨까?
라는 고민을 통하여 스레드(Thread) 라는 개념이 생겨나게 되었습니다.

스레드(Thread)란?

                       [원래 스레드(Thread)는 실이라는 의미]

다음은 스레드(Thread)의 위키피디아 정의입니다.

스레드(thread)는 어떠한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위를 말한다. 일반적으로 한 프로그램은 하나의 스레드를 가지고 있지만, 프로그램 환경에 따라 둘 이상의 스레드를 동시에 실행할 수 있다. 이러한 실행 방식을 멀티스레드(multithread)라고 한다.

스레드의 정의에서 중요한 첫 번째 포인트는,
프로세스 내에서 실행되는 흐름의 단위라고 말하고 있습니다.
실행되는 흐름의 단위라니, 직관적으로 와 닿지는 않습니다. 다른 정의를 좀 더 참고해 볼까요?

스레드(thread)란 프로세스(process) 내에서 실제로 작업을 수행하는 주체를 의미합니다.
스레드(thread)란 프로세스 내의 제어 영역에 해당하는 부분을 말합니다.
스레드(thread)는 프로세스의 코드에 정의된 절차에 따라 실행되는 특정한 수행 경로를 말합니다.

다른 정의를 좀 더 살펴보면, 스레드(thread)는 프로세스의 작업을 수행하는 주체이며,
프로세스의 제어부분을 담당하는 것
이라고 합니다.

즉, 프로세스는 자원을 할당받아 제어를 수행하면서 목적을 달성하게 되는데,
이 때 제어부분을 담당하는 것이 바로 스레드입니다.
(여기서 말하는 제어는 프로세스의 상태를 제어하는 등을 말합니다)

이러한 스레드는 우리가 프로그램을 코드로 작성할 때, 코드에 정의된 절차 하나하나의 수행 경로라고 이해할 수도 있습니다. 즉, 코드에서 작성된 함수 등이 스레드라고 볼 수 있는 것입니다.

또 위키피디아 정의에서 알 수 있는 또 다른 중요한 포인트는
프로그램 환경에 따라 하나의 프로세스 내에서 여러 개의 스레드가 동시에 작동할 수 있다는 점입니다.

예를 들어 우리는 게임을 만들 때 케릭터를 앞으로 걸어가면서 총을 쏘게 만들어야 한다면,
멀티 스레드를 활용해 동시에 작동되도록 할 수 있는 것입니다.

자, 스레드(Thread) 란 무엇인지 간단하게 알아보았는데요!
이제 정말 중요한 프로세스와 스레드의 정확한 차이가 무엇인지 더 깊게 알아보도록 하겠습니다.
특히 멀티프로세싱과 멀티스레딩의 차이는 꼭 알고 넘어가시는 것이 좋습니다.

두 개의 차이를 정확하게 알아야 앞으로 설명할 GIL, 동시성 관리에 대해서 잘 이해할 수 있으니
꼭 읽고 가시길 추천드립니다.

프로세스와 스레드의 차이

이번에는 프로세스와 스레드의 차이를 살펴보도록 하겠습니다.

앞서 프로세스의 데이터와 명령어가 있는 영역은 Text(Code), Data,Stack, Heap 이라고 말씀드렸는데요,
중요한 점은 각 프로세스는 별도의 공간에서 실행되고 프로세스끼리는 자원 공유를 하지 않는다는 점입니다.
이는 다시 말하면 각각의 프로세스는 독립적이다라고 말할 수 있으며, 프로세스 간의 자원 공유를 하려면 별도의 통신이 필요합니다.
(ex) IPC, inter-process communication, 메일 슬롯, 파이프, 소켓 등)

반면 스레드(Thread)는 Stack만 따로 할당받고, Code, Data, Heap 영역은 공유합니다.

이러한 차이는 프로세스와 스레드 각각의 장단점을 야기하는데요!

먼저 멀티프로세스로 처리시 프로세스 개별 자원이 모두 독립적이므로, 프로세스 하나에 문제가 발생해도 다른 프로세스에 영향이 확산되지 않아 안정적입니다.
하지만 멀티스레드보다 많은 메모리 공간과 CPU 시간을 차지한다는 단점을 가지고 있습니다.
또한 프로세스 간의 별도 통신을 해야한다는 점도 있습니다.

반대로 멀티스레드는 특정 자원을 공유하여 사용하기 때문에 시스템의 자원과 처리 비용이 멀티프로세싱에 비해 어느 정도 감소할 수 있습니다. 이는 통신의 부담이 적기 때문입니다.
하지만 자원을 공유하고 있기 때문에 동시에 스레드가 같은 자원을 접근시 발생할 수 있는 동기화 문제가 발생합니다.
또한 스레드가 개별로 유기적으로 움직이고 있기 때문에 테스트, 디버깅이 어려울 수 있습니다.
또 하나의 스레드의 오류로 전체 프로세스의 문제가 발생할 수 있기 때문에 각별히 신경써야 합니다.

결과적으로 멀티 프로세스 대신에 멀티 스레드를 사용하는 이유는 자원 사용의 효율성을 증대시키고, 처리 비용 감소, 응답 시간을 단축시키기 위함입니다.
하지만 스레드는 Data, Heap, Code 영역은 공유하기 때문에 이에 따른 동기화 문제는 잘 해결해야 합니다.

이번 시간에는 프로세스와 스레드에 대해서 알아보고,
프로세스와 스레드의 차이, 멀티프로세스, 멀티스레드에 대해서 알아보았습니다.

다음 시간에는 Python의 멀티스레딩과 멀티프로세싱과 Python의 특징 중 하나인 GIL에
대해서 좀 더 자세히 알아보도록 하겠습니다.

참조 블로그

이 글을 작성하기에 참조한 블로그는 다음과 같습니다.
Process state, 프로세스의 상태
[운영체제] 프로세스가 뭐지?
내 git 블로그

profile
재빅의 빅데이터 여행 기록 저장소

0개의 댓글