DOKKAEBI HILL

방문을 환영합니다!

모든 포스팅 >

동시성과 실행 유닛 [0] 2025-01-02
프로세스 [0] 2025-01-01

동시성과 실행 유닛

open_in_new

'초기의 컴퓨터는 하나의 일꾼(Processor)이 한번에 하나의 일(Job)을 할 수 있었을 것입니다. 그러면 지금 우리가 노래도 듣고 게임도 하고 인터넷 검색도 동시에 하는 것은 어떻게 가능하게 되었을까요? 이를 차차 알아보도록 하겠습니다.'

이전 포스팅의 맺음말에서 질문에 답하기 위해서 오늘의 주제를 이야기해보겠습니다. 


동시성과 병렬성

동시성과 함께 몇 가지 용어와 개념을 정리해보겠습니다.

동시성은 한 번에 여러 일을 처리하는 것이다. 병렬성은 한 번에 여러 일을 하는 것이다.
... 동시성은 (병렬화가 꼭 필요하지는 않지만) 병렬화할 수 있는 문제를 해결하는 데 필요한 구조를 제공한다.
- 롭 파이크 (Go 언어 공동 창시자)

동시성(Concurrency)과 병렬성(Parallelism) 용어에 대해선 의견이 다양하다고 합니다.

병렬성 | 물리적으로 여러 개가 병렬로 동시에 실행하는 것

동시성 | time sharing을 통해서 논리적으로 동시에 실행하는 것처럼 보이게 하는 것

누군가에게 동시성과 병렬성을 설명해보라고 한다면 저는 저렇게 말할 것 같습니다. 저를 포함해서 대부분 이렇게 이해하고 있을 것 같고요.

그리고 이런 설명이 롭 파이크의 해석과도 크게 달라보이지는 않습니다. 다만 롭 파이크의 견해대로라면 동시성은 병렬성을 포함하는 상위 집합이 되겠습니다.


일반적인 운용 환경의 랩톱에서 4코어인 CPU는 200개 이상의 프로세스를 일상적으로 실행하는데, 이를 '병렬'로 처리하려면 200개의 코어가 필요합니다.
CPU는 한번에 4가지가 넘는 작업을 할 수 없지만, OS의 프로세스 관리하에 수백 개가 실행할 수 있는 동시성을 제공합니다. 하지만 이 경우에 (200개 만큼의)병렬은 아닌 것이죠.


멀티 프로그래밍과 타임 쉐어링

멀티 프로그래밍과 멀티 프로세싱은 같은 의미인 것 같지만 다릅니다. (사실 요즘에서는 멀티 프로그래밍이란 말 자체가 어색하긴 합니다만)
공룡책에서는 멀티 프로그래밍이란
여러 프로그램을 메모리에 올릴 수 있는 구조라고 설명합니다.


아주 초기의 컴퓨터를 상상해봅시다. (사실 저도 경험해보지 못해서 잘 상상이 안됩니다만 가능한 구닥다리 컴퓨터를 상상해보면 되겠죠?)

이 PC는 한번에 단 하나의 프로세스만 실행됩니다. 노래를 들으면서 인터넷으로 검색하며 워드 작업을 하는 일은 상상조차 할 수 없습니다. 
만약 어떤 파일을 다운로드 받고 있다면 다운로드가 끝나는 동안 컴퓨터는 먹통이 됩니다. (프로세스는 I/O waiting 상태가 되며 다운로드 완료 signal이 올 때까지 대기 상태에 있을 것입니다.)


멀티 프로그래밍은 여러 프로세스가 실행되게 함으로서 CPU 효율의 극대화하기 위한 목적이었습니다. 다운로드 같은 장시간 I/O 작업이 생기는 동안에 CPU는 다른 프로세스를 실행함으로써 항상 CPU가 일하게 합니다.
메인 메모리에 모든 프로세스를 올리기는 버겁기 때문에, 디스크에 job pool을 만들고 메인 메모리에 할당되기를 기다립니다. 그리고 메인 메모리에 올라간 프로세스들이 스케줄링에 따라서 실행되는 것이죠.

main-memory 안에서 ready 상태의 프로세스를 execute하는 스케줄링을 short-term scheduling,
job pool 에서 main memory 로 프로세스를 할당하는 스케줄링을 long-term scheduling으로 구분짓기도 하네요. 이때 CPU-bound process와 I/O-bound process를 적절히 섞어주는 것이(good process mix) 중요하다고 합니다. (os가 할 일 일테지만?)

타임 쉐어링프로세스마다 부여된 CPU 타임만큼 실행되고 context switch 합니다. 타임 쉐어링이 없다면 메인 메모리에 여러 프로세스가 있다 할지라도 interrupt이나 system call이 있기 전에는 다른 프로세스가 CPU를 점유할 수 없지만, 타임 쉐어링은 시간만 기다리면 (공평하게) 차례가 돌아옵니다. 

