티스토리 뷰

지금까지 1년 6개월정도 프로그래밍 하면서 

전부다 단일스레드 환경에서 개발해왔다.

 

주로 Spring 웹 개발만 해왔으니 딱히 스레드를 사용할 필요가 없었다.

왜냐하면 톰캣에서 알아서 멀티 스레드 환경을 만들어줬기 때문이다

 

 

그래서 딱히 신경써볼 필요도 없었고 편하게 개발을 잘 하고 있었다.

하지만 이제 게임서버를 공부하면서 멀티스레드 환경을 직접 만들어야 되는 상황이다.

 

나는 대충은 멀티스레드가 뭔지 알고는 있다.

예를들어 하나의 프로그램에서 여러가지 이벤트에따라 동시에 처리되야할 경우 

우리는 스레드를 만들어서 컴퓨터에게 일을 시킨다.

 

이론상 스레드를 만들어서 일만 시키면 끝인것같지만. 사실 그렇게 쉽지 않다.

 

멀티스레드 프로그래밍에서 주의해야할점은 공용데이터 (전역변수, Heap)들은 값을 변경할때 주의해야한다.

 

#include <iostream>
#include <algorithm>
#include <thread>
using namespace std;

int sum = 0;


void Add() {
	for (int i = 1; i < 100'0000; i++) {
		sum++;
	}
}


void Sub() {
	for (int i = 1; i < 100'0000; i++) {
		sum--;
	}
}

int main() {
	
	std::thread t1(Add);
	std::thread t2(Sub);
	t1.join();
	t2.join();

	cout << sum << endl;
	return 0;
}

 

우선 스레드 2개를 만들어서 테스트를 해보겠다.

sum 에는 0이 출력되어야 할 것 같지만 결과는 그렇지가 않다.

 

-89875 전혀 이상한 값이 출력된다.

왜그럴까?

Add에서 sum++; 코드와 Sub sum--; 구문은 한줄로 되어있지만 사실 그렇지가 않다.

디컴파일러 해보면 기계어로 3줄이상의 구문으로 쓰여있기 때문이다.

그렇기 때문에 멀티스레드 환경에서는 각각의 스레드가 함수를 실행할때 sum + 1 하기위해서 참조하는 sum 값이 매순간 마다 바뀌기 때문에 0이라는 숫자가 나온다는 보장이 없다.

 

 

int sum = 0;


void Add() {
	for (int i = 1; i < 100'0000; i++) {
		// 1 t1 스레드에서 sum을 참조한다 
		// 2 t1에 sum은 t2가 덮어써버린 값이다
		// 3 sum = sum + 1 실행한다
		sum++;
	}
}


void Sub() {
	for (int i = 1; i < 100'0000; i++) {
		// 2. t2 스레드에서 sum을 빼버렸다
		sum--;
	}
}

 

 

이러한 현상을 해결하기 위해서는 

1. lock을 건다

2. atomic 클래스를 사용한다.

 

1. 경우

 - 예를들어 t1 스레드가 sum이라는 공용변수에 접근한순간 lock을 잠근다.

   그렇게 되면 t1 스레드가 sum이라는 변수를 점유하는 동안 t2스레드는 대기를 하게 된다. 

   그렇게 되면 시간은 조금 소요되더라도 동기적으로 실행되기때문에 안정적인 결과 값은 보장한다.

 

#include <iostream>
#include <algorithm>
#include <thread>
#include <mutex>
using namespace std;

int sum = 0;
mutex m;

void Add() {
	for (int i = 1; i < 100'0000; i++) {
		{
			std::lock_guard<std::mutex> lockGuard(m);
			sum++;
		}
	}
}


void Sub() {
	for (int i = 1; i < 100'0000; i++) {
		{
			std::lock_guard<std::mutex> lockGuard(m);
			sum--;
		}
	}
}

int main() {
	
	std::thread t1(Add);
	std::thread t2(Sub);
	t1.join();
	t2.join();

	cout << sum << endl;
	return 0;
}

 

 

2.경우

- C++ 에서 제공하는 atomic 클래스를 사용하여 처리한다.

- lock 과 비슷한 개념이지만. 추상적으로 생각하면 3번 연속으로 이루어지는 일을 원자성있게 하나의 처리로 묶는(?) 기능이다

 

#include <iostream>
#include <algorithm>
#include <thread>
#include <atomic>
using namespace std;
atomic<int> sum = 0;

void Add() {
	for (int i = 1; i < 100'0000; i++) {
		sum.fetch_add(1);
	}
}


void Sub() {
	for (int i = 1; i < 100'0000; i++) {
		sum.fetch_sub(1);
	}
}

int main() {
	
	std::thread t1(Add);
	std::thread t2(Sub);
	t1.join();
	t2.join();

	cout << sum << endl;
	return 0;
}

 

 

Comments
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday