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 |

