쓰레드와 Sleep
개요
CPU의 능력을 말할때 N코어 M쓰레드 라는 말을 언뜻 들은적이 있었다. 하지만 이게 정확히 무슨 뜻인지 잘 모르고 넘어갔다. 하지만 쓰레드를 나누다보니 CPU 사용률이 치솟는 현상이 있었고, 이 현상을 고치는 방법에 대해서 찾으면서 CPU와 Sleep 함수, 쓰레드에 대해서 간단하게 정리해보았다.
CPU 코어? 쓰레드?
CPU의 능력을 말할 때 사용하느 코어와 쓰레드는 무슨 용어일까? 나는 처음에 이것이 프로그래밍의 쓰레드와 관련이 없는줄알았다. 왜냐하면 내 컴퓨터는 4코어 4쓰레드였는데, 쓰레드 100개짜리 프로그램을 돌려도 CPU가 100%가 되지않고 잘 돌았었기 떄문이다. 하지만 이것은 그냥 나의 어림짐작 지식이었고, 진짜 내용은 다음과 같이 정리되었다.
일단 4코어 4쓰레드와 4코어 8쓰레드의 차이는 아래와 같다.
위 그림과 같이 기본적으로 코어 하나당 쓰레드 하나가 돌 수 있는데, 한 코어에 쓰레드를 나누는 기술(하이퍼쓰레드)을 사용하면 여러 쓰레드가 들어갈 수 있다. 이 부분은 하드웨어에 관심있는 사람이라면 대부분 알 수 있는 지식이다.
하지만 여기서 사용하는 쓰레드가 프로그래밍에서 어떻게 쓰이는가(돌아가는가)? 에 대해서 다시한번 정리해 보았다.
쓰레드?
프로세서와 쓰레드의 차이와 같이 흔히 배우는 내용은 알고 있었다. 하지만 쓰레드에 깊은 부분은 헷깔려서 다시 정리를 해야겠다.
1. 먼저 쓰레드는 프로세서의 하위 객체이고, 운영체제에서 적당히 시간을 분배해서 돌려준다.
2. 현재 돌고있는 모든 쓰레드에서 작업이 끝나면 논다(Idle 쓰레드를 돌린다). (Sleep에 닿았을때.)
3. 반대로 어떤 작업이 끝나지 않으면 놀지 않고, 그 작업이 끝날때 까지 100% 매진한다. (Sleep이 없는 로직)
4. 여기서 말하는 Sleep은 정말 Sleep을 포함해서 Select나 Poll과 같은 이벤트를 포함한다.
5. CPU에서 쓰레드 수는 한번에 동시에 돌릴수 있는 쓰레드의 수다.
6. 작업관리자의 CPU 점유율은 일정시간동안 Idle 쓰레드가 얼마나 돌아갔는지 측정하는 것이다.
Sleep
Sleep(Sleep_Until 등)은 해당 쓰레드에서 시간 조각을 포기하는 함수다. 여기서 사용하는 인자는 단순히 얼마만큼 포기하냐? 의 의미도 있지만 유니크하게 쓰는 방법도 있다.
Sleep(N) : N밀리초만큼 시간 조각을 포기한다.
Sleep(0) : 같은 우선순위의 쓰레드에게 시간 조각을 양도한다.
Sleep(1) : 다른 쓰레드에게 시간 조각을 양도한다.
Sleep 0과 1의 차이는 분명히 유의미하니까 기억하는게 좋다.
코드로 분석
4코어 8쓰레드 컴퓨터에서 테스트한 결과이다.
#include <iostream>
#include <thread>
#include <vector>
const int THREAD_COUNT = 8;
void TestThread() {
while (1) {
// do something
}
}
int main()
{
std::vector<std::thread> threads;
for (int i = 0; i < THREAD_COUNT; ++i) {
threads.push_back(std::thread(TestThread));
}
int a;
std::cin >> a;
}
8쓰레드 CPU라서 8쓰레드를 공회전 시키니 쉬는 시간없이 연산에 매진하는 모습이다.
딱히 빠른 연산이 필요하지 않아서 Sleep(1)을 먹였다고 가정하자.
#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
const int THREAD_COUNT = 8;
void TestThread() {
while (1) {
// do something
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
int main()
{
std::vector<std::thread> threads;
for (int i = 0; i < THREAD_COUNT; ++i) {
threads.push_back(std::thread(TestThread));
}
int a;
std::cin >> a;
}
그렇다면 같은 우선순위의 thread에게 양보하는 sleep(0)은 어떨까?
#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
const int THREAD_COUNT = 8;
void TestThread() {
while (1) {
// do something
std::this_thread::sleep_for(std::chrono::milliseconds(0)); // 0초로 바꿈.
}
}
int main()
{
std::vector<std::thread> threads;
for (int i = 0; i < THREAD_COUNT; ++i) {
threads.push_back(std::thread(TestThread));
}
int a;
std::cin >> a;
}
Idle 쓰레드는 우선순위가 제일 낮아서 [보통] 우선 순위의 공회전 쓰레드는 Idle쓰레드로 시간을 주지 않는 모습을 관찰 할 수 있었다.
결과
위에서 공부한 내용을 바탕으로 무한 공회전하고 있는 쓰레드들에 Sleep을 넣으니 CPU 점유율이 100%에서 30%로 다시 안정화 되었다! ^ㅁ^