sleep()과 Process/Thread

bolee·2022년 12월 1일
0

42seoul

목록 보기
24/27

Process와 Thread

CPU는 OS에 따라 Process 단위 또는 Thread 단위로 프로그램을 처리한다. (Linus, Unix의 경우 Process 단위, Window의 경우 Thread 단위)

Process/Thread는 상태(state)를 가지고 있는데, OS가 Process/Thread를 효율적으로 관리하기 위해 존재한다.

상태는 위 사진에 나타나지 않은 생성 상태와 완료를 포함하여 종 6가지 상태가 존재한다.

  • 생성 상태(Created): 프로세스가 메모리에 올라와 실행 준비를 완료한 상태
  • 준비 상태(Ready): 생성된 프로세스가 CPU를 선점하기 전까지 기다리는 상태
  • 실행 상태(Running): 준비 상태에 있는 프로세스 중 하나가 CPU를 선점하여 실제 작업을 수행하는 상태
  • 대기 상태(Asleep): 실행 상태에 있는 프로세스의 작업이 완료 되기 전까지(예를 들어, Blocking I/O) 대기하는 상태
  • 완료 상태(Terminated): 프로세스가 작업을 완료하여 종료되는 상태(메모리에서 삭제, PCB(Process Control Block, 프로세스 제어 블록) 제거)
  • 보류 상태(Suspend): 프로세스가 중단된 상태(자의적으로 중단을 하거나 외부적인 요인으로 인해 중단된 경우)

생성 상태부터 완료 상태까지를 활성 상태라고 하며, 일반적으로 아래와 같은 순서로 상태가 바뀌며 실행된다.

생성 상태 → 준비 상태 → 실행 상태 → 대기 상태 → 준비 상태 → 실행 상태 → 완료 상태

sleep()

Process/Thread를 잠시 멈추기 위해 종종 sleep() 함수를 사용하게 된다.

in C
#include <unistd.h>

unsigned int sleep(unsigned int seconds);
// in C++
#include <windows.h>

void Sleep(DWORD dwMilliseconds);

sleep() 함수는 초(second) 또는 밀리초(millisecond) 만큼 Process/Thread를 멈추는데, 해당 함수를 호출하면 OS는 해당 Process/Thread를 '보류 상태'로 전환하고 Process/Thread를 관리하는 목록에서 잠깐 제외시킨다.
즉, CPU를 선점하기 위한 목록에서 빠져나오게 된다.

이러한 상황은 중대한 사항을 발생시키는데, 만약 Process/Thread가 sleep(1)을 호출하게 된다면 해당 Process/Thread가 1 초동안 쉬는 것이 아닌 1 초동안 Thread 스케쥴러에서 제외되는 것이다.
이후 해당 Process/Thread는 '준비 상태'로 자동으로 전환되면서 관리 목록에 다시 들어가게 되고, CPU 점유를 위해 추가적으로 대기해야하는 상황이 발생한다.
즉, 실제 원하는 시간보다 더 많은 시간을 소요하여 다시 실행된다는 것이다.

그래서 'sleep()' 함수가 지연시키는 시간은 OS 상태, CPU 상태에 따라 부정확하고 알 수 없으며, 이는 위험성을 야기한다.

Sleep() 함수 실행 시간 출력하기

아래는 CPU의 타이머 주파수를 이용해 Sleep() 함수 호출 후 실제로 흐른 시간을 출력하는 코드이다.

#include <iostream>
#include <windows.h>

int main()
{
        LARGE_INTEGER freq;
        LARGE_INTEGER begin;
        LARGE_INTEGER end;
        __int64 elapsed;
        double during;

        // CPU 타이머 주파수 확인
        ::QueryPerformanceFrequency(&freq);
        std::cout << "초당 주파수: " << freq.QuadPart << std::endl;

        for (int i = 0; i < 10; i++)
        {
        		// 시작할 때 클럭(Clock) 수 저장
                ::QueryPerformanceCounter(&begin);
                //////////////////////////////////////////////////
                // 1 ms 중단
                ::Sleep(1);
                /////////////////////////////////////////////////
                // 끝났을 때 클럭 수 저장
                ::QueryPerformanceCounter(&end);

                elapsed = end.QuadPart - begin.QuadPart;
                during = (double) elapsed / freq.QuadPart;
                std::cout << "실제로 흘러간 시간(micro): " << during * 100 * 1000 << std::endl;
        }
        return 0;
}

Output

초당 주파수: 10000000
실제로 흘러간 시간(micro): 1279.41
실제로 흘러간 시간(micro): 1401.82
실제로 흘러간 시간(micro): 1375.61
실제로 흘러간 시간(micro): 1366.59
실제로 흘러간 시간(micro): 1423.19
실제로 흘러간 시간(micro): 1372
실제로 흘러간 시간(micro): 1485.09
실제로 흘러간 시간(micro): 1353.28
실제로 흘러간 시간(micro): 1384.54
실제로 흘러간 시간(micro): 1400.88

참고 자료

0개의 댓글