[c++] 프로세스, 스레드, 임계 영역과 뮤텍스

TNT·2024년 7월 14일
0

c++ 기초

목록 보기
10/17

이론

흔히 생각하는 프로그램에서 실행을 할경우 프로세스가 동작 하게 된다.
이때 컴퓨터 내부에서 코드와 데이터들이 메모리에 힙과 스택에 존재하면서 동작을 하게 된다.

윈도우 관리자를 켜서 확인해보면 동작 중인 프로세스 부터 다양하게 볼수 있다.
이렇게 프로세스들이 실행중인데 같은 이름으로 된 애들은 프로세스가 여러개 할당이 된상태로 동작하는것이다.
이게 즉 멀티 프로세싱 이라고 한다.

스레드

각 프로세스에는 독립도니 메모리 공간이 있다.
서로 다른 프로세스는 상대방의 메모리 공간을 알수 없다.
일반적으로 스레드 하는 기능을 제공하는데 프로세스 같아 보이지만
스레드는 한 프로세스에 여러개 존재 하며
같은 프로세스 안에 있으면 메모리 공간을 공유하고
스레드 마다 스택을 가지고 각자의 함수의 로컬변수들이 존재 한다.

메인 스레드가 동작하는동안 다른스레드를 생성 삭제 할수있다.
이런 스레드는 동시에 여러가지 작업을 처리 할때 사용하는데 사용하면 할수록 좋다고 생각할수도 있지만
오래 걸리는 일 처리 함녀서 다른 일도 같이 처리 해야할때 사용한다.

예시로 게임 로딩 화면 창 보여주면서 스레드로는 게임 안에 데이터를 불러오고 하는것이다.
그동안 게임이 멈추는것 처럼 안보이게 화면을 보여주는 스레드 따로 데이터 불러오는 스레드 따로 두고 처리 하는것이다.

컨텍스트 스위치

보통 cpu가 컴퓨터에 1개만 존재 한다.
그러면 하나의 동작을 처리 하는데 컴퓨터는 여러가지 일 처리를 할때 여러프로세스와 각자 안에 스레드를 일정 시간 마다
교체를 한다. 즉 번갈아 가면서 실행을 하는것이다. 이 과정을 컨텍스트 스위치 라고 한다.
이렇게 스레드를 교체하는 과정에서 연산이 발생하는데 실행중인 스레드를 저장하고 다른 스레드를 골라서 교체를 하고 작업 스택을 복원해주고 해야 하기 때문에 연산이 많이 들어간다.

경쟁 상태

각자 다른 스레드가 2개가 값 하나인 변수에 접근해서 데이터를 사용하려고 하는겅우
특별한 조치가 없으면 값이 이상할수가 있다. 두 스레드가 데이터에 접근해서 그 데이터 상태를 예측할 수 없게 하는것을
경쟁 상태 또는 데이터 레이스 라고 한다. 가끔 스레드 인해서 연산 결과가 이상하게 나오는 경우이다.
만약

x = 2 인경우
x += 3 thread 1

x += 4 thread 2 

각각 동작 했을경우
원하는 결과는
x += 9 이지만 9가 나오기도 하지만 다른 값이 나올수도 있는데
위에 있는 컨텍스트 스위치는 기계어 단위로 컴파일 하게 되어서 컴파일 후 기계어로 보게 된다면

t1 = x
t1 = t1 + 3
x = t1

이 기계어가 스레드 2개가 실행 된다고 하고 가정을 해서 컨텍스트 스위치가 발생 했다고 보자면

x = 2

// 스레드 1
t1 = x
t1 = t1 + 3
x = t1

// 스레드 2
t2 = x
t2 = t2 + 4
x = t2

이러면 다행지만

x = 2

// 스레드 1
t1 = x
t1 = t1 + 3

// 스레드 2
t2 = x
t2 = t2 + 4
x = t2

// 스레드 1
x = t1

사이에서 껴버리면 위 코드는 x 는 5가 되어 버린다.
이런 경우는 그래서 스레드가 엑세스 할때는 변수를 누군가 건들이고 있다면 다른 스레드가 못건들게 막아야 할것이다.
그래야 원자성이 보장되고 일관성 있게 상태를 유지 할수 있다.

멀티 스레드 프로그래밍 하다보면 이렇게 원자성과 일관성을 유지 하는 조치를 할때가 있는데 이를 동기화 라고하며
대표 적인 기법은 임계 영역과 뮤텍스, 락(lock) 기법 이 존재 한다.

임계 영역과 뮤텍스

스레드에서 어떤 정보를 사용하고 있는동안 다른 스레드는 못건들게 한다 +
다른 스레드는 접근하는 정보를 사용하고있다면 대기 하고있는다.
이때 뮤텍스를 사용한다.

예시

	
	#include <mutex> // include 해줘야한다.
    
	int x, y;
	
	std::mutex mx;
	mx.lock(); 		// 잠금 
	read(x); 		// 읽거나
	write(y); 		// 쓰기
	// 사용후 
	mx.unlock(); 	// 잠금 풀기

예시를 들자면 이런 방향으로 사용해주면 된다.
위 코드도 간단 한거라면 상관 없지만 만약 read 하다가 예외 처리 되어서 그냥 나가 버린다면 - try chtch 를 감싸서 처리 할수있게 해야한다.
c++에서는 로컬 변수가 파괴 될때 파괴자 함수에서 원하는 코들르 실행할수있다.

mutex 잠금 상태를 로컬 변수로 저장을 하고 그 변수가 사라지게 된다면 자동으로 잠금이 햬제가 되는 클래스를 제공 한다.
lock_guard가 그 클래스이다.

	#include <mutex> // include 해줘야한다.

	int x, y;

	std::recursive_mutex mx;
	std::lock_guard<std::recursive_mutex> lock(mx); // lock 객체 사라질때 처리

	read(x); // 읽거나
	write(y); // 쓰기

이렇게 한다면 로컬 변수 lock 객체가 사리질 때 자동으로 mx.unlock()이 실행 된다.

이렇게 잘 사용한다면 문제 없지만 교착상태 또는 뮤텍스 자체가 좀 무겁게 돌아가는 코드라서
남발 하는경우 오히려 문제가 된다. 이경우에는 다음 게시글에 작성 하겠다.

참고 도서
게임서버 프로그래밍 교과서

profile
개발

0개의 댓글