Concurrency Runtime – Task Scheduler 5. ( Context blocking )
VC++ 10 Concurrency Runtime 2010. 10. 12. 08:30Concurrency Runtime
– Task Scheduler 5. ( Context blocking )
작성자: 임준환( mumbi at daum dot net )
시작하는 글
이번 글에서는 컨텍스트를 제어할 수 있도록 도와주는 wait() 와 Context 클래스에 대해서 알아보도록 하겠습니다.
void Concurrency::wait( unsigned int _Milliseconds )
현재 컨텍스트를 지정된 시간 동안 지연시킵니다.
Windows API 의 Sleep() 과 같은 기능을 합니다. 하지만 Sleep() 과 달리 협조적 스케줄링을 한다는 것이 다릅니다. 컨텍스트가 지연되는 동안 컴퓨팅 리소스( core ) 는 다른 작업을 수행합니다.
매개변수로 0을 전달하면 다른 모든 활성화된 컨텍스트들이 작업을 수행할 수 있을 때까지 현재 컨텍스트를 지연시킵니다. 다른 작업들에게 양보한다는 뜻입니다.
Context
Context 클래스는 현재 컨텍스트를 나타냅니다. Context 클래스는 크게 2 가지의 기능을 가지고 있는데 그 중 첫 번째는 컨텍스트의 블러킹( blocking ) 이고, 두 번째는 초과 스레드 생성입니다.
이번 글에서는 컨텍스트의 블러킹에 대해서 알아보록 하고, 초과 스레드 생성은 다음 글에서 살펴보도록 하겠습니다.
Block & Unblock
현재 컨텍스트를 블럭킹하려면 Context::Block() 을 호출하면 됩니다. 이렇게 되면 현재 컨텍스트는 수행을 멈추고 다른 작업들에게 리소스를 양보하게 됩니다.
블러킹을 해제하려면 Context::Unblock() 을 호출하면 됩니다. 하지만 블러킹된 컨텍스트에서 스스로 블러킹을 해제할 수 없습니다. 블러킹된 컨텍스트에서 자기의 컨텍스트의 블러킹을 해제하려고 하면 context_self_unblock 예외가 던져집니다.
실제로 Context 객체를 사용해 블러킹과 해제를 하려면 Context::CurrentContext() 를 호출하여 현재 스레드에 연결된 Context 객체를 참조하여 블러킹하고, 다른 컨텍스트에 그 참조를 전달하여, 나중에 참조를 사용하여 블러킹을 해제해야 합니다.
Block() 과 Unblock() 은 항상 쌍을 이루어 호출되어야 합니다. 만약 같은 함수가 연속으로 호출되면 context_unblock_unbalanced 예외를 던집니다.
또한 Context 객체는 스케줄링 양보를 위한 Context::Yield() 를 제공합니다. Context::Block() 을 사용하면 스케줄링되지 않습니다.
예제
Context 클래스를 이용한 세마포어( semaphore ) 의 구현을 보도록 하겠습니다.
시나리오
구현된 세마포어를 사용한 parallel_for() 를 사용하여 작업이 어떻게 진행되는지 살펴보겠습니다.
코드
#include <Windows.h> #include <concrt.h> #include <concurrent_queue.h> #include <iostream> #include <sstream> #include <ppl.h> using namespace std; using namespace Concurrency; class semaphore { public: explicit semaphore( long capacity ) : semaphore_count( capacity ) { } void acquire() { if( InterlockedDecrement( &this->semaphore_count ) < 0 ) { this->waiting_contexts.push( Context::CurrentContext() ); Context::Block(); } } void release() { if( InterlockedIncrement( &this->semaphore_count ) <= 0 ) { Context* pWaitingContext = nullptr; while( !this->waiting_contexts.try_pop( pWaitingContext ) ) Context::Yield(); pWaitingContext->Unblock(); } } class scoped_lock { public: ~scoped_lock() { this->s.release(); } scoped_lock( semaphore& s ) : s( s ) { this->s.acquire(); } private: semaphore& s; }; private: long semaphore_count; concurrent_queue< Context* > waiting_contexts; }; int main() { semaphore s( 3 ); parallel_for( 0, 10, [&]( int i ) { semaphore::scoped_lock lock( s ); wstringstream ss; ss << L"In loop interation " << i << L"..." << endl; wcout << ss.str(); Concurrency::wait( 2000 ); } ); }
[ 코드1. Context 객체를 이용한 세마포어 구현 예제 ]
Parallel Patterns Library( 이하, PPL ) 의 acquire() 호출 시, 남은 자원이 없다면 해당 Context 객체를 블러킹하고 concurrent_queue 를 사용하여 관리합니다.
release() 호출 시, 남는 자원이 생기면 관리 중인 Context 객체를 꺼내서 블러킹을 해제합니다.
parallel_for() 를 사용해 10개의 작업을 수행하는데 세마포어에 의해 3 개의 작업 단위로 수행하게 됩니다.
[ 그림1. Context 객체를 이용한 세마포어 구현 예제 실행 결과 ]
마치는 글
Context 클래스를 이용해서 어떻게 컨텍스트를 제어할 수 있는지 알아보았습니다.
다음 글에서는 Context 클래스의 두 번째 기능인 초과 스레드 생성이란 무엇인지 알아보겠습니다.
'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 2. ( SchedulerPolicy ) (0) | 2010.09.18 |
Concurrency Runtime – Task Scheduler 1. ( Scheduler ) (0) | 2010.09.02 |
Concurrency Runtime - 만델브로트 프랙탈 ( Mandelbrot Fractal ) 예제 (3) | 2010.08.28 |