본문 바로가기

진리는어디에

Threads Scheduling

/**
 이래저래 먹고 사는데 바쁘다 보니 글하나 올리기도 빡세구나. 아직도 해야 할 일이 많은데 내가 생각하는 뭔가를 정리하고 올린다면 시간이 더 많이 걸릴 것 같고, 오늘은 웹에서 떠돌아 다니는 원문을 간단하게 해석해 보는 것으로 블로깅을 마무리 해야겠다.
 
 주제는 쓰레드 스케줄링(thread scheduling)에 관련한 것으로, 내가 멀티 쓰레드를 이용해서 뭔가를 하는데 아무리해도 성능이 안나오길래 혹시 scheduling에 관련한 문제가 있지 않나 해서 조사하다 찾은 문서를 설명 할 것이다. 원론적인 스케줄링에 관련된 이야기는 아니고, 여러가지 스케줄링 기법이 있고, 내가 생성한 쓰레드가 어떤 스케줄링 알고리즘을 사용하게 설정 하느냐 하는 API 사용법 정도라고 생각하면 되겠다. 보다 자세한 쓰레드 스케줄링에 대해서 알고 싶으면 'Operating System Concepts'라는 책을 추천한다. 흔히들 공룡책이라고 하면 안다.
*/

Inheritsched Attribute 

inheritsched attribute(사전에 없는 단어지만 상속받은 스케줄 속성 이라고 생각하면 될 듯)가 어떻게 정해 질 것인지를 정한다. 정해질 것을 정한다고 하니 말이 이상하게 꼬이는 감이 있는데, 쓰레드는 기본적으로 쓰레드를 생성하는 객체의 속성을 물려 받겠끔 되어 있다. 하지만 이런 속성들을 변경하고 싶다면 기본 속성을 물려 받지 않고 따로 정의 하겠다고 표시를 해야 한다. 그것이 스케줄 속성이 어떻게 정해 질지(물려 받을지 새로 지정할지)를 정하는 것이라고 하겠다. 속성은 아래와 같이 두 가지가 정의 되어 있다.

PTHREAD_INHERIT_SCHED 새로 생성되는 쓰레드가 스케줄링 속성(정책, 파라메터 속성등)을 부모쓰레드(새로운 쓰레드를 생성하는 쓰레드)에게서 물려 받겠다는 의미. 이 플래그를 지정하면 다른 모든 쓰레드 스케줄링 속성들이 무시되고 부모의 속성만을 물려 받는다.
PTHREAD_EXPLICIT_SCHED 새로 모든 속성들을 재 지정 해주겠다는 의미.

 위에서도 이야기 했듯이 기본 값은 PTHREAD_INHERIT_SCHED다. pthread_attr_setinheritsched 함수에 의해서 셋팅이 되고, 현재 셋팅 되어져 있는 값은 pthread_attr_getinheritsched 함수를 통해 얻어 올 수 있다.
 
 새로운 속성들을 정의 해주기 위해서는 반드시 PTHREAD_EXPLICIT_SCHED로 셋팅 되어야 하고, 그렇지 않으면 모든 값들이 무시된다.

Scheduling Policy and Priority

쓰레드 라이브러리는 아래의 세 가지 스케줄링 방법을 제공하고 있다 :

SCHED_FIFO First-in first-out (FIFO) scheduling. 각 쓰레드들이 고정된 priority를 가지고 있고, 여러 쓰레드들이 같은 prioirity level을 가지고 있으면 FIFO 형태로 돌아 간다.
SCHED_RR Round-robin (RR) scheduling. 각 쓰레드들이 고정된 priority르 가지고 있고, 여러 쓰레드 들이 같은 priority level로, 고정된 time slice 내에서만 동작하면 RR형태로 돌아간다.
SCHED_OTHER 기본 AIX scheduling. 각 쓰레드들이 동적으로 (activity나 기타 요소에 따라) 변경이 가능한 priority를 할당 받고, 쓰레드의 실행 시간이 일정 time-slice 내에서 실행 되게 되는 형태. 그런데 이것은 시스템의 구현 마다 다 다를 수 있다.

