유저모드와 커널모드
스레드는 유저모드에 배치된 스레드와 커널모드에도 배치된 스레드로 구분된다
유저모드에 배치된 스레드가 사용되다가
특정 동작(ex. sleep, cin, cout,...)을 하게 되면
커널모드의 스레드한테 요청이 보내지면서
이를 시스템콜이라고 한다
스케줄러
운영체제의 스케줄러는
다양한 스레드를 어떻게 실행할지에 대해
선택하고 판단하는 기능을 한다
이처럼 스케줄러가 본인의 기준에 맞게
스레드에게 사용가능한 특정 시간을 제공해 준다
이를 타임슬라이스라고 부른다
타임슬라이스
스레드가 스케줄러로부터 제공받은 타임슬라이스 시간 동안
스레드가 실행이 된다
그리고 타임슬라이스 시간이 끝나면
다시 커널이 소유권을 가지게 되고
스케줄링 후 그에 맞는 특정 스레드에게 타임슬라이스가 제공된다
스레드와 타임슬라이스
스레드의 입장에서는
타임슬라이스를 100퍼 소진할 필요는 없다
스스로 필요한 만큼만 쓰고 반환도 가능하고
지금 당장 필요가 없다면, 타임슬라이스 포기도 가능하다
준비, 실행, 대기
스레드의 타임슬라이스가 소진된 경우는 실행 -> 준비
스레드의 sleep, 입출력 등이 발생하면, 실행 -> 대기
이처럼 스케줄러는 준비 -> 실행 -> (대기) 단계를 관리한다
sleep 사용
지난번에 작성한 SpinLock을 직접 구현한 코드에서
Sleep 기능을 넣으면 아래와 같이 코드를 작성할 수 있다
세 가지 방법이 있으며 사실 코드는 매우 쉽지만
이렇게 Sleep, 입출력과 같은 코드를 작성하면,
시스템콜이 발생하면서
커널영역에 요청이 보내지고 그걸 처리하면서 대기단계를 거치기 때문에
코드는 한 줄이지만 그 과정에서 수많은 컨텍스트 스위칭이 발생한다는 사실을 명심해야 한다
#include "pch.h"
#include <iostream>
#include "CorePch.h"
#include <thread>
#include <atomic>
#include <mutex>
class SpinLock
{
public:
void lock()
{
bool expected = false;
bool desired = true;
while (_locked.compare_exchange_strong(expected, desired) == false)
{
expected = false;
// 첫번째 방법
this_thread::sleep_for(std::chrono::milliseconds(100));
// 두번째 방법
this_thread::sleep_for(100ms);
// 세번째 방법
this_thread::yield();// this_thread::sleep_for(0ms); 시간을 0ms로 한 것이 yield이다
}
}
void unlock()
{
_locked.store(false);
}
private:
atomic<bool> _locked = false;
};
int sum = 0;
//mutex m;
SpinLock m;
void Add()
{
for (int32 i = 0; i < 100'000; i++)
{
//lock_guard<mutex> guard(m);
lock_guard<SpinLock> guard(m);
sum++;
}
}
void Sub()
{
for (int32 i = 0; i < 100'000; i++)
{
//lock_guard<mutex> guard(m);
lock_guard<SpinLock> guard(m);
sum--;
}
}
int main()
{
std::thread t1(Add);
std::thread t2(Sub);
t1.join();
t2.join();
cout << sum << endl;
}
'게임 서버' 카테고리의 다른 글
[게임서버 섹션2 Note] Condition Variable (0) | 2024.12.30 |
---|---|
[게임서버 섹션2 Note] Event (0) | 2024.12.30 |
[게임서버 섹션2 Note] SpinLock (0) | 2024.12.29 |
[게임서버 섹션2 Note] Lock 구현 이론 (0) | 2024.12.29 |
[게임서버 섹션2 Note] DeadLock (0) | 2024.12.29 |