서버/멀티스레드

6. Sleep

광란의슈가슈가룬 2024. 7. 17. 17:33

스핀락은 어떠한 스레드가 락을 오래 잡고 있을 경우 다른 스레드들이 while문에서 오래동안 대기해야 한다.

자신이 이미 락을 획득하는데 실패했음에도 while문을 무한반복하며 할당받은 시간을 다 채우는 것이다.

이때 cpu의 낭비가 발생하는데 이러한 상황에서는 스레드가 반복적으로 계속 검사를 하여 타임슬라이스를 다 채우기보단 할당받은 타임슬라이스를 반환하고 대기를 하는 것이 효율적이다.

this_thread::sleep_for(chrono::milliseconds(100));
this_thread::sleep_for(100ms);

위의 함수가 하나의 예시이다. 인자로 시간을 건내주며 100 밀리세컨드 동안은 스케줄링 되지 않고 대기(sleep)를 한다는 것이다. 100ms라고 사용해도 무방하다.

this_thread::yield();
this_thread::sleep_for(0ms);

yield는 단어 뜻 그대로 양보한다는 것이다. 스레드가 할당받은 타임슬라이스를 반환하고 커널모드로 돌아가 다음 스케줄링을 기다린다. yield()는 sleep_for()에 인자로 0ms를 건내주는 것과 같다.

 

sleep_for()과 yield()는 할당받은 타임슬라이스를 반환하는 것은 같지만 sleep_for()은 인자로 건내준 시간만큼 스케줄링이 되지 않고(자신의 차례가 오지 않음) yield()는 언제든지 다시 스케줄링이 될 수 있다는 차이가 있다.

class SpinLock
{
public:
	void lock()
	{
		//CAS
		bool expected = false;
		bool desired = true;

		while (_locked.compare_exchange_strong(expected, true) == false)
		{
			expected = false;

			//this_thread::sleep_for(0ms);
			this_thread::yield();
		}
	}
	void unlock()
	{
		_locked = false;
	}

private:
	atomic<bool> _locked = false;
};

전 글의 스핀락 코드의 일부를 yield을 이용해 위와 같이 수정을 해보았다. 전의 코드는 할당받은 타임슬라이스 동안 while문을 계속 돌며 검사를 했다면 위의 코드는 락을 획득하지 못한다면 현재의 타임슬라이스를 반환하고 다음 스케줄링을 기다리게 된다. 엄밀히 말하면 스핀락은 아니다.

 

스핀락이 화장실 앞에서 사람이 나오기를 계속 기다리는 것이라면 위의 코드는 화장실 안에 사람이 있을 경우 잠시 자리로 돌아가서 대기하다가 다시 화장실에 가서 자리가 있는지 확인을 하는 것이다.

 

예시에서 알 수 있듯이 잠시 자리로 돌아가 대기중일 때 다른 사람이 화장실에 들어가버리면 자신의 차례는 미뤄진다.

위의 코드도 그러한 문제점이 있을 수 있다. 따라서 상황에 맞춰 어떠한 방법을 사용할지 적절히 선택해야 한다. 위의 코드는 1번 검사 후 바로 sleep을 하지만 몇 번 이상 검사를 한 후 락을 획득하지 못하면 sleep을 한다던지 그런식으로 환경에 맞춰 유동적인 규칙을 정할 수 있고 이는 개발자의 몫이다.

'서버 > 멀티스레드' 카테고리의 다른 글

8. Condition Variable  (0) 2024.07.20
7. Event  (0) 2024.07.20
5. SpinLock  (1) 2024.07.14
4. DeadLock  (0) 2024.07.11
3. Lock  (0) 2024.07.11