기본적인 스케줄링 알고리즘은 SCHED_OTHER로 셋팅되어져 있다.
/**
  사족이지만, 별다른 특별한 이슈가 없지 않는한 기본으로 셋팅되어지는 SCHED_OTHER가 가장 성능이 좋다고 개인적으로 판단한다. 아니 개인적 뿐만 아니라, 이 문서에서도 별다른 특정한 상황이 아니면 SCHED_OTHER가 가장 좋은 성능을 보일 것이라고 이야기 한다.
*/

Priority 는 정수형태의 값으로써 1에서 127까지의 값을 가질 수 있다. 1이 가장 우선순위가 낮고, 127이 가장 우선 순위가 높다. Priority level 0은 시스템에 의해 예약 되어 있기 때문에 사용 할 수 없다.
※ AIX 커널에서는 앞에 이야기 했던 priority level의 순서가 거꾸로 라는 것에 주의 하도록 하자. ps 커맨드를 이용하면 priority를 볼 수 있다.

쓰레드 라이브러리는 sched_param structure로 priority를 조절한다. sched_param은 sys/sched.h 파일에 정의 되어 있고,, 지금은 아래와 같이 두 개의 필드를 가지고 있다 :

sched_priority priority를 지정.
sched_policy 쓰레드 라이브러리에 의해 무시되며, 사용 해서는 안된다.

앞으로 쓰레드의 다른 속성을 나타내기 위해 필드가 더 추가 될 수 있다.
/**
  나의 경우는 sys/sched.h가 아니라 bit/sched.h에 sched_param이 정의 되어 있었고, 필드도 __sched_priority 하나 뿐이 었다. 운영체제는 CentOS2.0 다.
*/

Setting the Scheduling Policy and Priority at Creation Time

 쓰레드가 생성 될 때 pthread_attr_setschedpolicy 함수를 이용해서 위에서 언급한 세 가지 스케줄링 알고리즘 중에 하나를 지정 할 수 있다. 그리고 현재 지정되어 있는 스케줄링 알고리즘 값을 얻어 오려면 pthread_attr_getschedpolicy 함수를 이용하면 된다.

 Scheduling priority는 쓰레드 생성 시점에 sched_param 속성을 통해 지정 될수 있다. pthread_attr_setschedparam 함수로 schedparam 을 셋팅하거나,  pthread_attr_getschedparam 함수로 schedparam 값을 얻어 올 수 있다.

아래 코드는 RR(round-robin) 스케줄링 알고리즘과 priority level 3을 가지고 쓰레드를 생성 시킨다 :

sched_param schedparam;
 
schedparam.sched_priority = 3;
pthread_attr_init(&attr);
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
pthread_attr_setschedpolicy(&attr, SCHED_RR);
pthread_attr_setschedparam(&attr, &schedparam);
pthread_create(&thread, &attr, &start_routine, &args);
pthread_attr_destroy(&attr);

Setting the Scheduling Attributes at Execution Time

 현재 스케줄링 알고리즘과 priority는 pthread_getschedparam 함수로 알아 낼 수 있다. 역시 이 값들은  pthread_setschedparam 함수로 셋팅될 수 있다. 만일 대상 쓰레드가 현재 프로세서에 의해 처리중이라면 새로 셋팅되는 스케줄링 알고리즘과 priority는 다음 스케줄될 때(프로세서에 의해 처리 될 때) 반영 된다. 반대로 현재 쓰레드가 동작 중(running)이 아니라면, 함수 호출과 동시에 반영이 된다.

 예를 들어, 쓰레드 T는 현재 RR알고리즘으로 동작 중(running)이며, 이것을 FIOF로 변경 하고자 한다. 일단 쓰레드 T는 주어진 타임 슬라이스 동안 계속 RR로 동작 할 것이며, 주어진 타임슬라이스를 다 쓰고 나면 다시 running 상태로 가기 위해 대기 상태로 간다. 만일 쓰레드 T 보다 더 높은 우선 순위를 가진 쓰레드가 없다면 다시 running 상태로 넘어 갈것이고, 이제서야 T는 FIFO로 변경 된다. 한 가지 예를 더 들어 보도록 하자. 낮은 우선 순위를 가진 쓰레드 T가 대기 상태에 있고, 다른 쓰레드가 pthread_setschedparam 함수를 호출하여 T의 우선 순위를 높였다고 하자. 이 경우 T보다 높은 우선 순위를 가진 쓰레드가 동작 중이 아니라면 T는 바로 running 상태로 넘어가게 된다.

