락이 걸렸을때, 스레드 차원에서 대응방법은?
2. 다른일을 하다가 다시 시도한다. (일정시간 Sleep)
2번, Sleep에 대해 알아본다.
컨텍스트 스위칭과 퀀텀타임에 대한 설명.
왼쪽 그림은 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는 현재(호출한) 스레드와 관련된 유틸리티 네임스페이스. (<thread>
). 스레드 자신을 제어하거나 정보(아이디)를 얻을 때 사용한다.
std::this_thread::sleep_for(duration)
현재 스레드를 지정한 기간만큼 블락시킨다. 파라미터는 std::chrono::duration
.
스케쥴러 해상도에 따라 설정한 시간보다 약간 더 길어질 수 있음