본문 바로가기
MultiThread

CAS(Compare and Swap)와 SpinLock 구현

by W00gie 2021. 11. 2.

Compare And Swap(CAS)

멀티 스레딩에서 동기화를 수행하는 데 사용되는 원자적 명령.

메모리 위치의 내용을 주어진 값과 비교하고, 동일한 경우에만 해당 메모리 위치의 내용을 새로운 주어진 값으로 수정

 

SpinLock

멀티스레드 환경에서 임계 구역에 접근할 때

진입이 가능할때까지 루프를 돌면서 재시도하는 방식으로 구현된 락

 

SpinLock의 구현은 아래와 같다.

 

// GameServer.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include "pch.h"
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>


class SpinLock
{
public:
	void lock() //lockguard와 겹치지 않게 소문자로
	{
		//CAS (Compare-And-Swap) lock에 대한 첫번째 구현방법 (Spinlock)

		bool expected = false; //lock이 처음에 어떤값일지 기대값
		bool desired = true; //이후 lock을 어떻게 하고싶은지 희망값

		while (_locked.compare_exchange_strong(expected, desired) == false) {
			expected = false;  //expected값을 레퍼런스로 사용했기에 다시 바꿔줘야함
		}

		/* CAS를 위의 코드는 아래의 의사코드를 수행한다.
		while (_locked) {
			
		}

		_locked = true;
		*/
	}

	void unlock()
	{
		_locked.store(false);  //객체들에 대해서 원자적으로 쓰기를 지원해주는 함수
	}
private:
	atomic<bool> _locked = false;  
};

int32 sum = 0;
mutex m;
SpinLock spinLock;

void Add()
{
	for (int32 i = 0; i < 10'000; i++) {
		lock_guard<SpinLock>guard(spinLock);
		sum++;
	}
}

void Sub() {
	for (int32 i = 0; i < 10'000; i++) {
		lock_guard<SpinLock>guard(spinLock);
		sum--;
	}
}

int main()
{
	thread t1(Add);
	thread t2(Sub);

	t1.join();
	t2.join();
	
	cout << sum << endl;
}

실행결과

중요한 점은 atomic 라이브러리를 이용하여 while문내에서 compare_exchange_strong 함수를 통해 '원자적 비교'가 가능하다는 점이다. expected값이 실제 lock의 상태와 동일할 경우 true를 리턴하여 loop문을 빠져나오고 아닐 경우 false를 리턴하여 while문의 루프를 돌게 된다.

 

일반적인 bool값으로 lock을 확인할 경우 어셈블리 레벨에서 한 개의 명령으로 실행되지 않아(atomic X) 비동기화된 결과가 발생한다.

 

Spinlock의 경우 다른 동기화 방법보다 cpu 처리량의 소모가 크다는 점 주의할 것

 

 

 

2021.12.01+)

CAS에는 strong과 weak 두가지 버전이 존재한다.

성능상 큰 차이는 없으나 weak의 경우 다른 스레드의 interruption을 받아 실패하는 경우가 있을 수 있다.

 

atomic<bool> flag;
flag.compare_exchagne_weak(expected,desired);
flag.compare_exchagne_strong(expected,desired);