프로세스, 스레드, 레이스 컨디션, IPC

현지·2025년 9월 24일

프로세스와 스레드

프로세스가 가족이라면 스레드는 그 가족 구성원 한명한명 이라고 볼 수 있다. 프로세스는 실행 중인 프로그램 인스턴스 그 자체를 가리키는데 스레드는 그 인스턴스에서 실행되고 있는 여러 작업 흐름들을 말한다.

멀티 프로세스? 멀티 스레드?

학습을 하며 든 의문이 멀티 프로세스 개념이 있는데 왜 멀티 스레드라는 개념을 또 만든거지? 라는 생각이었다. 여러 작업을 동시에 처리할 수 있도록 해 주는것은 프로세스를 여러 개 사용하는 멀티 프로세스로도 가능하기 때문이다. 하지만 안 쓰는데는 이유가 있었다.

프로세스는 데이터 공유를 하지 않는다. 즉, 하나의 프로세스가 실행될때, 메모리에 고유의 영역을 할당받고 그 안의 데이터를 다른 프로세스와 공유하지 않는다. 이렇게 되면 당연히 컨텍스트 스위칭(쉽게말해 CPU에서 실행중인 프로세스를 변경하는것)을 할 때 데이터들을 전부 넘겨주어야 하니 작업이 무거워질 수밖에 없다.

그러나 스레드는 같은 프로세스의 스레드라면 전부 같은 메모리를 공유한다(Stack영역만 빼고). 그래서 프로세스와는 다르게 컨텍스트 스위칭이 훨씬 가볍다!

경쟁 조건(race condition)

다만 멀티 스레드도 당연히 단점이 있다. 스레드는 하나의 프로세스에게 할당된 메모리중에서 스택을 뺀 나머지 영역의 데이터를 공유하는데(힙, 텍스트, 데이터) 이 때문에 경쟁 조건(race condition)이 발생할 수도 있다.

경쟁 조건(race condition)이란 여러 스레드나 프로세스가 동시에 같은 자원에 접근하면서 실행 순서에 따라 결과가 달라지는 오류가 발생할 수 있는 상황을 말하는데, 다시 말해 자원을 공유하는 스레드가 병렬적으로 여러 개 존재하다 보니 스레드1과 스레드2가 공유 자원을 거의 동시에 접근해서 변경하면 값이 덮어써지거나, 중복처리 되는 등 해당 변수는 결과가 이상해질 수 있다.

그래서 멀티 프로세스와 멀티 스레드는 공유 자원 동기화가 필요하다! --> 경쟁 조건은 공유자원의 문제가 아니라 실행작업을 동시에 하는게 문제인 것임

멀티 프로세스/스레드 어떻게 동기화 시켜야 하나?

경쟁 조건이 발생하는 이유는 프로세스나 스레드가 동일한 작업을 동일한 시점에 하기 때문에 발생하는 것이다. 그렇다면 해당 작업에 하나의 프로세스/스레드만 진입이 가능하게 하면 경쟁조건이 발생할 가능성이 없어지지 않을까? 해당 함수나 모듈 내에서 사용중인 프로세스/스레드의 유무값을 저장하고, 이후 요청이 들어오면 그 유무값에 의해 거절하든 사용하라고 하든 하면 될 것이다!

이러한 개념을 critical section(임계 영역)이라고 한다. 공유 데이터의 일관성을 보장하기 위해 하나의 프로세스/스레드만 진입해서 실행 가능한 영역이라는 개념이다. 그리고 이러한 임계 영역에 진입하기 위한 요건을 확인하는 영역을 entry section이라고 한다.

그렇다면 공유 불가능한 자원의 동시 사용을 방지해주는 상호 배제 알고리즘에는 어떤 것들이 있을까.

스핀락 - lock값을 이용해 관리하는 방법이다. 실행중인 프로세스나 스레드가 있다면, lock 값이 실행 중 상태에서 벗어날 때까지 반복해서 시도하고, 실행중인 프로세스나 스레드가 완료되면, 비로소 lock값을 얻어 임계 영역에 진입이 가능해진다.
다만 이 방식은 기다리는 동안 계속해서 lock을 확인하기 때문에 CPU를 낭비한다는 단점이 있다.

뮤텍스 - 스핀락과는 다르게 계속해서 문을 두드리는게 아니라 예약을 걸어놓는다고 생각하면 이해가 쉽다. 만약 사용중이라면 해당 스레드를 큐에 넣어 예약을 걸어놓고, 기존 작업이 끝나면 큐에서 값을 꺼내 실행을 시킨다.

세마포어 - 뮤텍스와 어느 정도 유사하나 임계 영역에 여러 프로세스나 스레드가 들어갈 수 있음.

IPC?

멀티 스레드는 자원을 같이 쓰기 때문에 메모리가 자동적으로 공유된다. 그러나 멀티 프로세스는 메모리가 분리되어 있기 때문에 따로 데이터를 주고받는 통신이 필요하다. 그게 바로 IPC이다.

여러 프로세스가 공동 작업하거나, 하나의 프로세스가 다른 프로세스에게 신호나 명령을 전달할 때나, 분산 시스템, 병렬 처리 등에서 프로세스 간 협력이 필요할 때 IPC가 사용된다. IPC는 OS에서 제공하는 통신 메커니즘을 사용한다.

이러한 IPC는 락, 뮤텍스, 세마포어와 결합되어 상호 배제를 위해서도 사용될 수 있다.

비동기 이벤트 매니저

비동기 이벤트 매니저(Asynchronous Event Manager)는 이벤트 기반 프로그래밍에서 이벤트를 등록하고, 발생시키고, 처리하는 과정을 비동기 방식으로 관리하는 시스템 또는 객체를 말한다.

Publisher-Subscriber 패턴

🎙️ Publisher
: 어떤 이벤트(메시지)가 발생했다고 알리는 쪽

👂 Subscriber
: 어떤 이벤트가 발생하면 반응(처리)하는 쪽

이벤트를 발행한 쪽과 구독한 쪽이 서로를 몰라도 작동하는 구조

비동기 대기큐

지금 당장 실행하지 않고, 나중에 실행해야 할 작업(콜백, 이벤트 처리 등)을 임시로 저장해두는 대기열이다.

Event Emitter

Node.js에서 이벤트 기반 프로그래밍을 할때 사용되는 클래스이다. Node.js에서 가장 기본적인 이벤트 처리 방식 중 하나이다. EventEmitter 클래스를 상속한 객체를 만들고, on() 메서드를 사용해 이벤트 리스너를 등록하여 이벤트가 발생할 때마다 등록된 콜백 함수가 실행된다.

이벤트는 문자열 형태의 이름과 함께 발생하며, 이벤트에 대한 데이터를 선택적으로 전달할 수도 있다.

const { EventEmitter } = require("events");
const emitter = new EventEmitter();

emitter.on("hello", (msg) => console.log("받음:", msg));
emitter.emit("hello", "안녕");

emitter인스턴스는 이벤트 이름에 해당하는 콜백 리스트를 Map 형태로 내부에 저장한다.

.on메서드를 통해 이벤트를 등록하고(이건 subscriber 등록을 의미한다), .emit메서드를 통해 이벤트를 발생시킨다(Publisher가 이벤트 발생시킴).

Promise

Promise는 비동기 작업을 수행하는 함수가 반환하는 객체로, 성공하면 결과 값을 반환하고 실패하면 에러를 반환한다. 이러한 특성으로 Promise는 비동기 작업의 성공 또는 실패 상태를 쉽게 확인하고 처리할 수 있다.

profile
헤맨만큼 내 땅이다

0개의 댓글