Note: Both subroutines use two parameters: a policy parameter and a sched_param structure. Although this structure contains a sched_policy field, programs should not use it. The subroutines use the policy parameter to pass the scheduling policy and ignore the sched_policy field.

Considerations about Scheduling Policies

 무슨 특별한 이유가 있지 않는 이상, 어플리케이션은 디폴트 스케줄링 알고리즘을 사용해야만 한다.

 RR 알고리즘은 같은 우선 순위를 가진 스케줄들이 같은 타임 슬라이스를 할 당 받는 것을 보장 한다. 단, 쓰레드의 activity와 전혀 관계 없이 시간을 할당 하기 때문에 쓸모 없는 자원의 낭비가 발생 할 수도 있다. 이런 RR 알고리즘은 센서에서 값을 읽어 오거나(센서는 보통 일정 주기로 한 번씩 메시지를 보낸다) 동작기에 뭔가를 쓸 때 사용 하면 좋다.

 FIFO는 크리티컬한 작업들을 다룰 때 사용하면 좋다. FIFO를 사용하게 되면 특별한 인터럽트가 들어오기 전까지는 작업이 완료 될때 까지 계속 진행 된다. 높은 우선 순위를 가지고 있는 FIFO 쓰레드는 다른 쓰레드들에 의해 running 상태를 안 빼앗길 확률이 높으며(non-preermptive) 시스템 전체의 성능에 영향을 끼칠 수 있다. 예를 들어 FIFO는 강도가 세고 연산시간을 오래 잡아 먹는 계산(커다란 행렬 연산 같은 것)을 하는데 사용되면 전체 성능을 다운 시길 수 있다.

Contention Scope

쓰레드 라이브러리는 아래의 두 가지 contention scopes(분쟁 범위?)를 정의 한다 :
/**
 영향을 미치는 범위가 한 프로세스 내에서만인지, 아니면 시스템 전역인지를 구분 하는 것을 contention scope라고 한다.
*/

PTHREAD_SCOPE_PROCESS Process (or local) contention scope. 프로세스 내에 있는 모든 다른 쓰레드들에 한정하여 contention scope를 설정한다.
PTHREAD_SCOPE_SYSTEM System (or global) contention scope. 시스템 전역적으로 contention scope를 설정한다.

Setting the Contention Scope

 Contention scope 는 오직 쓰레드가 생성 되는 시점에만 셋팅 될 수 있다. pthread_attr_setscope 함수와 pthread_attr_getscope 함수를 통해 관련된 값을 셋팅하거나 얻어 올 수 있다.

 Contention scope 라는 것은 오직 M:N 쓰레드 구조(커널 쓰레드와 유저 쓰레드의 관계가 다대다 라는 이야기. 자세한 사항은 'Operating System Concepts'라는 책을 추천)에서만 유효하다. single-scope(1:1) 구조(예를 들어 AIX version 4.3)에서는 PTHREAD_SCOPE_PROCESS를 셋팅하면 항상 에러를 리턴한다. 이유는 모든 쓰레드들이 system contention scope를 가지고 있기 때문이다. 아래의 TestImplementation 처럼 함으로써 지원 여부를 쉽게 알아 낼 수 있다 :

