락이 걸렸을때, 스레드 차원에서 대응방법은?

  1. 무작정 기다린다. (스핀락)

2. 다른일을 하다가 다시 시도한다. (일정시간 Sleep)

  1. 락이 끝났을때 통보 받기. (이벤트를 사용하는 방법)

2번, Sleep에 대해 알아본다.

컨텍스트 스위칭과 퀀텀타임에 대한 설명.

image.png

왼쪽 그림은 Syscall로 커널모드가 전환되고 → 진행중이던 스레드가 블락 → 스케쥴러가 활성화되고 → 다음 수행될 스레드를 찾아 → 다음 수행될 스레드가 수행되는 과정을 설명한다.

10장 스핀락 예제에서, 효율을 위해 Sleep과 yield()를 추가한 모습

//

#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include <chrono>

using namespace std;

class SpinLock
{
public:
	void lock()
	{
		// CAS (Compare And Swap)
		bool expected = false; // 기대 값 (결과 상관없이 기존값으로 변경됨)
		bool desired = true; // 원하는 값

		// CAS 의사 코드 (locked가 false면 true로 바꾼다)
		while (_locked.compare_exchange_strong(expected, desired) == false)
		{
			expected = false;
			**this_thread::sleep_for(0ms);
			this_thread::yield();** 
			//this_thread::sleep_for(std::chrono::milliseconds(100));
		}

		/*
		위 compare_exchange_strong 의사 코드는 다음과 같다.
		expected는 결과와 상관없이 가지고 있던 값을 넣어준다.

		if (_locked == expected)
		{
			expected = _locked;
			_locekd = desired;
			return true;
		}
		else
		{
			expected = _locekd;
			return false;
		}

		*/
	}

	void unlock()
	{
		_locked.store(false); //InterlockedExchange로 구현
	}

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

int sum = 0;
SpinLock spinLock;

void add()
{
	for (int i = 0; i < 1'000'000; ++i)
	{
		lock_guard<SpinLock> guard(spinLock);
		++sum;
	}
}

void sub()
{
	for (int i = 0; i < 1'000'000; ++i)
	{
		lock_guard<SpinLock> guard(spinLock);
		--sum;
	}
}

int main()
{ 
	std::thread t1(add);
	std::thread t2(sub);
	t1.join();
	t2.join();
	cout << sum << '\\n';

	return 0;
}

std::this_thread

std::this_thread는 현재(호출한) 스레드와 관련된 유틸리티 네임스페이스. (<thread>). 스레드 자신을 제어하거나 정보(아이디)를 얻을 때 사용한다.

std::this_thread::sleep_for(duration)

현재 스레드를 지정한 기간만큼 블락시킨다. 파라미터는 std::chrono::duration.

스케쥴러 해상도에 따라 설정한 시간보다 약간 더 길어질 수 있음