Thread 내의 usleep 의 문제
이번에 OpenSUSE 11.0 으로 업그레이드하면서 이상한 현상이 발생하였습니다.
10.3에서 잘돌던 프로그램이 11.0으로 오면서 CPU 가 150% 이상 점유하는 문제였습니다.
그래서 결국에 문제를 찾기위해서 프로그램을 디버그해보았습니다.
문제가 생긴 부분은 thread에서 cpu점유를 막기위해서 사용한 usleep의 문제였습니다.
usleep(10)이라 해서 10us 만 쉬도록 되어있던 코드가 문제였습니다.
일단 thread내에서 cpu 점유율을 낮추기 위해서 10us를 쉬도록 한것 자체가 성립되지 않는 문제입니다.
그 이유는 man usleep 을 해보면 자세히 나와있고 sleep에 얽힌 이야기들 이란 글에 잘 나와있습니다.
대부분의 UNIX OS(특히 Linux) kernel timer는 1/HZ정도로 맞추어져있으며,
한 thread내에서 sleep등으로 context switching이 일어날 경우 다른 thread를
수행하고 다시 현재의 thread로 돌아오는 시간은 최소 > 10ms가
되며, 이것은 Intel Platform에서 지원하는 timer 주기와 맞물린다고 한다.
(이에 대해선 linux의 jiffies설정과 기타 문서 참조.)
따라서 실제로 thread내에서 usleep이든, nanosleep이든 sleep할 수 있는 최소의
시간은 >1/HZ(10ms intel)가 되어야하며, 이것은 1/100초 즉, 10msec라고 할 수 있다.
실제 sleep할 수 있는 시간은 10msec가 아닌 60msec정도가 한계인 경우(irix,solaris
혹은 linux)도 있다. 이것은 또한 사용자 레벨에서 줄 수 있는 최소의 sleep time이라
볼 수 있다.
usleep의 숫자를 늘린 뒤에는 (10ms이상으로) CPU 점유율이 정상적으로 돌아왔습니다.
glibc 소스를 받아서 10.3과 11.0의 usleep의 구현을 봐도 동일합니다.
(usleep 구현이 내부적으로 nanosleep을 호출하도록 되어있더군요. 구현이 틀리다는 내용도 있었는데 실제 보니.같았습니다.)
KLDP의 글에서 살짝이나마 예상을 할수 있었습니다.
usleep는 deprecated된 함수이고,
일반적으로 nanosleep을 사용하라고 권고하고 있습니다.
내부적으로 usleep은 select()함수를 이용하여 구현되었었다고
알고 있고, select를 직접 쓰시는 것과 큰 차이가 없습니다.
그리고, 한 쓰레드가 usleep을 한다고 해서 다른 쓰레드가 모두
정지하는 것이 아니고, 쓰레드의 스케쥴링 스코프가 SYSTEM
(BOUND Thread)일 경우에는 전혀 상관이 없습니다.
물론 PROCESS 스코프일 경우에도 SUN의 경우는 LWP Level을
자동으로 올려 그런 경우가 없도록 구현해 놓았습니다.
그리고, usleep이 Thread unsafe하다는 이야기는 처음 듣는
것이고, Thread unsafe한 함수의 경우 해당 플랫폼의
매뉴얼이나 Thread 프로그래밍 문서에 잘 나와있습니다.
(Signal Unsafe한 경우도 고려해야 합니다.)
nanosleep의 경우 사실상 CPU를 적게쓰기 위해 호출하는
것은 적절하지 않다는 생각이 듭니다.
문맥교환의 기본 시간단위가 mili second인데 nano second
단위로 Sleep을 하려면 CPU를 소모하면서 busy wait을
한 이후에 깨어나는 수 밖에는 없어서 그렇습니다.
개인적으로 테스트해 본 결과, select()보다 nanosleep()의 경우
CPU를 때때로 더 많이 사용하는 경우를 맞닥드린 경우도
있습니다. (물론 개인적인 테스트이기 때문에 편차가 있을 것으로
생각합니다만)
만일 CPU 점유율을 낮추기 위해 sleep을 하시려면
제가 보기에는 select() 관련 함수가 가장 적합해 보입니다.
좋은 결과 있기 바랍니다.
좀 오래된 글이라 (2003년) 안 맞는 이야기가있긴합니다.(usleep은 deprecated되었다 라던지. usleep구현이 틀리다. 현재 glibc 소스를 보면 usleep내부적으로 nanosleep로 되어있습니다.)
위의 내용에서 u sec의 수준으로 sleep을 하는 경우에 CPU를 소모하면서 busy wait를 한다고 되어있습니다.
아마 이게 주 원인이 아닐까 싶습니다.
결론적으로 thread내의 usleep의 10ms 이하의 sleep은 의미가 없고 OpenSUSE 11.0에서는 더 많은 cpu를 사용하는
문제가 생깁니다.
ps: 아직까지 11.0의 무엇이 이 원인을 생기게 했는지는 모르겠습니다. 누구 아시는 분은 좀 알려주세요.
추가의 글..
nanosleep의 manual을 보면 최소의 resolution은 1HZ라고 되어있습니다.
BUGS
The current implementation of nanosleep() is based on the normal kernel timer mechanism, which has a resolution of 1/HZ s (see time(7)). Therefore, nanosleep() pauses always for at least the
specified time, however it can take up to 10 ms longer than specified until the process becomes runnable again. For the same reason, the value returned in case of a delivered signal in *rem is
usually rounded to the next larger multiple of 1/HZ s.Old behavior
In order to support applications requiring much more precise pauses (e.g., in order to control some time-critical hardware), nanosleep() would handle pauses of up to 2 ms by busy waiting with
microsecond precision when called from a process scheduled under a real-time policy like SCHED_FIFO or SCHED_RR. This special extension was removed in kernel 2.5.39, hence is still present in
current 2.4 kernels, but not in 2.6 kernels.In Linux 2.4, if nanosleep() is stopped by a signal (e.g., SIGTSTP), then the call fails with the error EINTR after the process is resumed by a SIGCONT signal. If the system call is subse-
quently restarted, then the time that the process spent in the stopped state is not counted against the sleep interval.
man 7 time을 해보면 다음과 같은 글이 있습니다.
The Software Clock, HZ, and Jiffies
The accuracy of many system calls and timestamps is limited by the resolution of the software clock, a clock maintained by the kernel which measures time in jiffies. The size of a jiffy is
determined by the value of the kernel constant HZ. The value of HZ varies across kernel versions and hardware platforms. On i386 the situation is as follows: on kernels up to and including
2.4.x, HZ was 100, giving a jiffy value of 0.01 seconds; starting with 2.6.0, HZ was raised to 1000, giving a jiffy of 0.001 seconds; since kernel 2.6.13, the HZ value is a kernel configuration
parameter and can be 100, 250 (the default) or 1000, yielding a jiffies value of, respectively, 0.01, 0.004, or 0.001 seconds. Since kernel 2.6.20, a further frequency is available: 300, a num-
ber that divides evenly for the common video frame rates (PAL, 25 HZ; NTSC, 30 HZ).
위의 글들이 좀 오래된것이라 2001년 2003년.. 현재 상황과는 안 맞는 부분이 있습니다.
테스트 하나)
OpenSUSE 11.0과 OenSUSE 10.3에서 usleep(1)을 했을때 얼마 정도 지나야 깨어나는지 테스트해보았습니다.
#include <unistd.h>
#include <sys/time.h>
#include <stdio.h>int main()
{
struct timeval tv;gettimeofday(&tv,NULL);
printf("%d %d\n", tv.tv_sec, tv.tv_usec);
for(int i=0; i<10; i++)
{
usleep(1);
}
gettimeofday(&tv,NULL);
printf("%d %d\n", tv.tv_sec, tv.tv_usec);
}
OpenSUSE 10.3 (평균 7382us)
1215153330 337156
1215153330 415476
OpenSUSE 11.0(평균 9.4us)
1215153341 235090
1215153341 235184
보면 usleep의 resolution이 11.0이 상당히 높음을 알수 있습니다.
결국 OpenSUSE 11.0에서 usleep 을 사용하여 thread를 쉬게 한뒤에 넘겼을 경우에 빠른 시간내에 제어권을 가지고 오기 위해
CPU를 사용했다는것을 알수 있습니다.
[출처] Thread 내의 usleep 의 문제|작성자 황제펭귄