int TestImplementation()
{
        pthread_attr_t a;
        int result;
        pthread_attr_init(&a);
        switch (pthread_attr_setscope(&a, PTHREAD_SCOPE_PROCESS))
        {
                case 0:          result = LIB_MN; break;
                case ENOTSUP:    result = LIB_11; break;
                case ENOSYS:     result = NO_PRIO_OPTION; break;
                default:         result = ERROR; break;
        }
        pthread_attr_destroy(&a);
        return result;
}

Prior to AIX Version 4.3, this routine would return LIB_11.

In AIX Version 4.3, this routine returns LIB_MN.

Impacts of Contention Scope on Scheduling

 Contention scope는 쓰레드 스케줄링에 영향을 미치게 된다. 각 system contention scope는 하나의 커널 쓰레드에 묶이게 된다. 그래서 글로벌 유저 쓰레드의 스케줄링 알고리즘과 우선순위를 변경하게 되면 그 밑에 놓여 있는 커널 쓰레드의 스케줄링 알고리즘과 우선 순위를 변경 하게 되는 결과를 낳는다.

AIX에서는, 오직 root권한을 가진 커널 쓰레드만이 FIFO나 RR을 사용 할 수 있다. 아래 코드를 보도록 하자 :

	schedparam.sched_priority = 3;
	pthread_setschedparam(pthread_self(), SCHED_FIFO, schedparam);

 위의 코드를 system contention scope로 설정 되있고, 루트 권한 없이 실행 했다면 EPERM error code를 리턴 할 것이다. 하지만 process contention scope를 가지고 실행하게 된다면 에러를 리턴 하지 않는다. process contention scope에서는 쓰레드 스케줄링을 하기 위해 루트 권한이 필요하지 않다.

 로컬 유저 쓰레드(Local user thread)는 프로세스 내에서 어떠한 스케줄링 알고리즘이나 우선 순위를 설정 할 수 있다. 하지만 두 쓰레드가 같은 스케줄링 알고리즘과 우선순위를 가지고 있고, 다른 contention scope를 자기고 있다면 같은 방법으로 스케줄링 되지 않는다. Process contention scope를 가지고 있는 쓰레드는 커널 쓰레드에 의해 처리된다.

sched_yield Subroutine

 sched_yield 함수는 yield 함수와 같다. 이 함수를 호출한 쓰레드는 프로세서를 반환 하고 다른 쓰레드에게 스케줄링 될 기회를 주게 된다. 다음에 스케줄링 되는 쓰레드는 같은 프로세스 내애 있는 스레드이거나 다른 프로세스의 쓰레드일 수 있다. yield 함수는 멀티 쓰레드 프로그램에서 사용되면 안된다.
/**
 개인적으로 나는 쓰레드가 프로세서를 반납 할 때 usleep(1)을 사용해 왔는데, 이것은 낭비하지 않아도 될 1마이크로세컨드를 낭비한 것이다. 앞으로는 sched_yield()나 yield() 함수를 애용 해야 겠다.
 
 그리고 이 문서에서는 멀티쓰레드 환경에서 yield를 사용하면 안된다라고 나오지만 man page에서는 그런 것에 대한 것은 언급이 되어있지 않고 다만 '멀티 쓰레드인 프로세스에서 호출하게 되면, 호출한 쓰레드만 영향을 받는다' 라고 나와 있다. 보다 자세한 설명이 있었으면 하지만 물어 볼 메일 주소도 없고 물어 볼 사람도 없으니 그냥 내가 테스트 해보는 수 밖에..그런데 테스트도 만만치 않쿤하~아놔~
*/

The interface pthread_yield subroutine is not available in XOPEN VERSION 5.

원문 보기 : http://www.unet.univie.ac.at/aix/aixprggd/genprogc/threads_sched.htm
참고 : http://blog.naver.com/raon_pgm?Redirect=Log&logNo=140010348579

유익한 글이었다면 공감(❤) 버튼 꾹!! 추가 문의 사항은 댓글로!!