Concurrency Runtime – Task Scheduler 2. ( SchedulerPolicy )
VC++ 10 Concurrency Runtime 2010. 9. 18. 17:33Concurrency Runtime
– Task Scheduler 2. ( SchedulerPolicy )
작성자: 임준환( mumbi at daum dot net )
시작하는 글
지난 글에서 Scheduler 를 통해서 스케줄링 방법을 설정할 수 있다고 설명 드렸습니다. 스케줄링 방법을 제어하기 위한 기반 정보를 스케줄러 정책이라고 표현하고, 우리는 이 스케줄링 정책을 사용해서 스케줄링을 제어할 수 있습니다.
스케줄러 정책을 대표하는 클래스가 바로 오늘 설명드릴 SchedulerPolicy 입니다.
SchedulerPolicy
스케줄러에게 스케줄링에 필요한 기반 정보를 제공하는 클래스입니다. Scheduler 객체를 생성할 때, 이 SchedulerPolicy 를 지정할 수 있습니다. Scheduler 객체가 생성된 후에는 SchedulerPolicy 를 변경할 수 없습니다.
정책 생성과 설정
스케줄러 정책을 설정하기 위해서는 SchedulerPolicy 객체를 생성할 때, 설정할 정책들을 생성자의 매개변수로 설정해야 합니다.
_CRTIMP SchedulerPolicy(); _CRTIMP SchedulerPolicy(size_t _PolicyKeyCount, ...); _CRTIMP SchedulerPolicy(const SchedulerPolicy& _SrcPolicy);
[ 코드1. SchedulerPolicy 의 생성자 ]
기본 생성자와 복사 생성자를 제공하고, 정책을 설정할 수 있는 생성자를 제공하고 있습니다.
기본 생성자로 생성할 경우, 기본 설정으로 정책을 생성합니다.
정책을 설정할 수 있는 생성자는 첫 번째 인수로 설정할 정책의 수를 입력하고, 그 뒤에 입력한 정책의 수 만큼 정책의 키( key ) 와 값( value ) 을 입력해야 합니다.
SchedulerPolicy policy(3, MinConcurrency, 2, MaxConcurrency, 4, ContextPriority, THREAD_PRIORITY_HIGHEST );
[ 코드2. SchedulerPolicy 객체의 생성 ]
위와 같이 생성한 SchedulerPolicy 객체는 3개의 정책을 설정하는데, 사용할 최소 동시성 자원( computing core ) 의 개수는 2개, 사용할 최대 동시성 자원의 개수는 4개, 현재 컨텍스트의 스레드 우선순위를 최고 순위로 설정하게 됩니다.
위 코드에서 보여드린 정책들 뿐만 아니라 여러 가지 정책들을 설정할 수 있습니다.
설정할 수 있는 정책들
PolicyElementKey 열거형으로 설정할 수 있는 정책들을 정의하고 있습니다. 그 내용은 다음과 같습니다.
| 정책 키 | 설명 | 기본 값 |
| SchedulerKind | 작업들을 수행할 때, 일반 스레드를 사용할지, UMS 스레드를 사용할지 설정 | ThreadScheduler |
| MaxConcurrency | 스케줄러에서 사용할 최대 동시성 자원 수 | MaxExecutionResources |
| MinConcurrency | 스케줄러에서 사용할 최소 동시성 자원 수 | 1 |
| TargetOversubscriptionFactor | 작업을 수행 시, 자원에 할당할 스레드의 수 | 1 |
| LocalContextCacheSize | 캐시할 수 있는 컨텍스트 수 | 8 |
| ContextStackSize | 각 컨텍스트에서 사용할 스택 크기( KB ) | 0( 기본 스택 크기 사용 ) |
| ContextPriority | 각 컨텍스트의 스레드 우선 순위 | THREAD_PRIORITY_NORMAL |
| SchedulingProtocal | 스케줄 그룹의 작업 예약 알고리즘 | EnhanceScheduleGroupLocality |
| DynamicProgressFeedback | 자원 통계 정책, 사용 금지. | ProgressFeedbackEnabled |
[ 표1. PolicyElementKey 열거형에 정의된 설정할 수 있는 정책들 ]
기본 정책 설정
Asynchronous Agents Library( 이하, AAL ) 에는 우리가 생성한 정책을 지정한 Scheduler 객체를 사용할 수 있지만, Parallel Patterns Library ( 이하, PPL ) 에는 우리가 생성한 Scheduler 객체를 사용할 수 없고, 내부적으로 생성되는 기본 스케줄러를 사용하게 됩니다. 기본 스케줄러는 기본 정책을 사용하게 되는데, 이 기본 정책을 미리 설정해 둘 수 있습니다.
_CRTIMP static void __cdecl SetDefaultSchedulerPolicy( const SchedulerPolicy& _Policy );
[ 코드3. SetDefaultSchedulerPolicy() 의 선언 ]
SetDefaultSchedulerPolicy() 의 인자로 SchedulerPolicy 를 생성하여 설정하면, 직접 Scheduler 를 생성하여 사용하지 않더라도, 내부에서 생성되는 기본 스케줄러에도 정책을 설정할 수 있습니다.
스케줄러 정책 획득
이미 생성된 Scheduler 객체로부터 설정된 정책을 가져올 수 있습니다.
_CRTIMP static SchedulerPolicy __cdecl GetPolicy(); // CurrentScheduler::GetPolicy() static function virtual SchedulerPolicy GetPolicy(); // Scheduler::GetPolicy() member function
[ 코드4. GetPolicy() 들의 선언 ]
Scheduler 객체로부터 얻어온 정책은 설정할 때의 정책과 다를 수 있습니다. 정책을 설정하더라도, 해당 시스템에서 설정 가능한 정책만 적용되고, 그렇지 않은 경우에는 기본 정책 값이나 Resource Manager 에 의해 적당한 값으로 적용됩니다.
예를 들어 UMS 를 사용할 수 없는 시스템에서 UMS 를 사용하라고 설정하더라도, ThreadScheduler로 설정됩니다.
예제
Scheduler 를 사용하는 방법과 SchedulerPolicy 인해 어떤 영향을 받는지 알아보는 예제를 보도록 하겠습니다.
시나리오
문자열 순열을 구하는 프로그램입니다. 문자열 순열이란 문자열을 이루는 알파벳들로 만들 수 있는 모든 경우의 수를 말합니다.
문자열 순열을 구하는 작업은 오랜 시간이 걸리므로 agent 를 이용해 비 동기 처리를 합니다. 그리고 문자열 순열을 구하는 작업을 하는 agent 와 통신을 하며 진행 상황을 출력해주는 agent 가 비 동기로 처리됩니다.
이 때, Concurrency Runtime 은 협조적 스케줄링을 하기 때문에 바쁜 스케줄러에 더 많은 자원을 할당합니다. 반대로 바쁘지 않은 스케줄러에는 적은 자원이 할당됩니다. 이로 인해 진행 상황을 출력하는 agent 가 제대로 스케줄링되지 않는 현상이 발생하게 됩니다.
이의 해결책으로 진행 상황을 출력하는 agent 의 스레드 우선 순위를 높게 설정합니다.
코드
#include <Windows.h>
#include <ppl.h>
#include <agents.h>
#include <iostream>
#include <sstream>
using namespace std;
using namespace Concurrency;
// 문자열 순열을 구하는 agent
class permutor
: public agent
{
public:
explicit permutor( ISource< wstring >& source, ITarget< unsigned int >& progress )
: source( source )
, progress( progress ) { }
explicit permutor( ISource< wstring >& source, ITarget< unsigned int >& progress, Scheduler& scheduler )
: agent( scheduler )
, source( source )
, progress( progress ) { }
protected:
void run()
{
wstring s = receive( this->source );
this->permute( s );
this->done();
}
unsigned int factorial( unsigned int n )
{
if( 0 == n )
return 0;
if( 1 == n )
return 1;
return n * this->factorial( n - 1 );
}
wstring permutation( int n, const wstring& s )
{
wstring t( s );
size_t len = t.length();
for( unsigned int i = 2; i < len; ++i )
{
swap( t[ n % i ], t[i] );
n = n / i;
}
return t;
}
void permute( const wstring& s )
{
unsigned int permutation_count = this->factorial( s.length() );
long count = 0;
unsigned int previous_percent = 0u;
send( this->progress, previous_percent );
parallel_for( 0u, permutation_count, [&]( unsigned int i )
{
this->permutation( i, s );
unsigned int percent = 100 * InterlockedIncrement( &count ) / permutation_count;
if( percent > previous_percent )
{
send( this->progress, percent );
previous_percent = percent;
}
} );
send( this->progress, 100u );
}
private:
ISource< wstring >& source;
ITarget< unsigned int >& progress;
};
// 진행 상황을 출력하는 agent
class printer
: public agent
{
public:
explicit printer( ISource< wstring >& source, ISource< unsigned int >& progress )
: source( source )
, progress( progress ) { }
explicit printer( ISource< wstring >& source, ISource< unsigned int >& progress, Scheduler& scheduler )
: agent( scheduler )
, source( source )
, progress( progress ) { }
protected:
void run()
{
wstringstream ss;
ss << L"Computing all permutations of '" << receive( this->source ) << L"'..." << endl;
wcout << ss.str();
unsigned int previous_percent = 0u;
while( true )
{
unsigned int percent = receive( this->progress );
if( percent > previous_percent || percent == 0u )
{
wstringstream ss;
ss << L'\r' << percent << L"% complete...";
wcout << ss.str();
previous_percent = percent;
}
if( 100 == percent )
break;
}
wcout << endl;
this->done();
}
private:
ISource< wstring >& source;
ISource< unsigned int >& progress;
};
// agent 의 작업을 관리하는 함수
void permute_string( const wstring& source, Scheduler& permutor_scheduler, Scheduler& printer_scheduler )
{
single_assignment< wstring > source_string;
unbounded_buffer< unsigned int > progress;
permutor agent1( source_string, progress, permutor_scheduler );
printer agent2( source_string, progress, printer_scheduler );
agent1.start();
agent2.start();
send( source_string, source );
agent::wait( &agent1 );
agent::wait( &agent2 );
}
int main()
{
const wstring source( L"Grapefruit" );
// 기본 정책으로 작업을 수행
Scheduler* pDefault_scheduler = CurrentScheduler::Get();
wcout << L"With default scheduler: " << endl;
permute_string( source, *pDefault_scheduler, *pDefault_scheduler );
wcout << endl;
// 진행 상황을 출력하는 agent 에 필요한 스레드 우선 순위를 높게 하는 정책을 설정하여 적용
SchedulerPolicy printer_policy( 1, ContextPriority, THREAD_PRIORITY_HIGHEST );
Scheduler* pPrinter_scheduler = Scheduler::Create( printer_policy );
HANDLE hShutdownEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
pPrinter_scheduler->RegisterShutdownEvent( hShutdownEvent );
wcout << L"With higher context priority: " << endl;
permute_string( source, *pDefault_scheduler, *pPrinter_scheduler );
wcout << endl;
pPrinter_scheduler->Release();
WaitForSingleObject( hShutdownEvent, INFINITE );
CloseHandle( hShutdownEvent );
}
[ 코드5. Scheduler 객체에 스레드 우선 순위를 높인 SchedulerPolicy 객체를 적용한 예제 ]
기본 정책으로 수행했을 때에는 작업 진행 상황이 제대로 출력되지 않는 반면에, 스레드 우선 순위를 높게 설정한 경우에는 제대로 출력되는 것을 보실 수 있습니다.
[ 그림1. SchedulerPolicy 로 스레드 우선 순위를 변경한 예제 실행 결과 ]
마치는 글
이번 글에서는 Scheduler 의 기본적인 기능 중 하나인 SchedulerPolicy 를 설정하는 방법을 알아보았습니다. SchedulerPolicy 를 이용하여 기본 정책으로 해결되지 않는 다양한 문제점들을 해결 하실 수 있을 것입니다.
'VC++ 10 Concurrency Runtime' 카테고리의 다른 글
| Concurrency Runtime – Task Scheduler 4. ( ScheduleTask ) (0) | 2010.10.04 |
|---|---|
| Concurrency Runtime – Task Scheduler 3. ( ScheduleGroup ) (1) | 2010.09.27 |
| Concurrency Runtime – Task Scheduler 1. ( Scheduler ) (0) | 2010.09.02 |
| Concurrency Runtime - 만델브로트 프랙탈 ( Mandelbrot Fractal ) 예제 (3) | 2010.08.28 |
| Asynchronous Agents Library – message block 9. ( custom ) (0) | 2010.08.24 |

