나만의 작은 도서관
[TIL][C++] 250603 MMO 서버 개발 31일차: Atomic객체의 exchange 패턴 본문
주의사항: 해당 글은 일기와 같은 기록용으로, 다듬지 않은 날것 그대로인 글입니다.
Atomic객체의 exchange 패턴
- Atomic 객체는 원자적 연산을 지원하는 함수들이 존재한다. 대표적으로 자주 사용하는 load나 store도 있겠지만, 값을 교체하는 exchange도 꽤 자주 사용된다. 오늘은 이 exchange에 대해 사용하는 패턴을 좀 더 자세하게 알아보았다.
그전에… 왜 락을 걸지 않고 Atomic을 사용하는가?
- 락은 필요할 때만 걸어두는 거지, 안 걸 수 있으면 최대한 걸지 않는 것이 좋다. 오버헤드 관점에선 Atomic도, Mutex를 이용한 락도 꽤 먹어주지만, 락은 코드 흐름 자체를 막아버리기 때문에 데드락 발생 가능성이 존재한다.
- 반면, Atomic은 락을 사용하지 않아 데드락 발생에 대해서 자유롭다는 장점이 있다. 따라서, 멀티 스레드 환경에서 동시성을 제어하되, 락을 거는 것이 부담된다면, 일부 타입에 한해 간결한 코드로 thread-safe 함을 보장하는 Atomic 객체를 사용하는 것이다.
exchange 가드 패턴
- exchange함수는 인자로 넣은 값으로 해당 변수의 값을 변경하며, 반환 값은 이전 값을 반환한다. 즉, 인자를 뭘 넣어주던 상관없이 반환값은 항상 이전 값을 반환한다.
Atomic<bool> atomic_var = false;
bool res1 = atomic_var.exchange(false); // res1 = false
bool res2 = atomic_var.exchange(true); // res2 = false
- 이를 이용한 exchange 패턴이 있다. 아래 예시처럼 exchange 가드 패턴을 이용하면 접근한 여러 쓰레드들 중 값을 변경하는 데 성공한 단 하나의 스레드만 다음 코드로 진입할 수 있다.
// Diconnect 코드 일부
// _connected를 true -> false로 변경한 쓰레드만 가드 통과
if (_connected.exchange(false) == false)
return;
// Distribute
// _distributing를 false -> true로 변경한 쓰레드만 가드 통과
if (_distributing.exchange(true) == true)
return;
- 위 두 예시에서 보았듯, exchange 가드 패턴은 인자값과 비교값이 같을 때 가드 내로 진입하여 return 되는 방식이다. 즉, 값을 바꾸지 못한 다른 쓰레드들은 통과 권한을 얻지 못해 가드를 통과하지 못하는 것이다.
또 다른 가드 패턴
- 그렇다면 인자와 비교값이 같은 경우만 사용하는가? 하면 그건 아니다. 아래와 같이 인자와 비교값이 반대인 가드 패턴도 존재한다.
bool sendRegistered = false;
// Send
if (_sendRegistered.exchange(true) == false)
sendRegistered = true;
if (sendRegistered)
RegisterSend();
- 위 코드는 오히려 가드 내부로 진입한 스레드만 이후 RegisterSend()를 실행할 수 있도록 하는 패턴이다. (역가드?라고 불러야 하나…)
- 이 패턴은 모든 스레드가 통과할 수 있지만, 통과한 다음의 코드를 실행시킬 수 있는 권한을 단 하나의 쓰레드에게 부여하는 방식이다(sendRegistered가 true일 때만 다음 if문에 진입)
- 이 방식을 이용하면, 모든 쓰레드가 가드에 의해 튕겨나가지지 않고 끝까지 실행하되, 선택받은 하나의 스레드만 다른 코드 흐름을 가질 수 있게 된다.