C++/C++

Spin Lock 구현해보기

Elan 2021. 10. 3. 18:59

일반 Lock과 Spin Lock의 차이

 


1) 일반적인 락

- 락을 획득할 수 없을 경우 Context Swithcing을 발생시켜 다른 작업을 할수 있도록 한다.
  때문에 쓰레드간 경합(Contention)이 발생하지 않는다.

2) 스핀 락

- 락을 획득 할 때까지 Loop를 돌며 락 획득 시도를 반복하는 방식이다.
(time sliced가 만료된 경우에는 context switching이 발생하므로 락 시도가 잠정 중단된다. )

때문에 락 획득을 시도하고 있는 쓰레드들 끼리 경합이 발생한다는 단점이 있다.

그러므로 이 단점을 최소화 하기 위해서는
스핀 락은 아주 짧은 시간안에 끝날 수 있는 작업에만 사용하는 것이 좋다.




 

Spin Lock 구현해보기



atomic 변수를 이용하여 Spin Lock을 구현해보자.

※ 주의 사항

-  Spin Lock이 담당하는 Critical Section(임계 영역)에 진입한 쓰레드는
   해당 Critical Section을 벗어날 때까지 Interrupt되지 않아야 한다.

   즉, 임계 영역 수행 중인 쓰레드는 Context Switching이 되지 않도록 구현해야 한다.

   하지만 유저모드에서 CPU Scheduling을 Disabling하는 동작은
   대부분의 OS의 보안 정책을 위배하므로 불가능하다.

   그러므로 실사용 시에는 OS에서 제공하는 Spin Lock API를 사용하도록 하자.
  




 

Lock-free를 이용하여 Spin Lock 구현

class SpinLock {
public:
	void lock() {
		while (true)
		{
			for (int i = 0; i < 5000; i++)
			{
				bool expected = false;
				/* compare_exchange_weak 동작
				 : locked == expected 일 경우 expected = locked로 변경되고 true반환,
				   locked != expected 일 경우 locked = true로 변경하고 false 반환*/
				if (locked.compare_exchange_weak(expected, true, std::memory_order_acquire))
					return;
			}
			std::this_thread::yield(); // 5000번 시도했으면 Context Swithcing시키기
		}
	}
	void unlock() {
		locked.store(false, std::memory_order_release);
	}
private:
	std::atomic<bool> locked = false;
};

SpinLock spinLock;
void Add(int& num) {
	for (size_t i = 0; i < 10000; i++) {
		std::lock_guard<SpinLock> guard(spinLock);
		++num;
	}
}

void Sub(int& num) {
	for (size_t i = 0; i < 10000; i++) {
		std::lock_guard<SpinLock> guard(spinLock);
		--num;
	}
}

int main()
{
	int sum = 0;
	auto t1 = std::thread(Add, std::ref(sum));
	auto t2 = std::thread(Sub, std::ref(sum));

	t1.join();
	t2.join();

	std::cout << sum;
}