preemptive & non-preemptive scheduling 
preemptive : OS가 우선순위(FCFS, SRTF ...) first 나 CPU 할당 타임(Round Robin)에 따라서 프로세스에 할당된 CPU를 뺏고 다른 프로세스에게 할당할 수 있습니다. 
non-preemptive (cooperative) process | OS가 프로세스에 할당된 CPU를 통제하는 것이 아니라 프로세스가 자발적으로 포기합니다. 프로세스가 대기 또는 종료 상태가 되어야 CPU를 반납합니다. 

멀티 프로그래밍과 타임 쉐어링은 동시성을 실현하는 구조적 토대가 됩니다. 

우리가 말하는 멀티태스킹은 곧 동시성을 의미하며, 멀티 프로세싱, 멀티 스레딩 그리고 비동기 처리는 동시성을 가질 수 있는 방법이나 개념입니다.


실행 유닛

코드를 동시에 실행하는 객체로 프로세스, 스레드, 코루틴이 있습니다. 각각은 독립적인 상태와 콜 스택을 갖습니다.

프로세스 |
실행 중인 프로그램의 인스턴스로 메모리와 CPU 타임 슬라이스를 사용하여 동작합니다.
프로세스는 각자의 메모리 공간을 갖고 격리됩니다. 프로세스간에는 shared memory 접근이나 message passing(소켓, 파이프, RPC, 메세지 큐) 등의 방식으로 통신합니다. (IPC, Inter Process Communication)

스레드 |
프로세스 안에 있는 실행 유닛으로 CPU 사용의 기본 유닛입니다. 프로세스가 시작되는 것은 메인 스레드를 시작하는 것으로 시작되며 시스템 콜을 통해서 여러 스레드를 생성할 수 있습니다.
각 스레드는 ID, PC, register값, 스택 같은 실행 상태를 가지며(TCB, Thread Control Block) 프로세스로부터 code, data, heap section 그리고 open files 등 OS 자원을 공유합니다. 고유의 스택 그리고 공유된 자원들을 활용하여 통신할 수 있습니다.

코루틴 |
OS의 관리(context swiching, scheduling 같은)를 받는 process, thread와는 달리 코루틴은
코드 레벨에서 관리되는 object입니다. 각 프로그래밍 언어에서 generator의 발전은 별도의 실행 흐름을 가질 수 있는 코루틴을 탄생시켰습니다.

코드를 실행하는 스레드는 수백 수천개의 코루틴을 가질 수 있습니다. OS 컨텍스트 스위치가 없기 때문에 여기에서 발생하는 overhead를 최소화하며 코드 레벨의 instance들을 공유합니다. (일반적으로 하나의 스레드에서 동작하는데, multi threaded program에서는 반드시 한 스레드에 종속되는 것은 아니라고 하네요.)
코루틴은 cooperative(non-preemptive) multitasking을 지원합니다. OS의 스케줄링이 없는 대신 프로그래머가 suspend, await, yield 등을 통해서 적절히 스케줄링해야 합니다.


코루틴의 이러한 특징과 이벤트 루프 같은 프레임 워크가 결합되면서 비동기 처리를 쉽게 구현할 수 있게 됩니다. (코루틴과 비동기 처리가 같이 설명되는 경우가 많습니다. 심지어 python에서는 async def로 코루틴을 선언합니다. 이 때문에 코루틴 is equal to 비동기라고 헷갈리기 쉽지만 비동기는 비동기이고 코루틴은 코루틴입니다!)

thread를 lightweight process 라고 하기도 하고,
coroutine을 lightweight thread 라고 하기도 합니다.

스레드는 프로세스에 비해
1. Responsiveness
웹 서버를 떠올리면 알 수 있듯이 멀티 스레딩 시스템에서 한 스레드가 blocked 되거나 오랜 연산 작업 중이더라도 다른 스레드가 계속 서비스를 할 수 있습니다.
2. Resource Sharing
IPC는 비싼 연산이고 까다롭습니다. 이에 비해 스레드는 기본적으로 프로세스의 공유 메모리나 자원에 쉽게 접근할 수 있습니다. 프로세스의 경우도 마찬가지이지만 공유 영역(critical section)을 접근하는 경우 synchronization issue는 주의해야겠지만요.
3. Economy
프로세스는 스레드에 비해 무겁습니다. 각자 고유한 메모리를 공간을 차지하며, context switch될 때마다 PCB 저장 및 복원, CPU 캐시 메모리(메인 메모리와 CPU 사이에서 데이터 캐싱 역할)를 초기화 해야합니다.
스레드도 역시 context switch를 할 때 TCB 저장 및 복원 과정이 있으나, 프로세스에서 공유되는 정보를 제외한 스레드 고유의 register, stack 정보만 저장하기 때문에 빨리 읽고 쓸 수 있습니다. 그리고 스레드 context switch가 일어나더라도 CPU 캐시 메모리는 초기화되지 않습니다. (단, 다른 코어에서 실행되는 경우에 해당 캐시 메모리에 스레드 컨텍스트 정보가 로드되어야 하기 때문에 초기화될 수 있다고 합니다.)

참고: 공룡책, 전문가를 위한 파이썬(2판)



게시글을 삭제하시겠습니까?

'delete'를 입력하고 '삭제' 버튼을 눌러주세요.

All rights reserved.

GUEST님 환영합니다! ^_^