import ( "코딩", "행복", "즐거움" )

Thread와 atomic 본문

C++

Thread와 atomic

더코드마니아 2022. 10. 4. 12:48

c++개발 안한지 10년 가까이 되어 가는 것 같다. 

현재는 현업에서 golang과 C#으로 개발 중이다. 

 

필요한 일이 생길 수도 있어

c++ 감을 찾기위해 간단 히 포스팅 해본다. 

 

Thread

_beginthreadex 를 쓰던 시절이 기억난다. 

c++ 11에 추가된 thread 녀석은 매우 간결하다. (설명이 필요없음)

 

간단한 함수 만들고.. 

void Test()
{
	for (int i = 0; i < 10000; i++)
	{
		std::cout << i << std::endl; 
	}
}

스레드로 실행 , join()은 thread 함수가 끝날 때 까지 대기한다. 

auto th = std::thread(Test);
th.join();

 

 

atomic 

네트워크 프로그래밍을 하려면 thread에 대한 이해가 필수적이다. 

thread가 여러개일 경우 공유 데이터에 접근해야 하는 경우 

"동기화"가 필수 적이다. 

 

이런 "동기화" 처리는 매우 위험한 상황을 초래할 수 있기 때문에 

현업에서는 효율성을 희생하고, 안정성을 택하여 

큐를 이용해 스레드간 동기화를 하는 경우가 많다. 

 

하지만 극한의 성능을 뽑아햐는 경우는 생기는 법 

이럴 경우에는 결국 멀티스레드 프로그래밍을 해야 한다. 

 

atomic은 동기화 기술중 하나로 

공유자원에 동시접근을 해야 하는 변수가 있을 때 원자적인 연산을 가능하게 해준다. 

 

예를 들어 다음의 코드를 보자 

	int a = 0; 
	a += 1;

간단 한 코드이지만, 디어셈블리에서 처리 순서를 보면

1) 데이터의 값을 레지스터에 복사 

2) 레지스터에서 값 증가 

3) 레지스터에서 연산한 결과값을 해당 주소로 복사 

 

위 처리가 멀티스레드에서 동작한다면 어떻게 될까 ? 

코드에서는 한줄이지만, 실제 동작은 3가지로 구분되기 때문에 

3가지 동작이 완료되기 전 아무 시점에나 다른 스레드의 개입이 발생 할 수 있다. 

 

이럴 경우 원자적으로 단 한번만 처리하게 해주려면 

atomic과 같은 클래스를 사용 하면 된다. 

 

atomic으로 변수선언과 증가 처리 예제 .. 

std::atomic<int> sum = 0; 
void Test()
{
	for (int i = 0; i < 10000; i++)
	{
		sum.fetch_add(1);
		std::cout << sum << std::endl; 
	}
}

디어셈블리로 확인해보면 fetch_add로 한줄로 처리 되는 걸 확인 할 수 있다. 

 

굳이 fetch_add() 함수를 사용하지 않고 ++ 연산을 하더라도 

동일하게 처리되는 것을 확인 할 수 있다. 

함수를 쓰게 되면 장점은 명시적으로 이 변수는 스레드 동기화 되고 있구나라고 

개발자가 인지할 수 있게 되어 좋다. 

 

'C++' 카테고리의 다른 글

CAS ( Compare and swap )  (0) 2022.10.07
RAII (Resource Acquisition Is Initialization)  (0) 2022.10.04