Concurrency Runtime – Task Scheduler 4. ( ScheduleTask )

VC++ 10 Concurrency Runtime 2010. 10. 4. 08:30 Posted by 알 수 없는 사용자

Concurrency Runtime
– Task Scheduler 4. ( ScheduleTask )

작성자: 임준환( mumbi at daum dot net )

 

시작하는 글

 Concurrency Runtime 에서는 기존의 멀티스레드 프로그래밍할 때, 필수적인 스레드를 생성하는 함수( CreateThread(), _beginthread() 등 )와 같은 기능을 제공합니다.

이 글에서는 어떻게 위와 같은 기능을 제공하고, 어떻게 사용하면 되는지 알아보도록 하겠습니다.

 

ScheduleTask

 이 전에 설명했던 Parallel Patterns Library( 이하, PPL ) 이나 Asynchronous Agents Library( 이하, AAL ) 을 사용하게 되면 암묵적으로 스레드가 생성되고, 관리되기 때문에, 아주 간단한 작업을 처리할 때에는 비교적 높은 오버헤드( overhead )를 갖게 됩니다.

 그래서 Concurrency Runtime 에서는 간단한 작업들을 처리하기에 비교적 적은 오버헤드를 갖는 기능을 제공합니다.

 이 간단한 작업을 처리하는 방법은 기존의 스레드를 생성하는 방법과 유사합니다. 즉, 직접 스레드 코드를 제어하고 싶을 때, 사용할 수 있습니다.

 아쉬운 점은 간단한 작업의 처리를 완료했을 때, 알려주지 않습니다. 스레드 핸들을 사용하지 않기 때문에 WaitForSingleObject() 와 같은 함수도 사용할 수 없습니다.

 그렇기 때문에 스레드로 수행될 함수의 인자를 넘길 때, 스레드 종료를 알리는 메커니즘에 필요한 데이터를 포함해주어야 합니다.( 또는 전역적으로.. )

 Concurrency Runtime 에서는 위와 같이 스레드를 생성하는 기능을 제공하지만, 간단한 작업이 아니라면 PPL 또는 AAL 을 사용하는 것을 권하고 있습니다.

 이러한 기능은 ScheduleGroup::ScheduleTask(), CurrentScheduler::ScheduleTask(), Scheduler::ScheduleTask() 를 호출하여 사용합니다.

 

ScheduleGroup::ScheduleTask( TaskProc _Proc, void* _Data );

 일정 그룹 내에 간단한 작업을 추가합니다.

 첫 번째 매개변수의 타입인 TaskProc 는 다음과 같이 정의되어 있습니다.

typedef void (__cdecl * TaskProc)(void *)

[ 코드1. TaskProc 의 정의 ]

 그렇기 때문에 위와 같은 모양( signature ) 를 갖는 함수 포인터를 인자로 넘기면 됩니다.

두 번째 매개변수는 위에서 인자로 넘긴 함수 포인터의 인자로 넘길 데이터입니다.

 

CurrentScheduleer::ScheduleTask( TaskProc _Proc, void* _Data );

 호출하는 컨텍스트와 연결된( attached ) 스케줄러에 간단한 작업을 추가합니다.

매개변수의 내용은 위의 ScheduleGroup::ScheduleTask() 와 같습니다.

만약 호출하는 컨텍스트에 연결된 스케줄러가 없을 경우에는 기본 스케줄러에 추가합니다.

 

Scheduler::ScheduleTask( TaskProc _Proc, void* _Data );

 해당 스케줄러에 간단한 작업을 추가합니다.

매개변수의 내용은 위의 ScheduleGroup::ScheduleTask() 와 같습니다.

 

예제

 위에 언급된 내용과 함수들을 어떻게 사용하는 알아보도록 하겠습니다.

 

시나리오

 새로운 스레드를 생성하고, 그 스레드에서 인자로 전달 받은 구조체의 내용을 출력하는 내용입니다.

 

코드

#include <iostream>
#include <concrt.h>

using namespace std;
using namespace Concurrency;

void __cdecl MyThreadFunction( void* pParam );

// 스레드에서 사용할 데이터
struct MyData
{
	int val1;
    int val2;
    event signal;
};

int main()
{
	MyData* pData = new MyData;

	if( nullptr == pData )
		return 1;
	
	pData->val1 = 50;
	pData->val2 = 100;
	
	// _beginthreadex() 처럼 스레드를 생성하고 인자로 넘어간 함수를 생성된 스레드에서 수행한다.
	CurrentScheduler::ScheduleTask( MyThreadFunction, pData );

	// 수행 중인 작업이 끝날 때까지 대기.
	pData->signal.wait();

	// 작업이 끝난 후, 자원 해제.
	delete pData;	

	return 0;
}

[ 코드2. CurrentScheduler::ScheduleTask() 를 사용하여 스레드를 생성하는 예제 ]

 간단한 구조체를 생성하고, 스레드에서 수행될 함수에 전달합니다.

 스레드에서는 구조체의 정보를 출력합니다.

 스레드의 수행이 종료되기 전에 구조체를 제거하면 안되므로, event 객체를 이용해 스레드가 종료될 때까지 기다린 후, 제거합니다.

 

마치는 글

 Concurrency Runtime 을 사용 중에 간단하게 스레드를 생성해야 한다면, 기존의 스레드 생성 함수를 사용하는 것보다 일관성 있게 ScheduleTask 를 사용하는 것이 좋습니다.

 하지만 이 글에서 언급했듯이 간단하지 않은 작업이라면 PPL 이나 AAL 을 사용하는 것이 좋습니다.

 다음 글에서는 Scheduler 에서 제공하는 Context 에 대해서 알아보도록 하겠습니다.

[Step. 14] 인터페이스 ( interface )

C++/CLI 2010. 10. 1. 09:30 Posted by 알 수 없는 사용자

인터페이스는 비관리코드에서는 순수가상함수만을 가진 클래스와 같습니다. ‘interface class’라는 키워드를 사용하여 정의하면 이 클래스에는 아래와 같은 형만 멤버로 가질 수 있습니다.

 

함수

프로퍼티

이벤트

 

또한 선언만 가능하지 정의는 할 수 없습니다.

 


C++/CLI ref class C#이나 Java와 같이 다중 상속은 할 수 없지만 인터페이스를 사용하면 다중 상속(즉 인터페이스를 상속)을 할 수 있습니다.

 

interface class IA {

public:

    void funcIA();

};

interface class IB {

public:

    void funcIB();

};

interface class IC {

public:

    void funcIC();

};

ref class A {

    int i;

};

ref class B : A, IA, IB, IC {

public:

    virtual void funcIA() {   }

    virtual void funcIB() {   }

    virtual void funcIC() {   }

};

int main() {

    B^ b = gcnew B;

 

    IA^ ia = b;

    IB^ ib = b;

    IC^ ic = b;

    b->funcIA();

    ia->funcIA();

    ib->funcIB();

    ic->funcIC();

    return 0;

}

 



출처

http://cppcli.shacknet.nu/cli:interface

 

 

Concurrency Runtime
– Task Scheduler 3. ( ScheduleGroup )

작성자: 임준환( mumbi at daum dot net )

 

시작하는 글

 Scheduler 는 항상 하나 이상의 스케줄 그룹을 가지고 있습니다. 스케줄 그룹이 무엇인지 알아보고 동시성 프로그래밍에 어떤 영향을 주는지 알아보도록 하겠습니다.

 

SchedulerGroup

 SchedulerGroup 이란 Scheduler 가 스케줄링 해야 할 작업들을 모아 놓은 스케줄 그룹을 대표하는 클래스입니다.

 

정책

 이전 글에서 본 스케줄러 정책들 중 SchedulingProtocol 정책은 스케줄링 순서를 변경하여 작업들의 처리 순서를 변경할 수 있습니다. ScheduleringProtocol 정책이 EnhanseScheduleGroupLocality 로 설정된 경우에는 스케줄링 중인 스케줄 그룹을 변경하지 않고, 현재 스케줄링 중인 스케줄 그룹을 다른 스케줄 그룹보다 먼저 처리합니다. 반면에 SchedulingProtocol 정책이 EnhanseForwardProgress 로 설정된 경우에는 다른 스케줄링 그룹의 작업을 처리합니다. 스케줄 그룹 간에 작업을 공평하게 처리하게 됩니다.

 

생성

 CurrentScheduler::CreateScheduleGroup() 또는 Scheduler::CreateScheduleGroup() 을 통해 ScheduleGroup 객체를 생성할 수 있습니다.

 Scheduler 가 참조 개수를 사용하여 수명을 관리하는 것처럼 ScheduleGroup 객체 또한 참조 개수를 사용합니다. 생성되는 순간 참조 개수는 1이 됩니다. SchedulerGroup::Reference() 는 참조 개수는 1이 증가하고, SchedulerGroup::Release() 는 참조 개수가 1이 감소하게 됩니다.

 

동작 및 사용

 Scheduler 객체에는 기본적으로 기본 ScheduleGroup 을 가지고 있습니다. 명시적으로 ScheduleGroup 을 생성하지 않는다면 스케줄링 해야 할 작업들은 기본 ScheduleGroup 에 추가됩니다.

 ScheduleGroup 은 Concurrency Runtime 중 Asynchronous Agents Library( 이하, AAL ) 에서 사용할 수 있습니다. agent 클래스나 message block 들의 생성자로 ScheduleGroup 을 전달하여 사용할 수 있습니다.

 

예제

 ScheduleGroup 을 어떻게 사용하는지 이해를 도울 예제를 살펴보겠습니다.

 

시나리오

 단순하게 어떤 스케줄 그룹의 작업들이 어떤 순서로 처리되는지 알아보는 예제입니다.

 

코드

#include <agents.h>
#include <vector>
#include <algorithm>
#include <iostream>
#include <sstream>

using namespace std;
using namespace Concurrency;

#pragma optimize( "", off )

void spin_loop()
{
	for( unsigned int i = 0; i < 500000000; ++i )
	{
	}
}

#pragma optimize( "", on )

class work_yield_agent
	: public agent
{
public:
	explicit work_yield_agent( unsigned int group_number, unsigned int task_number )
		: group_number( group_number )
		, task_number( task_number ) { }

	explicit work_yield_agent( ScheduleGroup& scheduleGroup, unsigned int group_number, unsigned int task_number )
		: agent( scheduleGroup )
		, group_number( group_number )
		, task_number( task_number ) { }

protected:
	void run()
	{
		wstringstream header, ss;

		header << L"agent no. " << this->group_number << L"-" << this->task_number << L": ";

		ss << header.str() << L"first loop..." << endl;
		wcout << ss.str();
		spin_loop();

		ss = wstringstream();
		ss << header.str() << L"waiting..." << endl;
		wcout << ss.str();
		Concurrency::wait( 0 );

		ss = wstringstream();
		ss<< header.str() << L"second loop..." << endl;
		wcout << ss.str();
		spin_loop();

		ss = wstringstream();
		ss << header.str() << L"finished..." << endl;
		wcout << ss.str();

		this->done();
	}

private:
	unsigned int	group_number;
	unsigned int	task_number;
};

void run_agents()
{
	const unsigned int group_count = 2;
	const unsigned int tasks_per_group = 2;

	vector< ScheduleGroup* > groups;
	vector< agent* > agents;

	for( unsigned int group_index = 0; group_index < group_count; ++group_index )
	{
		groups.push_back( CurrentScheduler::CreateScheduleGroup() );

		for( unsigned int task_index = 0; task_index < tasks_per_group; ++task_index )
		{
			agents.push_back( new work_yield_agent( *groups.back(), group_index, task_index ) );
		}
	}

	for_each( agents.begin(), agents.end(), []( agent* pAgent )
	{
		pAgent->start();
	} );

	agent::wait_for_all( agents.size(), &agents[0] );

	for_each( agents.begin(), agents.end(), []( agent* pAgent )
	{
		delete pAgent;
	} );

	for_each( groups.begin(), groups.end(), []( ScheduleGroup* pScheduleGroup )
	{
		pScheduleGroup->Release();
	} );
}

int main()
{
	wcout << L"Using EnhanceScheduleGroupLocality..." << endl;
	CurrentScheduler::Create( SchedulerPolicy( 3,
		MinConcurrency, 1,
		MaxConcurrency, 2,
		SchedulingProtocol, EnhanceScheduleGroupLocality ) );

	run_agents();
	CurrentScheduler::Detach();

	wcout << endl << endl;

	wcout << L"Using EnhanceForwardProgress..." << endl;
	CurrentScheduler::Create( SchedulerPolicy( 3,
		MinConcurrency, 1, 
		MaxConcurrency, 2,
		SchedulingProtocol, EnhanceForwardProgress ) );

	run_agents();
	CurrentScheduler::Detach();
}

[ 코드1. 처리되는 스케줄 그룹의 순서를 확인하는 예제 ]

 최대 동시성 리소스( computing core ) 의 개수를 2개로 하고, 한 번은 SchedulingProtocol 정책을 EnhanceScheduleGroupLocality 로 하고, 한 번은 EnhanceForwardProgress 으로 하여 작업의 순서를 알아보았습니다.

 2개의 스케줄 그룹을 생성하고, 하나의 그룹에 2개의 작업을 추가하여, 총 4개의 작업이 수행됩니다.

 예제의 spin_loop() 는 어떤 작업을 처리하는 것을 의미하고 wait() 는 양보를 뜻합니다. wait() 가 호출되면 다른 작업이 스케줄링 되는데 이 때 결정되는 작업이 어떤 작업인지는 앞서 설정한 SchedulingProtocol 정책에 따릅니다.

 EnhanceScheduleGroupLocality 로 설정된 경우에는 같은 스케줄 그룹 내의 작업이 처리되는 반면에, EnhanceForwardProgress 로 설정된 경우에는 다른 스케줄 그룹의 작업이 처리되게 됩니다.

 

[ 그림1. 처리되는 스케줄 그룹의 순서를 확인하는 예제 실행 결과 ]

[ 그림1. 처리되는 스케줄 그룹의 순서를 확인하는 예제 실행 결과 ]

 

마치는 글

 이렇게 스케줄링 정책과 스케줄 그룹을 통해서 스케줄링 순서를 결정하는데 관여할 수 있다는 것을 알아보았습니다.

다음 글에서는 스레드를 직접 생성하여 사용할 수 있는 ScheduleTask 라는 것에 대해서 알아보도록 하겠습니다.

[Step. 15] static 생성자, initonly, literal

C++/CLI 2010. 9. 24. 09:00 Posted by 알 수 없는 사용자

static 생성자

 

static 생성자는 클래스의 생성자에서 static 멤버를 초기화 하고 싶을 때 사용합니다.

ref class, value class, interface에서 사용할 수 있습니다.

 

#include "stdafx.h"

#include <iostream>

 

using namespace System;

 

ref class A {

public:

    static int a_;

    static A()

    {

        a_ += 10;

    }

};

ref class B {

public:

    static int b_;

    static B()

    {

//        a_ += 10; // error

        b_ += 10;

    }

};

ref class C {

public:

    static int c_ = 100;

    static C()

    {

        c_ = 10;

    }

};

 

int main()

{

    Console::WriteLine(A::a_);

    A::A();

    Console::WriteLine(A::a_);

 

    Console::WriteLine(B::b_);

 
    Console::WriteLine(C::c_);


     getchar();

    return 0;

}

 

< 결과 >


 

static 생성자는 런타임에서 호출하기 때문에 클래스 A의 멤버 a_는 이미 10으로 설정되어 있습니다. 그리고 이미 런타임에서 호출하였기 때문에 명시적으로 A::A()를 호출해도 실제로는 호출되지 않습니다.

 

클래스 B의 경우 static 생성자에서 비 static 멤버를 호출하면 에러가 발생합니다.

 

클래스 C의 경우 static 멤버 c_를 선언과 동시에 초기화 했지만 런타임에서 static 생성자를 호출하여 값이 10으로 설정되었습니다.

 

 

 

initonly

 

initonly로 선언된 멤버는 생성자에서만 값을 설정할 수 있습니다. 그리고 initonly static로 선언된 멤버는 static 생성자에서만 값을 설정할 수 있습니다.

 

 

ref class C
{
public:
    initonly static int x;
    initonly static int y;
    initonly int z;
    static C()
    {
        x = 1;
        y = 2;
        // z = 3; // Error
    }
    C()
    {
        // A = 2; // Error
        z = 3;
    }
    void sfunc()
    {
        // x = 5; // Error
        // z = 5; // Error
    }
};


int main()
{
    System::Console::WriteLine(C::x);
    System::Console::WriteLine(C::y);
    C c;
    System::Console::WriteLine(c.z);
    return 0;
}

 

 

 

literal

 

literal로 선언된 멤버는 선언과 동시에 값을 설정하고 이후 쓰기는 불가능합니다. 오직 읽기만 가능합니다.

using namespace System;
ref class C
{
public:
    literal String^ S = "Hello";
    literal int I = 100;
};

int main()
{
    Console::WriteLine(C::S);
    Console::WriteLine(C::I);
    return 0;
}



참고
http://cppcli.shacknet.nu/cli:static%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF
http://cppcli.shacknet.nu/cli:initonly
http://cppcli.shacknet.nu/cli:literal





'C++/CLI' 카테고리의 다른 글

[Step. 16] array 클래스에 non-CLI 오브젝트 사용  (0) 2010.10.27
[Step. 14] 인터페이스 ( interface )  (1) 2010.10.01
[Step. 13] parameter array  (1) 2010.09.10
[Step. 12] for each  (1) 2010.09.03
[Step. 11] 열거형( enum )  (2) 2010.08.27

Concurrency 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 로 스레드 우선 순위를 변경한 예제 실행 결과 ]

[ 그림1. SchedulerPolicy 로 스레드 우선 순위를 변경한 예제 실행 결과 ]

 

마치는 글

 이번 글에서는 Scheduler 의 기본적인 기능 중 하나인 SchedulerPolicy 를 설정하는 방법을 알아보았습니다. SchedulerPolicy 를 이용하여 기본 정책으로 해결되지 않는 다양한 문제점들을 해결 하실 수 있을 것입니다.

[Step. 13] parameter array

C++/CLI 2010. 9. 10. 09:00 Posted by 알 수 없는 사용자

비관리코드에서 로그 기능을 구현할 때 주로 가변 길이 인수를 사용합니다.

void LOG( char* szText, ... )

{

}

 


위와 같은 가변 길이 인수를 C++/CLI에서는 parameter array라는 것으로 구현합니다.

void LOG( … array<Object^>^ Values )

{

for each( Object^ value in Values )

{

   ….

}

}

 

int main()

{

LOG( 23 );

LOG( 2, 1, “error” );

 

return 0;

}

 


parameter array를 사용하면 이전 보다 안전하고, 하나의 형이 아닌 다양한 형을 인자로 받아 들일 수 있어서 유연성이 높습니다.

 

그러나 하나의 함수에서 parameter array는 하나 밖에 사용하지 못합니다. 하지만 parameter array가 아닌 형이라면 여러 개 사용할 수 있습니다. 다만 이 때는 parameter array는 가장 마지막 인수가 되어야 합니다.

 

void LOG( int nLogLevel, … array<Object^>^ Values )

{

}

 

 


참고

http://cppcli.shacknet.nu/cli:parameter_array

 

'C++/CLI' 카테고리의 다른 글

[Step. 14] 인터페이스 ( interface )  (1) 2010.10.01
[Step. 15] static 생성자, initonly, literal  (1) 2010.09.24
[Step. 12] for each  (1) 2010.09.03
[Step. 11] 열거형( enum )  (2) 2010.08.27
[Step. 10] 이벤트 ( event )  (0) 2010.08.20

[Step. 12] for each

C++/CLI 2010. 9. 3. 09:00 Posted by 알 수 없는 사용자

데이터셋에 있는 요소를 열거할 때 비관리코드에서는 보통 for문이나 while문을 자주 사용합니다.

그러나 C++/CLI에서는 for each을 사용하여 데이터셋에 있는 요소들을 열거할 수 있습니다.

for each에서 사용할 수 있는 것은 배열 이외에도 아래의 형으로 구현한 것들을 사용할 수 있습니다.

 

1. IEnumerable 인터페이스를 구현한 클래스

2. STL의 이터레이터와 같은 것을 가지고 있는 클래스

 


참고로 VC++ 8(VS 2005)에서는 비관리코드에서도 for each 문을 지원하고 있습니다.

for each를 사용할 때 주의해야 할 점은 열거하는 요소를 변경할 수는 없다는 것입니다.

 

#include "stdafx.h"

#include <iostream>

#include <vector>

#include <map>

 

using namespace std;

using namespace System;

using namespace System::Collections;

 

 

int main(array<System::String ^> ^args)

{

     // 배열

     array< int >^ Nums = { 0, 1, 2, 3, 4, 5 };

     for each(int value in Nums)

     {

          value = 2;

          Console::WriteLine( value );

     }

    // 위에서 for each 내부에서 요소의 값을 바꾸었지만 아래의 출력 값을 보면

    // 바뀌지 않은 것을 알 수 있습니다.

    for each(int value in Nums)

    {

          Console::WriteLine( value );

    }

 

   

    // 리스트

    ArrayList^ NumList = gcnew ArrayList();

    NumList->Add(1);

    NumList->Add(2);

    NumList->Add(3);

 

    for each(int value in NumList)

    {

        Console::WriteLine(value);

    }

 

 

    // vector

    vector<int> vi;

    vi.push_back(3);

    vi.push_back(4);

   

    for each(int i in vi)

    {

        Console::WriteLine(i);

    }

 

 

    // map

    map<const char*, int> num;

    num["ten"] = 10;

    num["hundred"] = 100;

 

    for each( pair<const char*, int> c in num )

    {

        Console::WriteLine(gcnew String(c.first) + c.second.ToString());

    }

 

 

    // 해쉬 테이블

    Hashtable^ ht = gcnew Hashtable();

    ht["aaa"] = "111";

    ht["bbb"] = "222";

    for each(DictionaryEntry^ dic in ht)

    {

        Console::WriteLine(dic->Key->ToString() + dic->Value->ToString());

    }

    

 

    getchar();

    return 0;

}

'C++/CLI' 카테고리의 다른 글

[Step. 15] static 생성자, initonly, literal  (1) 2010.09.24
[Step. 13] parameter array  (1) 2010.09.10
[Step. 11] 열거형( enum )  (2) 2010.08.27
[Step. 10] 이벤트 ( event )  (0) 2010.08.20
[Step. 09] 델리게이트 (delegate)  (2) 2010.08.12

Concurrency Runtime – Task Scheduler 1. ( Scheduler )

VC++ 10 Concurrency Runtime 2010. 9. 2. 08:30 Posted by 알 수 없는 사용자

Concurrency Runtime
– Task Scheduler 1. ( Scheduler )

작성자: 임준환( mumbi at daum dot net )

 

시작하는 글

 이번 글은 Parallel Patterns Library( 이하 PPL ) 과 Asynchronous Agents Library( 이하 AAL ) 내부에서 스케줄링을 하는 Scheduler 에 대해서 알아보도록 하겠습니다.

 

Scheduler class

 Scheduler 클래스는 Concurrency Runtime 에서 실제로 스케줄링을 하는 객체입니다. 우리는 Scheduler 객체를 사용해서 스케줄링의 방법을 설정할 수 있습니다.

 Scheduler 는 내부적으로 작업들을 그룹화한 ScheduleGroup 을 관리합니다. 또한 요청된 작업을 수행하는 Context 객체에 접근할 수 있도록 하여, 좀 더 구체적인 스케줄링을 할 수 있도록 도와줍니다.

 

Scheduler 생성

 우리가 직접 Scheduler 를 생성하지 않아도, Concurrency Runtime 내부에서 기본 Scheduler 가 생성되어 스케줄링을 하게 됩니다. 이 경우에는 스케줄링 정책을 바꿀 수는 있으나, 세밀하게 제어할 수 없습니다.

 기본 Scheduler 외에 직접 우리가 Scheduler 를 생성하는 방법은 2 가지가 있습니다.

  • CurrentScheduler::Create() 는 현재 컨텍스트와 연결하는 Scheduler 를 만듭니다.
  • Scheduler::Create() 는 현재 컨텍스트와 연결되지 않는 Scheduler 를 만듭니다.

 Scheduler 는 내부적으로 참조 개수( reference counting ) 을 사용하여, 수명을 관리합니다. 그래서 참조 개수가 0이 되면 Scheduler 가 소멸됩니다.

 

Scheduler::Create()

 현재 컨텍스트와 연결되지 않은 Scheduler 를 생성합니다. 참조 개수가 1로 설정됩니다.

 

Scheduler::Attach()

 현재 컨텍스트와 Scheduler 를 연결합니다. 참조 개수가 증가합니다.

 

Scheduler::Reference()

 참조 개수가 증가합니다.

 

Scheduler::Release()

 참조 개수가 감소합니다. 참조 개수가 0이 되면 소멸됩니다.

 

CurrentScheduler::Create()

 현재 컨텍스트와 연결된 Scheduler 를 생성합니다. 참조 개수가 1로 설정됩니다.

 

CurrentScheduler::Detach()

 현재 컨텍스트를 분리합니다. 참조 개수가 감소합니다. 참조 개수가 0이 되면 소멸됩니다.

 

생성과 소멸, 연결과 분리

 위와 같은 함수들을 제공하지만, 생성과 소멸, 연결과 분리가 짝을 이루어야 합니다.

 CurrentScheduler::Create() 로 생성하였다면, CurrentScheduler::Detach() 로 소멸시키는 것이 좋습니다.

 Scheduler::Create() 로 생성하고, Scheduler::Attach() 로 연결하였다면, Scheduler::Detach() 로 해제하고, Scheduler::Release() 로 소멸해야 합니다.

 만약 Scheduler::Reference() 를 통해 참조 개수를 증가시켰다면, Scheduler::Release() 를 사용하여 참조 개수를 감소시켜주어야 합니다.

 

소멸 시점 알림

 모든 작업이 끝나기 전에는 Scheduler 를 소멸시키지 않습니다. 언제 Scheduler 가 소멸되는지 알기 위해서는 RegisterShutdownEvent() 를 사용하여 Windows API 의 EVENT 객체를 지정해 주고, WaitForSingleObject() 를 사용하여 소멸될 때까지 대기할 수 있습니다.

 

그 외의 멤버 함수

 위에서 설명한 멤버 함수 이외에 제공하는 멤버 함수들을 알아보도록 하겠습니다.

 

CurrentScheduler

  • Get() – 현재 컨텍스트에 연결된 Scheduler 의 포인터를 반환합니다. 참조 개수가 증가하지 않습니다.
  • CreateScheduleGroup() -  ScheduleGroup 을 생성합니다.
  • ScheduleTask() – Scheduler 의 일정 큐에 간단한 작업을 추가합니다.
  • GetPolicy() – 연결된 정책의 복사본을 반환합니다.

 

Scheduler

  • CreateScheduleGroup() – ScheduleGroup 을 생성합니다.
  • ScheduleTask() – Scheduler 의 일정 큐에 간단한 작업을 추가합니다.
  • GetPolicy() – 연결된 정책의 복사본을 반환합니다.
  • SetDefaultSchedulePolicy() – 기본 Scheduler 에 적용될 정책을 설정합니다.
  • ResetDefaultSchedulePolicy() – 기본 Scheduler 의 정책을 SetDefaultSchedulerPolicy() 를 사용하기 전의 정책으로 설정합니다.

 

마치는 글

 이번 글에서는 Concurrency Runtime 의 Scheduler 에 대해서 알아보았습니다. 위의 설명만으로는 어떻게 사용해야 하는지, 어떤 기능을 하는지 알기 어렵습니다.

다음 글에서 위에서 소개해드린 멤버 함수들의 사용 방법과 활용 예제들에 대해서 알아보도록 하겠습니다.

[Step. 11] 열거형( enum )

C++/CLI 2010. 8. 27. 09:00 Posted by 알 수 없는 사용자


비관리 코드의 열거형

 

비관리 코드에서 열거형을 정의할 때는 다음과 같습니다.

 

enum WEAPON_TYPE

{

GUN = 1,

SWORD = 2,

BOW = 3

};

 


열거형은 정수형으로 int 형에 대입할 수 있습니다.

int nUsedWeapon = GUN;

 

그런데 저는 위의 방식으로 사용할 때 ‘GUN’이라고 사용하기 보다는 ‘GUN’이 어떤 열거형에 속하는지 표시할 수 있도록 좀 더 다른 방식으로 사용하고 있습니다.

 

struct WEAPON

{

enum TYPE

{

GUN = 1,

SWORD = 2,

BOW = 3

};

};

 

int nUsedWeapon = WEAPON::GUN;

 

이렇게 저는 열거형을 조금 이상한 방법으로 사용하고 있는데 C++/CLI에서는 그럴 필요가 없어졌습니다. C++/CLI는 제가 딱 원하는 방식을 정식으로 지원하고 있습니다.

 


 

C++/CLI의 열거형

 

enum class WEAPON

{

   GUN = 1,

SWORD = 2,

BOW = 3

};

 

int nUsedWeapon = static_cast<int>(WEAPON::GUN);

 

C++/CLI의 열거형은 비관리코드와 비교해서 다른 점은 위에서 알 수 있듯이 암묵적으로 int 형에 대입할 수 없습니다. 왜냐하면 열거형은 정수형이 아니고 object이기 때문입니다.

그래서 캐스팅을 해야 합니다.

 

그리고 C++/CLI의 열거형은 정수형을 명시적으로 정할 수 있습니다.

enum class WEAPON : short

{

   GUN = 1,

SWORD = 2,

BOW = 3

};

 


< 추가 > - 2010. 12. 10

VC++10에서 열거형의 타입을 바로 위의 코드와 같이 명시적으로 지정해도 사용할 수가 없습니다.

enum을 사용할 때 타입 캐스팅을 해야합니다. 왜 이런지 저도 자세한 이유는 모르겠습니다.

C++/CLI에서는 enum 보다는 literal을 사용하는 것이 더 좋을 것 같다고 생각합니다.


public ref class WEAPON
{
public:
        literal short GUN = 1;
        literal short SWORD = 2;

        literal short BOW = 3;

};




참고

http://msdn.microsoft.com/ko-kr/library/ms235243.aspx

 

 

 



'C++/CLI' 카테고리의 다른 글

[Step. 13] parameter array  (1) 2010.09.10
[Step. 12] for each  (1) 2010.09.03
[Step. 10] 이벤트 ( event )  (0) 2010.08.20
[Step. 09] 델리게이트 (delegate)  (2) 2010.08.12
[Step. 08] 프로퍼티 ( property )  (8) 2010.08.06

Asynchronous Agents Library
– message block 9. ( custom )

작성자: 임준환( mumbi at daum dot net )

 

시작하는 글

 아마도 이 글이 Asynchronous Agents Library( 이하 AAL ) 에 관련된 마지막 글이라고 생각이 됩니다. 더 추가적으로 적을 내용이 생기면 더 적어 보도록 하겠습니다.

 이전 글까지 AAL 에서 제공하는 message block 들을 알아보았지만, 제공되는 것들이 마음에 들지 않는 분들은 직접 message block 을 만들어 사용할 수 있습니다.

 직접 message block 을 만들어 사용하는 것이 그리 간단하지만은 않기 때문에 이번 글이 좀 길어질 것 같습니다. 천천히 읽어주시기 바랍니다.

 

Message block 의 종류와 인터페이스

 우선 message block 정의에 앞서 message block 의 종류에 대해서 알아보는 것이 좋겠습니다. 이미 이전 글들에서 본 제공되는 message block 들을 보시면서 아셨겠지만, 한 번 더 짚고 넘어가도록 하겠습니다.

 Message 를 보내는 역할을 하는 message block 을 source block 이라 하고, message 를 받는 역할을 하는 역할을 하는 message block 을 target block 이라고 합니다.

 이 두 가지 종류의 block 들의 기본적인 행동들을 알리기 위해서 source block 들은 ISource 인터페이스를, target block 들은 ITarget 인터페이스를 상속받아 인터페이스 메소드들을 정의해야 합니다.

 

Message block 의 기본 클래스들

 위에서 언급한 ISource 인터페이스나 ITarget 인터페이스를 직접 상속받아 정의해도 되지만, AAL 에서는 그 인터페이스를 상속받아 정의하기 쉽도록 해주는 기본 클래스 3 가지를 제공합니다.

  • source_blockISource 인터페이스를 상속받고, 다른 block 에 message 를 보냅니다.
  • target_blockITarget 인터페이스를 상속받고, 다른 block 으로부터 message 를 받습니다.
  • propagator_blockISource 인터페이스와 ITarget 인터페이스를 상속받고, 다른 block 들과 message 를 보내고 받습니다.

 

 AAL 에서는 사용자 정의 message block 을 정의할 때, 인터페이스를 직접 상속받기 보다는 위의 기본 클래스들을 상속받아 정의하는 것을 권고하고 있습니다.

 

연결 관리 및 message 관리를 위한 템플릿 매개변수에 사용할 수 있는 클래스 템플릿

  위의 3가지 기본 클래스들은 클래스 템플릿으로, message block 들간의 연결을 어떻게 관리할 것인지, message 들을 어떻게 관리할 것인지에 대한 정보를 템플릿 매개변수를 통해 지정할 수 있습니다.

 AAL 은 연결 관리를 위한 2가지의  클래스 템플릿을 제공하고 있습니다.

  • single_link_registry – source 나 target 의 하나의 연결만 허용.
  • multi_link_registry – source 나 target 의 여럿의 연결을 허용.

 

 예를 들면, AAL 에서 제공하는 transformer 는 출력을 하나의 연결만 허용하는 single_link_registry 를 사용하고, 입력은 여럿의 연결을 허용하는 multi_link_registry 를 사용하고 있습니다.

template<class _Input, class _Output>
class transformer : public propagator_block<single_link_registry<ITarget<_Output>>, multi_link_registry<ISource<_Input>>>

[ 코드1. transformer 에서 사용되는 single_link_registry 와 multi_link_registry ]

 

 또한 message 관리를 위한 하나의 클래스 템플릿을 제공합니다.

  • ordered_mesage_processor – message 를 받는 순서대로 처리하도록 허용.

 

재정의해야 할 멤버 함수들

 위에 말한 기본 클래스들을 상속한 클래스는 다음과 같은 멤버 함수를 재정의해야 합니다.

 

source_block 을 상속한 클래스가 재정의해야 할 멤버 함수들

  • propagate_to_any_targets()
  • accept_message()
  • reserve_message()
  • consume_message()
  • release_message()
  • resume_propagation()

 

propagate_to_any_targets()

 입력 message 나 출력 message 를 비 동기 또는 동기적으로 처리하기 위해 런타임으로부터 호출됩니다.

 

accept_message()

 Message 를 수락하기 위해 target block 으로부터 호출됩니다.

 

reserve_message(), consume_message(), release_message(), resume_propagation()

 이 멤버 함수들은 message 를 예약하는 기능을 제공합니다.

 target block 은 message 를 제공받을 때와 message 를 나중에 사용하기 위해 예약해야 할 때 reserve_message() 를 호출합니다.

 target block 은 하나의 message 를 예약한 후에, message 를 사용하기 위해 consume_message() 를 호출하거나 예약을 취소하기 위해 release_message() 를 호출할 수 있습니다.

 consume_message() 는 accept_message() 와 함께 사용되어 message 의 소유권을 보내거나 message 의 복사본을 보내도록 할 수 있습니다.

 예를 들어, unbounded_buffer 와 같은 message block 은 오직 하나의 target 에만 message 를 보냅니다. 즉, message 의 소유권을 target 에 보냅니다. 그렇기 때문에 보낸 후, message block 내에는 보낸 message 가 남아있지 않습니다.

 반면에 overwrite_buffer 와 같은 message block 은 각 연결된 target 에 각각 message 를 제공합니다. 즉, message 의 복사본을 보냅니다, 그래서 보낸 후, message block 내에 보낸 message 가 여전히 존재하게 됩니다.

 target block 이 예약된 message 를 사용하거나 취소한 후에, 런타임은 resume_propagation() 을 호출합니다. resume_propagation() 은 큐( queue )의 다음 message 를 시작으로 message 의 전달을 계속해서 진행시킵니다.

 

target_block 을 상속한 클래스가 재정의해야 할 멤버 함수들

  • propagate_message()
  • send_message() ( option )

 

propagate_message(), send_message()

 런타임은 다른 block 으로부터 현재의 block 으로 message 를 비 동기적으로 받기 위해 propagate_message() 를 호출합니다.

send_message() 는 비 동기적이 아니라 동기적으로 message 를 보내는 것을 제외하면 propagate_message() 와 비슷합니다.

 기본적으로 send_message() 의 정의는 모든 입력 message 를 거부하도록 구현되어 있습니다.

 만약 message 가 target block 에 설정된 필터 함수를 통과하지 못하면 send_message() 나 propagate_message() 를 호출하지 않습니다.

 

propagator_block 을 상속한 클래스가 재정의해야 할 멤버 함수들

  propagator_blocksource_blocktarget_block 의 특징을 모두 상속하므로 두 클래스에 대해 재정의해야 할 멤버 함수들을 모두 재정의해야 합니다.

 

예제: priority_buffer

 priority_buffer 는 우선 순위를 기준으로 순서를 정하는 사용자 정의 message block 입니다. 여럿의 message 들을 받을 때 우선 순위대로 처리할 때 사용하기 유용합니다.

 Message 큐를 가지고 있고, source 와 target 의 역할을 하며, 여럿의 source 와 여럿의 target 을 연결할 수 있기 때문에 unbounded_buffer 와 비슷합니다.

 그러나 unbounded_buffer 는 message 를 받는 순서를 기반으로 message 를 전달한다는 것이 다릅니다.

 priority_buffer 는 우선 순위와 데이터를 같이 전달해야 하므로 우선 순위 타입과 데이터의 타입을 포함하는 tuple 타입의 message 를 전달합니다.

 priority_buffer 는 2개의 message 큐를 관리합니다. 입력 message 를 위한 std::priority_queue 와 출력 message 를 위한 std::queue 입니다.

 priority_buffer 의 정의를 위해 propagator_block 을 상속하고 7개의 멤버 함수들을 정의해야하고, link_target_notification() 과 send_message() 를 재정의해야 합니다.

 또한 2개의 public 멤버 함수인 enqueue() 와 dequeue() 를 정의합니다. private 멤버 함수로 propagate_priority_order() 를 정의합니다.

 

코드

#include <agents.h>
#include <queue>

// 우선 순위를 비교할 비교 함수 객체.
namespace std
{
	template< class Type, class PriorityType >
	struct less< Concurrency::message< tuple< PriorityType, Type > >* >
	{
		typedef Concurrency::message< tuple< PriorityType, Type > >		MessageType;

		bool operator()( const MessageType* left, const MessageType* right ) const
		{
			// message 가 tuple 이므로 get() 을 사용.
			return ( get< 0 >( left->payload ) < get< 0 >( right->payload ) );
		}
	};

	template< class Type, class PriorityType >
	struct greater< Concurrency::message< tuple< PriorityType, Type > >* >
	{
		typedef Concurrency::message< tuple< PriorityType, Type > >		MessageType;

		bool operator()( const MessageType* left, const MessageType* right ) const
		{
			return ( get< 0 >( left->payload ) > get< 0 >( right->payload ) );
		}
	};
}

namespace Concurrency
{
	// Type - 데이터 타입, PriorityType - 우선 순위 타입, PredicatorType - 비교 함수 객체
	// source 와 target 역할을 하므로 propagator_block 을 상속받고, 다중 입력 연결과 다중 출력 연결을 허용하므로 모두 multi_link_registry 를 사용.
	template< class Type, typename PriorityType = int, typename PredicatorType = std::less< message< std::tuple< PriorityType, Type > >* > >
	class priority_buffer
		: public propagator_block< multi_link_registry< ITarget< Type > >, multi_link_registry< ISource< std::tuple< PriorityType, Type > > > >
	{
	public:
		~priority_buffer()
		{
			// 연결을 모두 해제하기 위해 propagator_block 의 remove_network_links() 를 사용.
			this->remove_network_links();
		}

		priority_buffer()
		{
			// source 와 target 을 초기화 하기 위해 propagator_block 의 initialize_source_and_target() 을 사용.
			this->initialize_source_and_target();
		}

		priority_buffer( filter_method const& filter )
		{
			this->intialize_source_and_target();

			// 필터 함수를 등록하기 위해 target_block 의 register_filter() 를 사용.
			this->register_filter( filter );/
		}

		priority_buffer( Scheduler& scheduler )
		{
			this->initialize_source_and_target( &scheduler );
		}

		priority_buffer( Scheduler& scheduler, filter_method const& filter )
		{
			this->initialize_source_and_target( &scheduler );
			this->register_filter( filter );
		}

		priority_buffer( ScheduleGroup& schedule_group )
		{
			this->initialize_source_and_target( nullptr, &schedule_group );
		}

		priority_buffer( ScheduleGroup& schedule_group, filter_method const& filter )
		{
			this->initialize_source_and_target( nullptr, &schedule_group );
			this->register_filter( filter );
		}

		// 공개 멤버 함수들.
		bool enqueue( Type const& item )
		{
			return Concurrency::asend< Type >( this, item );
		}

		Type dequeue()
		{
			return Concurrency::receive< Type >( this );
		}

	protected:
		// message 처리하기 위해 런타임으로 부터 호출됨. message 를 입력 큐에서 출력 큐로 이동하고, 전달.
		virtual void propagate_to_any_targets( message< _Target_type >* )
		{
			message< _Source_type >* input_message = nullptr;

			{
				critical_section::scoped_lock lock( this->input_lock );

				// 보낼 message 를 우선 순위 큐에서 꺼냄.
				if( this->input_messages.size() > 0 )
				{
					input_message = this->input_messages.top();
					this->input_messages.pop();
				}
			}

			if( nullptr != input_message )
			{
				// 입력된 message 는 우선 순위와 데이터를 같이 가지고 있으므로 데이터만 가진 message 로 가공하여 출력 큐에 넣음.
				message< _Target_type >* output_message = new message< _Target_type >( get< 1 >( input_message->payload ) );
				this->output_messages.push( output_message );

				delete input_message;

				if( this->output_messages.front()->msg_id() != output_message->msg_id() )
				{
					return;
				}
			}

			// message 보내기.
			this->propagate_priority_order();
		}

		// target block 이 message 를 받기 위해 호출함. 이 코드에서는 출력 큐에서 message 를 제거해서 소유권을 이전.
		virtual message< _Target_type >* accept_message( runtime_object_identity msg_id )
		{
			message< _Target_type >* message = nullptr;

			if( !this->output_messages.empty() && this->output_messages.front()->msg_id() == msg_id )
			{
				message = this->output_messages.front();
				this->output_messages.pop();
			}

			return message;
		}

		// target block 이 제공된 message 를 예약하기 위해 호출. 이 코드에서는 전달 가능 여부를 확인.
		virtual bool reserve_message( runtime_object_identity msg_id )
		{
			return ( !this->output_messages.empty() && this->output_messages.front()->msg_id() == msg_id );
		}

		// target block 이 제공된 message 를 사용하기 위해 호출. 이 코드에서는 message 전달.
		virtual message< Type >* consume_message( runtime_object_identity msg_id )
		{
			return this->accept_message( msg_id );
		}

		// target block 이 예약된 message 를 취소하기 위해 호출. 이 코드에서는 아무 역할을 하지 않음.
		virtual void release_message( runtime_object_identity msg_id )
		{
			if( this->output_messages.empty() || this->output_messages.front()->msg_id() != msg_id )
			{
				throw message_not_found();
			}
		}

		// 예약된 message 처리 후, 계속해서 진행.
		virtual void resume_propagation()
		{
			if( this->output_messages.size() > 0 )
				this->async_send( nullptr );
		}

		// 새로운 target 이 연결되었음을 알림.
		virtual void link_target_notification( ITarget< _Target_type >* )
		{
			// 이미 예약된 message 가 있으면 전달하지 않음.
			if( this->_M_pReservedFor != nullptr )
				return;

			// message 보내기.
			this->propagate_priority_order();
		}

		// 비 동기적으로 전달. propagator_block 의 propagate() 에 의해 호출됨.
		virtual message_status propagate_message( message< _Source_type >* message, ISource< _Source_type >* source )
		{
			message = source->accept( message->msg_id(), this );

			if( nullptr != message )
			{
				{
					critical_section::scoped_lock lock( this->input_lock );
					this->input_messages.push( message );
				}

				this->async_send( nullptr );

				return accepted;
			}else
				return missed;
		}

		// 동기적으로 전달. propagator_block 의 send() 에 의해 호출됨.
		virtual message_status send_message( message< _Source_type >* message, ISource< _Source_type >* source )
		{
			message = source->accept( message->msg_id(), this );

			if( nullptr != message )
			{
				{
					critical_section::scoped_lock lock( this->input_lock );
					this->input_messages.push( message );
				}

				this->sync_send( nullptr );

				return accepted;
			}else
				return missed;
		}

	private:
		// message 를 보내는 함수.
		void propagate_priority_order()
		{
			// 이미 예약된 message 가 있으면 전달하지 않음.
			if( nullptr != this->_M_pReservedFor )
				return;

			// 출력 큐의 모든 message 를 보냄.
			while( !this->output_messages.empty() )
			{
				message< _Target_type >* message = this->output_messages.front();

				message_status status = declined;

				// 연결된 target 을 순회하면서 message 를 전달.
				for( target_iterator iter = this->_M_connectedTargets.begin();
					nullptr != *iter;
					++iter )
				{
					ITarget< _Target_type >* target = *iter;
					status = target->propagate( message, this );

					if( accepted == status )
						break;

					if( nullptr != this->_M_pReservedFor )
						break;		 
				}

				if( accepted != status )
					break;
			}
		}

	private:
		std::priority_queue<
			message< _Source_type >*,
			std::vector< message< _Source_type >* >,
			PredicatorType >							input_messages;		
		std::queue< message< _Target_type >* >			output_messages;
		critical_section								input_lock;

	private:
		priority_buffer const& operator=( priority_buffer const& );
		priority_buffer( priority_buffer const&  );
	};
}

[ 코드2. priority_buffer 구현 ]

 재정의하는 함수들이 많고, 어디서 어떻게 호출되는지 알기 어려울 수 있습니다. 간단하게 설명하자면 외부로부터 보내는 함수, send() 또는 asend() 에 의해서 message 가 전달될 경우, target block 의 역할을 하게 됩니다. 이 때는 propagate_message() 나 send_message() 중 하나가 호출됩니다.

 반대로 외부로부터 받는 함수, receive() 또는 try_receive() 에 의해서 message 를 전달해야 하는 경우, source block 의 역할을 하게 되고, 나머지 함수들이 호출되게 됩니다.

 이 예제로 완벽하게는 아니더라도 어떻게 사용자 정의 message block 을 구현해야 하는지 아셨을 것이라 생각됩니다.

 

사용 코드

#include <ppl.h>
#include <iostream>
#include "priority_buffer.h"

using namespace Concurrency;
using namespace std;

int main()
{
	priority_buffer< int > buffer;

	parallel_invoke(
		[ &buffer ] { for( unsigned int i = 0; i < 25; ++i ) asend( buffer, make_tuple( 1, 12 ) ); },
		[ &buffer ] { for( unsigned int i = 0; i < 25; ++i ) asend( buffer, make_tuple( 3, 36 ) ); },
		[ &buffer ] { for( unsigned int i = 0; i < 25; ++i ) asend( buffer, make_tuple( 2, 24 ) ); } );

	for( unsigned int i = 0; i < 75; ++i )
	{
		wcout << receive( buffer ) << L' ';
		if( ( i + 1 ) % 25 == 0 )
			wcout << endl;
	}
}

[ 코드3. priority_buffer 를 사용하는 예제 ]

 parallel_invoke() 를 사용하기 때문에 순서를 입력되는 순서를 보장할 수 없습니다. 하지만 receive() 를 사용하여 message 를 출력해 보면 우선 순위가 큰 순서대로 출력되는 것을 알 수 있습니다.


[ 그림1. priority_buffer 를 사용하는 예제 결과 ]

[ 그림1. priority_buffer 를 사용하는 예제 결과 ]


 

마치는 글

 사용자 정의 message block 을 구현하는 것을 마지막으로 AAL 에 관련된 글을 마무리합니다. AAL 에 관련해 새로운 소식이 있으면 그 때 다시 AAL 에 관련된 글을 작성하도록 하겠습니다.

 다음 글은 AAL 을 포함하는 Concurrency Runtime 에서 제공하는 작업 스케줄러에 대해서 작성해 볼 예정입니다. 작업 스케줄러는 AAL 의 message block 과 함께 유용하게 사용할 수 있으므로 AAL 에 관심이 많으신 분들도 기대하셔도 좋을 것 같습니다.

[Step. 10] 이벤트 ( event )

C++/CLI 2010. 8. 20. 09:00 Posted by 알 수 없는 사용자
이벤트는 그 이름처럼 이벤트를 처리할 때 사용하는 것으로 델리게이트와 조합하여 사용합니다.

보통 이벤트를 설명할 때 button 클래스를 예제로 사용하므로 저도 이것을 사용하여 간단하게 설명하겠습니다.^^

button 클래스의 여러 이벤트 중 clicked 이벤트를 구현할 때 이벤트로 불러질 클라이언트는 OnClick()이라는 멤버함수를 정의한 후 이것을 button 클래스의 clicked에 등록합니다.

 

delegate void ClickHandler();
ref class button {
public:
    event ClickHandler^ Clicked;
    button() {
        btnForTest = this;
    }
public:
    void someoneclicked() {
        Clicked();
    }
    static button^ btnForTest;
};

ref class Client {
private:
    button^ btn;
public:
    Client() {
        btn = gcnew button;
        btn->Clicked += gcnew ClickHandler(this, &Client::OnClick);
    }
private:
    void OnClick() {
        Console::WriteLine("someone clicked.");
    }
};
int main()
{
    Client^ client = gcnew Client;
   
button::btnForTest->someoneclicked();
    return 0;
}
< 출처 : http://cppcli.shacknet.nu/cli:event >
 
 
event는 몇 개의 제약이 있습니다. 
1. Clicked 호출은 button 밖에 할 수 없습니다. 
2. 클라이언트가 Clicked에 대한 조작은 += -=만 할 수 있으며 Clicked() Clicked = nullptr 같은 
것은 할 수 없습니다.
 
 
참고
http://cppcli.shacknet.nu/cli:event






Concurrency Runtime – 동기화 객체 2. ( event )

VC++ 10 Concurrency Runtime 2010. 8. 18. 08:30 Posted by 알 수 없는 사용자

Concurrency Runtime
– 동기화 객체 2. ( event )

작성자: 임준환( mumbi at daum dot net )

 

시작하는 글

 지난 글에 이어서 Concurrency Runtime 에서 제공하는 동기화 객체에 대해서 알아보도록 하겠습니다.

이번 글에서는 event 에 대해서 알아보겠습니다.

 

event

 event 는 어떤 상태를 나타낼 수 있는 동기화 객체입니다.

 event 는 어떤 공유 데이터에 대한 접근을 동기화하는 것이 아니라 실행의 흐름을 동기화합니다.

 어떤 작업이 완료되기를 기다렸다가 진행될 수 있고, 어떤 상태를 외부에 알릴 수도 있습니다.

 event 는 기본적으로 2가지 상태를 갖습니다. 설정된 상태( 시그널된 상태라고도 합니다. )와 설정되지 않은 상태를 가지고 있고, 플래그의 역할을 할 수 있습니다.

 event 는 자신의 상태가 설정될 때까지 기다리는 기능을 가지고 있어서 실행의 흐름을 동기화할 수 있습니다.

  Windows API 의 이벤트 객체와 유사합니다. 다른 점은 event 는 협조적이라는 것입니다. wait() 에 의해 대기하고 있을 때, 대기 중인 스레드 자원을 다른 작업에 사용하여 더욱 효율적인 스케쥴링을 하게 됩니다.

 

멤버 함수

 생성자와 소멸자를 제외한 public 인 멤버 함수들에 대해 알아보도록 하겠습니다.

 

void set()

 event 를 설정합니다.

 wait() 로 기다리던 event 는 계속해서 진행하게 됩니다.

 

void reset()

 event 를 설정하지 않습니다. 즉, 초기 상태로 되돌립니다.

 

size_t wait(unsigned int _Timeout = COOPERATIVE_TIMEOUT_INFINITE)

 event 가 설정될 때까지 기다립니다.

 매개변수인 _Timeout 은 기다리는 최대 시간을 지정할 수 있습니다. 기본 매개변수인  COOPERATIVE_TIMEOUT_INFINITE 는 무한대를 나타냅니다.

 event 가 설정되어 기다리는 것을 멈추고 계속 진행될 때, wait() 는 0 을 반환합니다. 반면에 지정한 최대 시간을 초과하여 기다리는 것을 멈추고 계속 진행될 때는 COOPERATIVE_WAIT_TIMEOUT( 0xffffffff ) 를 반환합니다.

 

static size_t __cdecl wait_for_multiple(event ** _PPEvents, size_t _Count, bool _FWaitAll, unsigned int _Timeout = COOPERATIVE_TIMEOUT_INFINITE);

 정적 멤버 함수로 여러 개의 event 를 기다립니다.

 매개 변수인 _PPEvents 는 event 의 포인터 배열입니다.

 매개 변수인 _Count 는 기다릴 event 의 개수입니다.

 매개 변수인 _FWaitAll 은 지정된 event 들이 모두 설정될 때까지 기다릴 것인지 여부입니다. false 를 지정하면 하나의 event 라도 설정되면 기다리는 것을 멈추고 계속 진행됩니다.

 마지막 매개 변수인 _Timeout 은 최대 시간입니다. 기본 매개 변수인 COOPERATIVE_TIMEOUT_INFINITE 는 무한대를 나타냅니다.

 매개 변수 중 _FWaitAll 을 false 로 지정하고, 하나의 event 가 설정되었을 때, 설정된 event 의 _PPEvents 로 지정된 배열의 인덱스가 반환됩니다.

 _FWaitAll 을 true 로 지정했을 경우에 모든 event 가 설정되었을 때에는 COOPERATIVE_WAIT_TIMEOUT 이 아닌 값이 반환됩니다.

 Windows API 의 이벤트 객체를 사용할 때 함께 사용하는 WaitForMultipleObject() 와 유사합니다.

 

예제

 Windows API 의 이벤트 객체와 어떻게 다른지 알아볼 수 있는 예제를 구현해보겠습니다.

 

시나리오

 우선 최대 2개의 작업이 동시에 수행될 수 있도록 설정합니다.

 그리고 하나의 event 를 생성한 후, 5개의 작업을 병렬로 처리합니다. 각 작업마다 생성한 event 가 설정될 때까지 기다리도록 합니다.

 메인 스레드에서 1 초 후에 그 event 를 설정합니다.

 같은 작업을 Windows API 의 이벤트 객체를 사용해서 구현합니다.

 

코드

// 코드의 출처는 msdn 입니다.
#include <windows.h>
#include <concrtrm.h>
#include <ppl.h>
#include <iostream>
#include <sstream>

using namespace Concurrency;
using namespace std;

// Demonstrates the usage of cooperative events.
void RunCooperativeEvents()
{
   // An event object.
   event e;

   // Create a task group and execute five tasks that wait for
   // the event to be set.
   task_group tasks;
   for (int i = 0; i < 5; ++i)
   {
      tasks.run([&] {
         // Print a message before waiting on the event.
         wstringstream ss;
         ss << L"\t\tContext " << GetExecutionContextId() 
            << L": waiting on an event." << endl; 
         wcout << ss.str();

         // Wait for the event to be set.
         e.wait();

         // Print a message after the event is set.
         ss = wstringstream();
         ss << L"\t\tContext " << GetExecutionContextId() 
            << L": received the event." << endl; 
         wcout << ss.str();
      });
   }

   // Wait a sufficient amount of time for all tasks to enter 
   // the waiting state.
   Sleep(1000L);

   // Set the event.

   wstringstream ss;
   ss << L"\tSetting the event." << endl; 
   wcout << ss.str();

   e.set();

   // Wait for all tasks to complete.
   tasks.wait();
}

// Demonstrates the usage of preemptive events.
void RunWindowsEvents()
{
   // A Windows event object.
   HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, TEXT("Windows Event"));

   // Create a task group and execute five tasks that wait for
   // the event to be set.
   task_group tasks;
   for (int i = 0; i < 5; ++i)
   {
      tasks.run([&] {
         // Print a message before waiting on the event.
         wstringstream ss;
         ss << L"\t\tContext " << GetExecutionContextId() 
            << L": waiting on an event." << endl; 
         wcout << ss.str();

         // Wait for the event to be set.
         WaitForSingleObject(hEvent, INFINITE);

         // Print a message after the event is set.
         ss = wstringstream();
         ss << L"\t\tContext " << GetExecutionContextId() 
            << L": received the event." << endl; 
         wcout << ss.str();
      });
   }

   // Wait a sufficient amount of time for all tasks to enter 
   // the waiting state.
   Sleep(1000L);

   // Set the event.

   wstringstream ss;
   ss << L"\tSetting the event." << endl; 
   wcout << ss.str();

   SetEvent(hEvent);

   // Wait for all tasks to complete.
   tasks.wait();

   // Close the event handle.
   CloseHandle(hEvent);
}

int wmain()
{
   // Create a scheduler policy that allows up to two 
   // simultaneous tasks.
   SchedulerPolicy policy(1, MaxConcurrency, 2);

   // Attach the policy to the current scheduler.
   CurrentScheduler::Create(policy);

   wcout << L"Cooperative event:" << endl;
   RunCooperativeEvents();

   wcout << L"Windows event:" << endl;
   RunWindowsEvents();
}

[ 코드1. event 와 Windows API 이벤트 객체와의 차이 ]

 event 의 경우 5 개의 작업 중 동시에 2개가 수행되도록 하여 2 개의 작업이 기다리고 있을 때, 기다리는 스레드 자원은 다른 작업을 하게 됩니다. 이것이 바로 협력적인 스케쥴링입니다.

 반면에 Windows API 의 이벤트 객체를 사용할 경우, 2 개의 작업이 다시 시작될 때까지 스레드 자원은 낭비를 하게 됩니다.

 

 

[ 그림1. event와 Windows API 이벤트 객체와의 차이 예제 결과 ]

[ 그림1. event와 Windows API 이벤트 객체와의 차이 예제 결과 ]

 

마치는 글

 Concurrency Runtime 에서 제공하는 동기화 객체인 event 까지 알아보았습니다. 이렇게 해서 제공하는 모든 동기화 객체를 알아보았습니다.

 동기화 객체도 알아보았으니 이제 사용자 정의 message block 을 구현할 준비가 다 된 것 같습니다.

 다음 글에서는 제공하는 message block 이 외에 사용자가 구현사여 사용하는 message block 에 대해서 알아보겠습니다.

Concurrency Runtime
– 동기화 객체 1. ( critical_section & reader_writer_lock )

작성자: 임준환( mumbi at daum dot net )

 

시작하는 글

 기본적으로 Asynchronous Agents Library( 이하 AAL ) 에서 제공하는 message block 들은 스레드에 안전합니다. 이 말은 내부적으로 동기화를 구현하고 있다는 말입니다.

 하지만 Concurrency Runtime 을 이용한 동시 프로그래밍을 할 때 AAL 의 message block 을 사용하지 않고 일반적인 사용자 변수들을 사용할 수 있습니다. 하지만 이런 변수들은 동기화가 되지 않아 직접 동기화를 해 주어야 합니다.

 이 때 Windows API 의 유저 모드 혹은 커널 모드의 동기화 객체를 사용할 수 있지만, Concurrency Runtime 에서 제공하는 동기화 객체를 쉽게 사용할 수 있습니다.

 이번 글에서는 Concurrency Runtime 에서 제공하는 동기화 객체들에 대해서 알아보도록 하겠습니다.

 

critical_section

 임계 영역을 의미하는 객체로, 어떤 영역을 잠그고 풀 수 있습니다. 잠긴 영역에 도달한 다른 스레드는 잠김이 풀릴 때까지 기다려야 합니다. 그러므로 해당 영역을 모두 사용한 후에는 다른 스레드도 사용할 수 있도록 풀어주어야 합니다.

 이 기능은 Windows API 에서 제공하는 유저 모드 동기화 객체인 CRITICAL_SECTION 과 같은 기능을 합니다.

 

멤버 함수

 생성자와 소멸자를 제외한 public 인 멤버 함수들에 대해서 알아보겠습니다.

 

void lock()

 해당 영역을 잠급니다. 잠근다는 의미는 영역의 접근 권한을 획득한다는 의미도 있습니다. 잠긴 영역에 도달한 다른 스레드들은 잠김이 풀릴 때까지 기다려야 합니다.

 

bool try_lock()

 해당 영역의 접근을 시도합니다. 이미 다른 스레드에 의해 잠겨있더라도 풀릴 때까지 기다리지 않고 반환합니다.

 이미 다른 스레드에 의해 잠겨있다면 false 를, 아니면 true 를 반환합니다.

 

void unlock()

 접근 영역의 접근 권한을 반환합니다. 즉, 잠금을 해제합니다. 이 함수가 호출된 후에 다른 스레드가 이 영역에 접근할 수 있습니다.

 

native_handle_type native_handle()

 native_handle_typecritical_section 의 참조입니다. 결국, 이 함수는 자기 자신의 참조를 반환합니다.

 

범위 잠금

 범위 잠금이란 코드 내의 블럭의 시작부터 블럭이 끝날 때까지 잠그는 것을 말합니다. 이것은 생성자에서 잠그고, 소멸자에서 잠금을 해제하는 원리( RAII – Resource Acquisition Is Initialization )로 구현되어 있으며, 굳이 unlock() 을 호출하지 않아도 된다는 편리함이 있습니다.

 

critical_section::scoped_lock

 critical_section 객체를 범위 잠금 객체로 사용합니다. 생성자로 critical_section 의 참조를 넘겨주어야 합니다. 스택에 할당되고 가까운 코드 블럭이 끝날 때, 소멸자가 호출되어 잠금이 해제됩니다.

 

reader_writer_lock

 reader_writer_lock 은 쓰기 접근 권한과 읽기 접근 권한을 구분하여 획득하고 잠글 수 있는 객체입니다.

한 스레드가 쓰기 접근 권한을 획득하여 잠긴다면, 다른 스레드의 모든 쓰기 / 읽기 접근 권한 획득시도가 실패하며 기다리게 됩니다.

 만약 한 스레드가 읽기 접근 권한을 획득하여 잠긴다면, 다른 스레드가 읽기 접근 권한 획득 시도 시, 동시에 읽기 접근 권한을 획득 가능합니다.

 위에 설명한 것처럼 2 가지 종류의 접근 권한이 있지만, 잠기는 것은 단 하나의 객체라는 것을 명심하셔야 합니다. 이 사실을 잊으면 헷갈릴 수 있습니다.

 reader_writer_lock 의 이와 같은 기능은 Windows API 의 SRW( Slim Reader Writer ) 잠금과 유사합니다. 다른 점이 있다면 reader_writer_lock 은 기본 잠금이 쓰기 잠금으로 되어 있고, 잠겼을 때 다른 스레드들이 기다린 순서대로 접근 권한을 얻는다는 것입니다. 즉, Windows SRW 는 기본 잠금이라는 개념이 없고, 기다린 스레드들 중 어떤 순서로 접근 권한을 얻는지 알 수 없습니다.

 

멤버 함수

 생성자와 소멸자를 제외한 public 인 멤버 함수들에 대해서 알아보겠습니다.

 

void lock()

 기본 잠금으로 쓰기 접근 권한을 획득하고 잠급니다. 이미 다른 스레드에서 접근 권한을 획득하여 잠겨 있다면 해제될 때까지 기다립니다.

 이 함수로 잠길 경우, lock() 과 읽기 접근 권한 획득 함수인 lock_read() 으로 접근 권한을 획득하려 하더라도 획득할 수 없고, 잠금이 해제될 때까지 기다리게 됩니다.

 

bool try_lock()

 쓰기 접근 권한 획득을 시도합니다. 이미 다른 스레드가 접근 권한을 획득하여 잠겼을 경우에는 false 를 반환하고, 접근 권한을 획득하게 되면 true 를 반환합니다. 즉, 대기하지 않습니다.

 

void lock_read()

 읽기 잠금으로 읽기 접근 권한 획득을 합니다. 다른 스레드에서 lock_read() 로 읽기 접근 권한 획득을 시도하면 이미 다른 스레드가 읽기 접근 권한을 획득하였다고 하더라도 동시에 읽기 접근 권한을 획득할 수 있습니다.

 다른 스레드에서 lock() 으로 쓰기 접근 권한 획득을 시도한다면, 이 스레드는 모든 읽기 권한으로 인한 잠금이 해제될 때까지 기다리게 됩니다.

 

bool try_lock_read()

 읽기 접근 권한 획득을 시도합니다. 이미 다른 접근 권한을 획득하여 잠겼을 경우에는 false 를 반환하고, 접근 궈한을 획득하게 되면 true 를 반환합니다. 즉, 대기하지 않습니다.

 

void unlock()

 어떤 잠금이든 해제합니다.

 

범위 잠금

 critical_section 과 마찬가지로 범위 잠금을 지원합니다.

 

reader_writer_lock::scoped_lock

 critical_section 과 같습니다. 다른 점이 있다면 이 잠금 객체는 쓰기 잠금의 범위 잠금 객체입니다.

 

reader_writer_lock:scoped_lock_read

 이 잠금 객체는 읽기 잠금의 범위 잠금 객체입니다.

 

마치는 글

 Concurrency Runtime 에서 제공하는 동기화 객체은 critical_sectionreader_writer_lock 에 대해서 알아보았습니다.

 reader_writer_lock 이 동시 읽기 접근이 가능하므로 critical_section 보다는 상황에 따라 성능이 좋을 수 있습니다.

 다음 글에서는 동기화 객체 중 마지막 하나, event 에 대해서 알아보도록 하겠습니다.

[Step. 09] 델리게이트 (delegate)

C++/CLI 2010. 8. 12. 09:30 Posted by 알 수 없는 사용자
델리게이트는 비관리코드에서는 함수 포인터와 같은 기능을 합니다.

 

#include <iostream>

delegate void TestDelegate();

ref class TEST

{

public:

void TestMethod()

{

  System::Console::WriteLine(“TEST::TestMethod()”);

}

};

 

int main()

{

TEST^ test = gcnew TEST();

TestDelegate^ testDelegate = gcnew TestDelegate( test, &TEST::TestMethod );

 testDelegate();


 getchar();

    return 0;

};

 

 

정적 멤버함수의 델리게이트

 

#include <iostream>

 delegate void TestDelegate();

 ref class TEST

{

public:

static void TestMethod()

{

  System::Console::WriteLine(“TEST::TestMethod()”);

}

};

 

int main()

{

TestDelegate^ testDelegate = gcnew TestDelegate( &TEST::TestMethod );

 

testDelegate();

 

getchar();

return 0;

};

 

 

복수 개의 델리게이트

 

델리게이트는 하나가 아닌 여러 개를 설정할 있습니다.

 

#include <iostream>

delegate void TestDelegate();

 

ref class TESTA

{

public:

void TestMethod()

{

  System::Console::WriteLine(“TESTA::TestMethod()”);

}

};

 

ref class TESTB

{

public:

static void TestMethod()

{

  System::Console::WriteLine(“TESTB::TestMethod()”);

}

};

 

ref class TESTC

{

public:

void operator()()

{

  System::Console::WriteLine(“TESTC::operator()”);

}

};

 

int main()

{

TestDelegate^ testDelegate;

TESTA testA;

TESTC testC;

 

testDelegate = gcnew TestDelegate( testA, &TESTA::TestMethod );

testDelegate += gcnew TestDelegate(&TESTB::TestMethod );

testDelegate += gcnew TestDelegate( testC, &TESTC::operator );

 

testDelegate();

 

getchar();

return 0;

};

 

 

델리게이트의 비교와 삭제

 ==으로 델리게이트를 비교하면 이것은 핸들의 비교가 아닌 델리게이트가 가지고 있는 함수를 비교하는 것입니다. -=을 사용하여 설정한 델리게이트를 제거할 수도 있습니다.

using namespace System;

delegate void MyDele(int);


void func1(int i)

{

    Console::WriteLine("func1");

}


void func2(int j)

{

    Console::WriteLine("func2");

}


int main()

{

    MyDele^ dele;

    dele += gcnew MyDele(&func1);

    dele += gcnew MyDele(&func2);

    MyDele^ dele2 = gcnew MyDele(func1);

    dele2 += gcnew MyDele(&func2);

    if ( dele == dele2 )  {

        Console::WriteLine("TRUE");

    } else {

        Console::WriteLine("FALSE");

    }

   

    dele -= gcnew MyDele(&func1); 

   

    dele(1); 

   

   return 0;

}

< 예제 출처 : http://cppcli.shacknet.nu/cli:delegate >

 

위의 예의 델리게이트들은 모두 void를 반환하고 파라미터가 없는 것인데 당연하듯이 반환 값이나 파라미터를 가질 수 있습니다.




델리게이트의 비동기 실행

 

델리게이트는 비동기 실행을 지원합니다. 비동기 실행은 처리를 요청한 후 종료를 기다리지 않은 호출한 곳으로 제어를 넘겨줍니다. 델리게이트 함수가 긴 시간을 필요로 하는 작업인 경우 비동기 실행을 이용하면 프로그램의 응답성을 높일 수 있습니다.

 

델리게이트의 비동기 실행은 스레드를 사용합니다. 이런 경우 비동기 실행을 할 때마다 스레드의 생성과 소멸에 부담을 느낄 수도 있지만 델리게이트는 닷넷의 기능을 잘 활용하여 스레드를 생성/삭제하지 않고 스레드 풀에 있는 스레드를 사용하므로 스레드 사용에 대한 부담이 작습니다.

 

비동기 실행을 할 때는 주의해야 할 점이 있습니다. 비동기 실행을 하는 경우 델리게이트에는 꼭 하나의 함수만 등록해야 합니다. 만약 2개 이상 등록하였다면 예외가 발생합니다.

 

비동기 실행은 BeginInvoke()를 사용하고, 만약 종료를 기다리고 싶다면 EndInvoke()를 사용합니다.

 

#include "stdafx.h"

#include <iostream>

 

using namespace System;

 

delegate void MyDele(void);

 

void myfunc(void)

{

    System::Threading::Thread::Sleep(3000);

}

 

 

int main(array<System::String ^> ^args)

{

     MyDele^ dele = gcnew MyDele(&myfunc);

            Console::WriteLine(L"1");

     IAsyncResult^ result = dele->BeginInvoke(nullptr,nullptr);

            Console::WriteLine(L"2");

    dele->EndInvoke(result);

            Console::WriteLine(L"3");

   

    getchar();

    return 0;

}

 

위 코드를 실행하면 '2'가 찍힌 이후 3초가 지난 이후에 3이 찍힙니다.





참고
http://cppcli.shacknet.nu/cli:delegate
http://cppcli.shacknet.nu/cli:delegate%E3%81%9D%E3%81%AE2



Asynchronous Agents Library – message block 8. ( timer )

VC++ 10 Concurrency Runtime 2010. 8. 12. 08:30 Posted by 알 수 없는 사용자

Asynchronous Agents Library
– message block 8. ( timer )

작성자: 임준환( mumbi at daum dot net )

 

시작하는 글

 이번 글에서 설명하는 timer 를 마지막으로 Asynchronous Agents Library( 이하 AAL ) 에서 제공하는 모든 message block 들에 대한 소개를 마칩니다.

  • unbouned_buffer
  • overwrite_buffer
  • single_assignment
  • call
  • transformer
  • choice
  • join
  • multitype_join
  • timer

   각각의 message block 들의 특징을 파악하고 필요한 때에 적합한 것들을 사용하시기 바랍니다.

 

timer< _Type >

 timer 는 message 의 전송을 연기하거나, 주기적으로 같은 message 를 전송하는 역할을 합니다.

 안타깝게도 하나의 timer 는 같은 message 밖에 전송할 수 없게 구현되어 있습니다. 또한 하나의 message block 에만 연결하여 전송할 수 있습니다.

 timercall 또는 transformer 를 연결한다면 좀 더 유연하게 확장할 수 있을 것입니다.

 

상태

 timer 는 총 4가지의 상태를 갖습니다.

  • Initialized – 생성된 후, 아직 시작하지 않은 상태.
  • Started – 시작된 상태.
  • Paused – 일시 정지된 상태.
  • Stopped – 정지한 상태.

 

멤버 함수

 생성자 및 public 인 멤버 함수들 중 인터페이스를 재정의한 함수들을 제외하고 알아보도록 하겠습니다.

 

생성자

timer(unsigned int _Ms, _Type const& _Value, ITarget<_Type> *_PTarget = NULL, bool _Repeating = false)

[ 코드1. timer 의 생성자 ]

 첫 번째 매개변수인 _Ms 는 연기되는 시간 또는 주기적인 시간으로 밀리 초를 받습니다.

 두 번째 매개변수인 _Value 는 전송할 message 입니다.

 세 번째 매개변수인 _PTarget 은 전송한 message 를 받을 대상 message block 입니다. 기본 값이 NULL 인데 NULL 인 경우에는 생성된 이후에 link_target() 을 이용하여 연결할 수 있습니다.

 여기서 주의해야 할 점은 timer 는 단 하나의 message block 만을 연결할 수 있다는 것입니다. link_target() 으로 여러 message block 들을 연결할 경우, 런타임 에러를 보게 될 것입니다.

 네 번째 매개변수인 message 의 전송을 연기 또는 주기적으로 전송하는 것을 선택합니다. true 일 경우 주기적으로 전송하며, false 는 연기 후, 단 한번만 전송됩니다.

 

void start()

 start() 는 timer 를 시작하는 함수로 생성된 후인 Initialized 또는 일시 정지된 Paused 상태에서만 동작합니다. 그리고 Started 상태가 됩니다.

 

void stop()

 stop() 은 timer 를 정지하는 함수입니다. 상태가 Stopped 가 되고, 해당 timer 를 다시 사용할 수 없습니다.

 

void pause()

 pause() 는 timer 를 일시 정지하는 함수입니다. 만약 주기적인 message 전송이 아니라 message 의 전송을 연기하도록 생성했다면, stop() 을 호출합니다. 그렇지 않을 경우, 내부 타이머를 멈추고, 상태를 Paused 로 변경합니다.

 

예제

 timer 의 특징을 알아보기 쉽도록 예제를 구현해보겠습니다.

 

시나리오

 채팅 중에 관리자에 의해서 사용자에게 전송되는 공지 사항을 시뮬레이션 해보도록 하겠습니다.

 

코드

#include <iostream>
#include <string>
#include <array>
#include <agents.h>

using namespace std;
using namespace Concurrency;

// 채팅 메시지 구조체
struct Message
{
	wstring		id;
	wstring		message;
};

// 채팅 메시지를 입력받는 agent.
class Typer
	: public agent
{
public:
	Typer( ITarget< Message >& buffer, const wstring id )
		: buffer( buffer )
		, isAvailable( true )
		, id( id ) { }

protected:
	void run()
	{
		while( this->isAvailable )
		{
			Message sendingMessage = { this->id };

			getline( wcin, sendingMessage.message );			

			send( this->buffer, sendingMessage );
		}		

		this->done();
	}

private:
	ITarget< Message >&		buffer;
	bool					isAvailable;
	wstring					id;
};

// 전달받은 메시지를 화면에 출력하는 agent.
class MessageDisplayer
	: public agent
{
public:
	MessageDisplayer( ISource< Message >& source )
		: source( source ) { }

protected:
	void run()
	{
		while( true )
		{
			Message receivedMessage = receive( this->source );

			wcout << L"[" << receivedMessage.id << L"]: " << receivedMessage.message << endl;
		}

		this->done();
	}

private:
	ISource< Message >&		source;
};

int main()
{
	// 시스템 로케일 정보로 설정.( 한글 깨짐 방지 )
	setlocale(LC_ALL, "");

	// 전달된 메시지를 보관하는 버퍼.
	unbounded_buffer< Message > messageBuffer;
	
	// 공지로 전달할 메시지.
	Message noticeMessage = { L"공지", L"2010년 8월 28일에 Visual Studio Camp 가 열립니다." };

	// 5초에 한 번씩 주기적으로 메시지를 전달할 timer
	timer< Message > notifier( 5000, noticeMessage, &messageBuffer, true );

	// timer 시작.
	notifier.start();

	// agent 들. 
	Typer typer( messageBuffer, L"MuMbi" );
	MessageDisplayer displayer( messageBuffer );

	array< agent*, 2 > agents = { &typer, &displayer };

	// agent 들 작업 시작.
	for_each( agents.begin(), agents.end(), []( agent* pAgent )
	{
		pAgent->start();
	} ) ;

	// agent 들 작업 종료까지 대기.
	agent::wait_for_all( agents.size(), &*agents.begin() );
}

[ 코드2. timer 를 이용한 주기적인 message 전송 예제 ]

 Typer 클래스는 사용자의 입력을 메시지 버퍼로 전송하는 역할을 하고, MessageDisplayer 클래스는 전송된 메시지들을 화면에 출력해주는 역할을 합니다.

 timer 는 5초마다 공지 사항을 메시지 버퍼로 전달합니다.

 결국 MessageDisplayer 클래스는 사용자의 메시지와 주기적으로 전달되는 메시지를 모두 화면에 출력하게 됩니다.

 

[ 그림1. timer 를 이용한 주기적인 message 전송 예제 결과 ]

[ 그림1. timer 를 이용한 주기적인 message 전송 예제 결과 ]

 

마치는 글

 이번 글을 마지막으로 AAL 에서 제공하는 모든 message block 들에 대해서 알아보았습니다.

 제공되는 message block 들은 각각 모두 사용할 상황이 다른 특징을 가지고 있고, 굉장히 유용합니다. 하지만 약간의 부족한 점들을 느끼고, 아쉬운 점이 보입니다.

 그런 점들을 보완하여 우리가 원하는 message block 을 만들 수 있습니다. 그렇게 만들어진 message block 은 스레드에 안전함을 보장해주어야 합니다. 

 다음글에서는 스레드 안전 보장을 위한 동기화 데이터 구조에 대해서 알아보도록 하겠습니다.

Asynchronous Agents Library
– message block 7. ( join & multitype_join )

작성자: 임준환( mumbi at daum dot net )

 

시작하는 글

 이 글에서는 동기화 메커니즘으로 사용할 수 있는 joinmultitype_join 에 대해서 알아보겠습니다. join 은 이전 글에서 소개한 choice 와 함께 동기화 메커니즘으로 사용할 수 있습니다.

 

join< _Type, _JType >

 join 은 연결된 모든 message block 에서 message 를 받아올 때까지 기다리는 역할을 합니다. join 의 이 역할을 수행하기 위해서는 전달 함수인 receive() 와 함께 사용해야 합니다.

 템플릿 매개변수인 _Type 은 message 의 타입입니다. 연결된 message block 들이 처리하는 message 타입으로 같은 타입이어야 합니다.

 템플릿 매개변수인 _JType 은 join 의 타입입니다. join 은 2가지 종류가 있습니다. 하나는 greedy 이고, 다른 하나는 non_greedy 입니다.

 _JType 에는 greedy 또는 non_greedy 를 사용할 수 있으며, 기본 템플릿 인자로 non_greedy 가 지정되어 있습니다.

 join 타입은 다음과 같은 enum 형으로 정의되어 있습니다.

enum join_type { 
    greedy = 0,
    non_greedy = 1 
};

[ 코드1. join 타입 ]

 두 타입의 join 모두 연결된 message block 들에서 message 를 받아올 때까지 기다리는 기능은 같습니다. 그렇다면 다른 점에 대해서 알아보겠습니다.

 기본 인자로 지정된 non_greedy 타입은 연결된 message block 들 중 어떤 message block 이 먼저 message 를 받아올 수 있는 상태가 되더라도 연결된 모든 message block 들이 모두 message 를 받아올 수 있는 상태가 될 때까지 message block 에 받아올 수 있는 message 들을 유지합니다. 연결된 모든 message block 들에서 message 를 받아올 수 있을 때가 되면 한꺼번에 message 들을 받아옵니다.

 반면 greedy 타입은 연결된 message block 들 중 어떤 message block 이 먼저 message 를 받아올 수 있는 상태가 되면 그 message 를 미리 받아옵니다. 이 때 이 message block 이 unbounded_buffer 라면 이로 인해 join 이 받아간 message 가 제거됩니다.

 

멤버 함수

 생성자와 소멸자를 제외한 public 인 멤버 함수가 없습니다. 하지만 ISource 인터페이스를 재정의한 link_target() 을 사용할 수 있으며, 이 함수는 join 과 다른 message block 을 연결하는데 사용하게 됩니다.

 가장 기본적인 생성자의 매개변수는 연결할 message block 의 개수입니다. 이 개수보다 실제로 연결된 message block 의 개수가 많으면 런타임에 에러를 발생하고, 적으면 교착상태에 빠지게 됩니다. 생성자의 매개변수로 지정된 message block 의 개수가 연결되어 message 를 받을 때까지 대기하기 때문입니다.

 

특징

 message block 이 join 을 연결할 경우 몇 가지의 특징들이 있습니다.

  • 하나의 message block 이 여러 개의 join 에 연결될 경우, 먼저 연결된 join 에 message 를 보냅니다.
  • 연결된 join 들 중 greedy join 이 있을 경우 greedy join 에게 먼저 message 를 보냅니다.
  • 연결된 join 들 중 greedy join 이 여러 개일 경우, 먼저 연결된 greedy join 에 message 를 보냅니다.

 

 또한 어떤 mesage block 들은 join 과 함께 사용할 수 없습니다.

  • single_assignment 는 단 한번만 send() 가 적용되고 이 후의 send() 는 무시되기 때문에 joinsingle_assignment 으로부터 message 를 한번만 받을 수 있다. 두 번 이상은 받을 수 없기 때문에 무한대기하게 되어 교착상태에 빠진다.
  • transformerjoin 에 연결한 후, 연결한 transformer 로 부터 message 를 받아가면 에러를 발생한다.

 

multitype_join< _TupleType, _JType >

 multitype_joinjoin 과 같은 기능을 하지만 다른 종류의 message 들을 받을 수 있습니다. 그래서 템플릿 매개변수가 message 타입이 아닌 tuple 타입을 받습니다. tuple 에 사용할 수 있는 타입은 choice 에서 사용하는 tuple 의 특징과 같습니다.

 multitype_join 의 타입은 join 과 마찬가지로 greedynon_greedy 를 사용할 수 있습니다.

 

멤버 함수

생성자와 소멸자를 제외한 public 인 멤버 함수는 없고, 인터페이스를 재정의한 멤버 함수들이 있습니다.

 

헬퍼 함수

 tuple 을 사용하는 choice 처럼 multitype_join 도 tuple 을 사용하므로 헬퍼 함수 없이는 굉장히 길고 복잡한 선언이 필요합니다.

그래서 다음과 같은 헬퍼 함수를 제공합니다.

unbounded_buffer< int > i;
unbounded_buffer< float > f;
unbounded_buffer< bool > b;

auto j = make_join( &i, &f, &b );

[ 코드2. 헬퍼 함수 make_join ]

 

예제

 join 의 특징을 살펴볼 수 있는 간략한 예제를 보겠습니다.

 

시나리오

 보통 온라인 게임에서 게임을 시작하게 되면 연결된 모든 클라이언트가 모두 로딩이 완료된 후 동시에 게임을 시작하게 됩니다. 그것을 join 을 이용해서 시뮬레이션 해보겠습니다.

 

코드

#include <iostream>
#include <array>
#include <random>
#include <agents.h>

using namespace std;
using namespace Concurrency;

const unsigned int CLIENT_COUNT = 10;

class Loader
	: public agent
{
public:
	~Loader()
	{
		this->isComplete.unlink_targets();
	}

	Loader( unsigned long id, join< bool >& synchronizer )
		: random_generator( id )
	{
		this->isComplete.link_target( &synchronizer );
	}

protected:
	void run()
	{
		this->Loading();

		send( this->isComplete, true );
		wcout << L"Load is complete." << endl;
		
		this->done();
	}

private:
	void Loading()
	{
		unsigned int loadingTime = random_generator() % 3000 + 1000;
		Concurrency::wait( loadingTime );
		wcout << L"Loading time: " << loadingTime / 1000.0f << L" sec..";
	}

private:	
	single_assignment< bool >	isComplete;
	mt19937						random_generator;
};

int main()
{	
	join< bool > synchronizer( CLIENT_COUNT );
	
	array< Loader, CLIENT_COUNT > loaderArray = {
		Loader( 1, synchronizer ),
		Loader( 2, synchronizer ),
		Loader( 3, synchronizer ),
		Loader( 4, synchronizer ),
		Loader( 5, synchronizer ),
		Loader( 6, synchronizer ),
		Loader( 7, synchronizer ),
		Loader( 8, synchronizer ),
		Loader( 9, synchronizer ),
		Loader( 10, synchronizer ),
	};

	for_each( loaderArray.begin(), loaderArray.end(), []( Loader& loader ) { loader.start(); } );

	receive( synchronizer );

	wcout << L"Game Start!" << endl;

	for_each( loaderArray.begin(), loaderArray.end(), []( Loader& loader ) { agent::wait( &loader ); } );
}

[ 코드3. join 을 이용한 동기화 예제 ]

 10개의 클라이언트 역할을 하는 agent가 있고, 각 클라이언트마다 로딩 속도가 다른 것을 시뮬레이션 하기 위해 난수를 사용하여 로딩 시간을 결정하였습니다.

 각 agent 는 완료를 나타내는 single_assignment 를 하나의 join 에 연결하여 로딩이 모두 완료될 때까지 기다립니다.

  join 에서 message 를 받는다는 것이 동기화가 완료된 것이므로 바로 게임을 시작합니다.

 

[ 그림1. join 을 이용한 동기화 예제 ]

[ 그림1. join 을 이용한 동기화 예제 ]

 

마치는 글

 이번 글에서 joinmultitype_join 에 대해서 알아보았습니다.

 choice 와 비교해서 choice 는 연결된 message block 들 중 먼저 들어온 message 가 있으면 바로 message 를 받을 수 있던 반면에 join 은 연결된 message block 들로부터 message 를 모두 받을 때까지 대기합니다.

 이 message block 들, choicejoin, multitype_join 은 Win32 API 의 WaitForMultipleObjects() 와 비슷한 기능을 한다는 것을 알 수 있습니다.

 이 message block 들의 특징은 외부 이벤트 객체가 아닌 데이터 자체가 이벤트로 바인딩되어 처리된다는 점입니다.

결론적으로 이 message block 들을 이용해 동기화를 구현할 수 있음을 알 수 있습니다.

[Step. 08] 프로퍼티 ( property )

C++/CLI 2010. 8. 6. 09:30 Posted by 알 수 없는 사용자

보통 비관리 클래스를 정의할 때 캡슐화를 위해서 멤버 변수는 최대한 private 접근으로 한 후 외부에서의 접근을 위해서 get set 멤버 함수를 정의합니다.

 

class Character

{

public:

…….

   void SetCharCd( const int nCharCd ) { m_nCharCd = nCharCd; }

   int GetCharCd() { return m_nCharCd; }

…….

private:

   int m_nCharCd;

   …….

};

 


클래스의 멤버를 하나 정의할 때마다 그에 대응하는 get, set 멤버 함수를 정의하는 것은 좀 귀찮은 일이기도 합니다. 그래서 관리코드에서는 이 작업을 쉽게 해주는 property가 생겼습니다.

 

ref class Character

{

public:

…….

    property int CharCd

{

   void set( int nCharCd ) { m_nCharCd = nCharCd; }

   int get() { return m_nCharCd; }

}  

…….

private:

   int m_nCharCd;

   …….

};

 

위 코드에서는 get set을 둘 다 정의 했는데 둘 중 하나만 정의 해도 괜찮습니다.

또 위의 set은 아주 간단하게 그냥 대입만 하고 있는데 좀 더 로직을 넣을 수도 있습니다.

 

ref class Character

{

public:

…….

    property int CharCd

{

   void set( int nCharCd )

{

  if( nCharCd < 0 ) {

     m_nCharCd = 0;

  } else {

     m_nCharCd = nCharCd;

  }

}

 

   int get() { return m_nCharCd; }

}  

…….

private:

   int m_nCharCd;

   …….

};

 

 


property 선언과 정의 나누기


일반적으로 클래스의 멤버 선언은 헤더 파일에 정의는 cpp 파일에 하듯이 property도 선언과 정의를 나눌 수 있습니다.

 

ref class Character

{

public:

…….

    property int CharCd

{

   void set( int nCharCd );

   int get();

}  

…….

private:

   int m_nCharCd;

   …….

};

 

 

void Character::CharCd::set( int nCharCd )

{

if( nCharCd < 0 ) {

m_nCharCd = 0;

} else {

   m_nCharCd = nCharCd;

  }

}

 

void Character::CharCd::get()

{

return m_nCharCd;

}

 



 

get set의 접근 지정자 다르게 하기

 

위에서 property를 정의할 때 get set은 모두 public 였습니다.

이것을 각각 다르게 접근 지정자를 정할 수 있습니다.

 

ref class Character

{

public:

…….

    property int CharCd

{

protected:

   void set( int nCharCd )

{

  if( nCharCd < 0 ) {

     m_nCharCd = 0;

  } else {

     m_nCharCd = nCharCd;

  }

}

 

    public:

   int get() { return m_nCharCd; }

}  

…….

private:

   int m_nCharCd;

   …….

};

 

 

 

 

참고

http://cppcli.shacknet.nu/cli:property

http://vene.wankuma.com/prog/CppCli_Property.aspx

 

 

Asynchronous Agents Library
– message block 6. ( choice )

작성자: 임준환( mumbi at daum dot net )

 

시작하는 글

 unbounded_buffer, overwrite_buffer 그리고  single_assignment 와 같이 message 를 보관하는 버퍼 기능의 message block 들과, call, transformer 와 같이 지정된 함수형 객체를 수행하는 기능의 message block 들에 대해서 알아보았습니다.

 이번 글에서는 message block 들의 상태를 기반으로 동작하는 message block 들 중 choice 에 대해서 알아보도록 하겠습니다.

 

choice< _TupleType >

 choice 의 특징은 지정된 message block 들 중 가장 먼저 messge 를 가져올 수 있는 message block 을 가리키는 기능입니다. 다시 설명하면 어떤 message 를 받을 준비가 된 message block 들을 choice 에 지정한 후, 지정된 message block 들 중 가장 먼저 message 를 받은 message block 을 choice 가 가리키게 됩니다.

 

tuple 의 조건

 choice 은 message 를 받을 준비가 된 message block 들을 지정할 때 tr1 의 tuple 을 사용합니다. 이 글의 주제가 tr1 이 아닌 만큼 tuple 에 대한 자세한 설명은 생략하겠습니다. 간략히 설명하면 std::pair 의 일반화된 객체라고 생각하시면 됩니다. std::pair 가 2개의 타입을 저장할 수 있다면 tuple 은 10개의 여러 가지 타입을 저장할 수 있습니다.

 그래서 다른 message block 과 달리 템플릿 매개변수가 message 타입이 아닌 여러 message block 들의 타입을 저장할 수 있는 tuple 의 타입입니다.

 하지만 아무 tuple 이나 사용할 수 있는 것이 아닙니다. 약간의 조건을 충족시키는 tuple 이어야만 합니다.

  • tuple 의 구성 요소로 source_type 이라는 typedef 를 가지고 있는 message block 들이어야 한다. ( message block 중 call 은 source_type 이 정의되어 있지 않기 때문에 사용할 수 없습니다. )
  • message block 들은 복사 생성자와 배정 연산자( = ) 의 사용을 금지( private )하고 있으므로 tuple 의 구성 요소로 포인터 타입을 사용해야 한다.

 

특징

 choice 는 여러 message block 들 중 가장 먼저 message 를 받은 message block 을 가리킵니다. 그 말은 choice 는 tuple 로 지정된 message block 들의 index 를 가지고 있다는 말입니다.

 그런데 그 index 를 저장하기 위한 변수로 내부적으로 single_assignment 를 사용합니다. 그 뜻은 single_assignment 는 단 한 번만 값을 저장할 수 있기 때문에 choice 는 가리키게 된 message block 의 index 를 변경할 수 없습니다. 즉, 일회성이라는 말이 됩니다.

 receive() 로 choice 의 message 를 받아오면 지정된 message block 들 중 하나라도 message 를 받을 때까지 기다리다가 message 를 받은 message block 이 생기면 그 message block 의 index 를 반환합니다.

 위의 특징을 응용하면 여러 message block 들 중 하나라도 message 를 받을 때까지 대기하는 기능으로 사용할 수 있습니다.

 

멤버 함수

 생성자와 소멸자를 제외한 public 인 멤버 함수 중 인터페이스로 재정의된 함수들을 제외한 함수들입니다.

인터페이스를 재정의한 멤버 함수들은 이전 글인 2010/07/10 - [Language Development/VC++ 10 Concurrency Runtime] - Asynchronous Agents Library – message block 1. ( 인터페이스 ) 를 참고하시기 바랍니다. 

 

bool has_value() const

 현재 message block 의 index 를 가지고 있는지 반환합니다. 내부적으로 single_assignmenthas_value() 를 호출합니다.

 

size_t index()

 가지고 있는 message block 의 index 를 반환합니다. 내부적으로 single_assignmentvalue() 를 호출하고 하고, 그 value() 는 receive() 를 사용하기 때문에 아직 index 를 가지고 있지 않다면 가질 때까지 대기하게 됩니다.

 

template <typename _Payload_type>
_Payload_type const & value()

 choice 가 가리키고 있는 index 의 message block 의 message 의 값을 반환합니다. message 의 타입을 명시적으로 호출해야 하기 때문에 사용하기 불편합니다.

 

헬퍼 함수

 choice 는 tuple 을 사용하기 때문에 선언이 복잡할 수 밖에 없습니다. 그래서 조금 더 쉽게 선언을 할 수 있도록 헬퍼 함수를 제공합니다.

unbounded_buffer< int > i;
unbounded_buffer< float > f;
unbounded_buffer< bool > b;

tuple< unbounded_buffer< int >*, unbounded_buffer< float >*, unbounded_buffer< bool >* > tp( &i, &f, &b );
choice< tuple< unbounded_buffer< int >*, unbounded_buffer< float >*, unbounded_buffer< bool >* > > ch( tp );

[ 코드1. 헬퍼 함수를 사용하지 않은 choice 선언 ]

 헬퍼 함수를 사용하지 않으면 템플릿 매개변수인 타입들을 모두 적어야 하기 때문에 굉장히 길어집니다. 특히나 message block 들의 타입이 굉장히 길기 때문에 더욱 불편합니다. typedef 를 사용하더라도 길기는 마찬가지 입니다.

 

unbounded_buffer< int > i;
unbounded_buffer< float > f;
unbounded_buffer< bool > b;

auto ch = make_choice( &i, &f, &b );

[ 코드2. 헬퍼 함수를 사용한 choice 선언 ]

 헬퍼 함수와 Visual studio 2010 에서 지원하는 C++0x 문법인 auto 를 사용하여 더욱 더 짧게 선언할 수 있습니다.

 

예제

 choice 의 특징을 쉽게 알아볼 수 있는 예제를 구현해보겠습니다.

 

시나리오

 두 agent 를 사용해서 어떤 agent 가 더 빨리 수행되는가를 육상에 비유하여 표현해보았습니다. choice 객체는 두 agent 중 빨리 수행한 쪽을 판별하는 역할을 합니다.

 

코드

#include <iostream>
#include <string>
#include <agents.h>

using namespace std;
using namespace Concurrency;

class Runner
	: public agent
{
public:
	Runner( ITarget< bool >& target, const wstring& name )
		: target( target )
		, name( name ) { }

	const wstring& GetName() const
	{
		return this->name;
	}

protected:
	void run()
	{
		for( unsigned int i = 0; i < 1000; ++i )
		{
			if( 0 == i % 100 )
				wcout << this->name << L" - " << i << L"m." << endl;
		}

		wcout << this->name << L" - " << L"Finished!" << endl;

		asend( target, true );

		this->done();
	}

private:
	ITarget< bool >&	target;
	wstring				name;
};

int main()
{
	single_assignment< bool > isFinished[2];

	auto winnerRecoder = make_choice( &isFinished[0], &isFinished[1] );

	Runner runner[2] = { Runner( isFinished[0], L"Carl Lewis" ), Runner( isFinished[1], L"Usain Bolt" ) };
	runner[0].start();
	runner[1].start();

	agent* agents[2] = { &runner[0], &runner[1] };
	agent::wait_for_all( 2, agents );

	wcout << runner[ receive( winnerRecoder ) ].GetName() << L" is winner!" << endl;
}

[ 코드3. choice 를 사용한 먼저 수행된 agent 판별 예제 ]

 두 agent 에 육상 선수인 칼루이스와 우사인 볼트의 이름을 붙여보았습니다. 각 agent 는 수행이 완료되면 완료 message 를 single_assignment 에 보내고, choice 객체는 완료 message 가 먼저 도착한 message block 의 index 를 갖게 됩니다.

 그 index 를 이용해 해당 agent 의 육상 선수 이름을 출력합니다.

 이 예제의 결과는 스케쥴링에 따라 다르기 때문에 실행할 때마다 결과가 다를 수 있습니다.

 

[ 그림1. choice 를 사용한 먼저 수행된 agent 판별 예제 ]

[ 그림1. choice 를 사용한 먼저 수행된 agent 판별 예제 ]

 

마치는 글

 이번에는 tuple 을 이용하는 choice 에 대해서 알아보았습니다. Visual studio 2010 에서 지원하는 C++0x 문법을 사용하면 좀 더 쉽게 사용할 수 있습니다.

다음 글 또한 새로운 message block 에 대해서 알아보도록 하겠습니다.

Asynchronous Agents Library
– message block 5. ( transformer )

작성자: 임준환( mumbi at daum dot net )

 

시작하는 글

 이번 글에서는 transformer 라는 message block 에 대해서 알아봅니다.

 transformer 는 지난 글에서 알아본 call 과 유사하나 좀 더 유연합니다. call 과 비교해서 보시고, calltransformer 를 언제 사용해야 할지 구분해 보시기 바랍니다.

 

transformer< _Input, _Output >

 transformercall 과 마찬가지로 지정된 함수, 함수 객체를 수행하는 역할을 합니다. 하지만 call 과는 확실히 다릅니다. 이름에서도 알 수 있듯이 message 를 변환하는 기능을 가지고 있습니다. 이 기능이 call 과 어떻게 다른지 알아보겠습니다.

 

특징

 transformer 의 특징 중 첫째는 결과를 반환한다는 것입니다. 변환하는 기능을 가지고 있기 때문에 변환된 값을 내보내 주어야 하기 때문입니다. 템플릿 매개변수를 보고 이미 눈치채신 분도 있겠지만, transformer 를 선언할 때 입력되는 매개변수의 타입과 반환 타입을 명시해주어야 합니다.

 이로 인해 내부적으로 call 과 다른 점은 반환 값을 보관하는 큐( queue ) 를 포함하고 있습니다. call 은 작업들을 순차적으로 처리하기 위한 작업 큐를 가지고 있고, transformer 는 작업 큐 뿐만 아니라 반환 값을 보관하는 큐도 가지고 있습니다.

 반환 값을 보관하는 큐의 값을 꺼내오기 위해서 receive() 나 try_receive() 를 사용할 수 있습니다. 이 특징은 message 들을 전달, 보관하는 unbounded_buffer 와 비슷합니다. 하지만 unbounded_buffer 처럼 사용하시면 안됩니다.

 그 이유는 transformer 는 두 가지 기능, 변환 함수를 수행하는 기능과 변환된 값을 내보내는 기능을 가지고 있는데 이 둘을 동시에 처리할 수 없기 때문입니다. 변환 함수를 수행하는 중인 transformer 에서 변환된 값( 반환 값 )을 얻어내려 시도해도 변환 함수를 수행 중일 때에는 변환된 값을 내어주지 않아 원하는 시기에 값을 얻어낼 수 없습니다.

 원하는 시기에 값을 제대로 얻기 위해서는 변환된 값을 unbounded_bufferoverwrite_buffer 등과 같이 message 를 보관할 수 있는 message block 에 전달한 후, 그 message block 에서 값을 얻어와야 합니다.

 두 번째 특징은 다른 message block 들과 연결하여 변환된 반환 값을 스스로 전달할 수 있다는 것입니다. ISource 인터페이스의 link_target() 을 사용하는데, 이 기능을 사용하여 절차적인 작업들을 잘게 나누어 순차적으로 처리할 수 있습니다. 이러한 메커니즘을 파이프라인( pipeline ) 이라고 합니다.

 일반적인 C++ 프로그램에서도 파이프라인 개념은 이미 많이 사용되고 있습니다.

 FunctionA( FunctionB( FunctionC() ) ) 와 같이 진행되는 처리도 파이프라인으로 볼 수 있습니다. 하지만 transformer 를 이용한 파이프라인은 이와 차이점이 있습니다. 바로 비 동기 처리가 가능하다는 것입니다.

 하나의 파이프라인의 작업이 끝날 때까지 대기하지 않기 때문에, 여러 파이프라인의 작업이 동시에 수행할 될 수 있습니다.

 잠시 후, 이런 파이프라인을 이용하는 예제를 알아보도록 하겠습니다.

 

선언

template < class _Output, class _Input >
class transformer

[ 코드1. transformer 의 선언 ]

 transformercall 처럼 함수 타입을 지정할 수 없습니다.

 transformer 는 std::tr1::function<_Output(_Input const&)> 로 함수 타입이 고정되어 있습니다.

 

예제

 transformer 를 이용하여 파이프라인을 구성하여 처리하는 예제를 구현해보도록 하겠습니다.

 

시나리오

 문자열을 꾸미는 작업을 각 단계별로 나누어 파이프라인을 구성하고, 이것을 비 동기로 처리하는 시나리오입니다.

 

코드

#include <iostream>
#include <string>
#include <agents.h>

using namespace std;
using namespace Concurrency;

// 문자열을 꾸며주는 helper class.
class DecorationHelper
{
public:
	static wstring Boiler( wstring food )
	{		
		wcout << L"boiling.." << endl;
		Concurrency::wait( 1000 );		
		return L"boiled " + food;
	}

	static wstring AddSugar( wstring food )
	{		
		wcout << L"adding sugar.." << endl;
		Concurrency::wait( 1000 );		
		return L"sweet " + food;
	}

	static wstring PutOnPlate( wstring food )
	{
		wcout << L"putting on a plate.." << endl;
		Concurrency::wait( 1000 );		
		return food + L" on a plate";
	}

	static wstring WrapUp( wstring food )
	{
		wcout << L"Wrapping the food up.." << endl;
		Concurrency::wait( 1000 );		
		return food + L".";
	}
};

int main()
{
	// 문자열 꾸밈 함수들을 수행하는 transformer message block 들.
	transformer< wstring, wstring > boiler( &DecorationHelper::Boiler );
	transformer< wstring, wstring > addSugar( &DecorationHelper::AddSugar );
	transformer< wstring, wstring > putOnPlate( &DecorationHelper::PutOnPlate );
	transformer< wstring, wstring > wrapUp( &DecorationHelper::WrapUp );

	// 최종 변환된 message 를 저장하는 message block.
	unbounded_buffer< wstring > result;

	// 꾸며진 문자열들을 다른 꾸밈 함수에 연결하여 파이프라인을 구성.
	boiler.link_target( &addSugar );
	addSugar.link_target( &putOnPlate );
	putOnPlate.link_target( &wrapUp );
	wrapUp.link_target( &result );

	// 구성된 pipeline 에 message 전달.
	asend( boiler, wstring( L"soup" ) );
	asend( boiler, wstring( L"noodle" ) );
	asend( boiler, wstring( L"water" ) );	

	// 최종 변환된 message 를 받아서 출력.
	while( true )
	{
		wcout << L"completed food: " << receive( result ) << endl;		
	}
}

[ 코드2. transformer 를 이용하여 파이프라인을 구성하고 비 동기로 처리하는 예제 ]

 문자열을 꾸미는 함수들을 각각 transformer 에 지정한 후, ISource 인터페이스인 link_target() 을 이용하여 파이프라인을 구성합니다.

 구성된 파이프라인의 첫 message block 인 boiler 객체에 인자로 사용할 문자열을 message 로 보내면 파이프라인이 비 동기로 처리됩니다.

 파이프라인의 마지막은 unbounded_buffer 를 사용하여 변환된 최종 값을 저장하고, 저장된 값들을 꺼내서 화면에 출력합니다.

 

[ 그림1. transformer 를 이용하여 파이프라인을 구성하고 비 동기로 처리하는 예제 ]

[ 그림1. transformer 를 이용하여 파이프라인을 구성하고 비 동기로 처리하는 예제 ]


 

마치는 글

  transformer 를 이용해 파이프라인을 구성하고 비 동기로 처리하는 방법을 알아보았습니다.

 지난 글에서 소개해드린 call 과는 확실히 다르다는 것을 알게 되셨을 것입니다.

 이 파이프라인 개념은 많은 곳에 적용되고 있으므로 유용하게 사용하실 수 있을 것입니다.

[Upgrade to VC++ 10] _WIN32_WINNT 버전 문제

Visual C++ 10 2010. 8. 2. 09:00 Posted by 알 수 없는 사용자

VS.NET(VS2002)에서 MFC 프로젝트로 만들었던 프로그램을 VC++10 프로젝트로 변환하여 컴파일 했더니 에러가 발생하면서 아래의 경고가 나왔습니다.

 

C:\Program Files\Microsoft Visual Studio 10.0\VC\atlmfc\include\atlcore.h(35):#error This file requires _WIN32_WINNT to be #defined at least to 0x0403. Value 0x0501 or higher is recommended.

 

에러 내용은 프로젝트에서 정의된 _WIN32_WINNT 버전이 0x403인데 atlcore.h 버전이 최소 0x0501 이상이 되어야 한다는 것입니다.

 

그래서 _WIN32_WINN 정의한 stdafx.h 파일을 열어보니

#define _WIN32_WINNT 0x0400

되어 있었더군요. 그래서 일단 이것을 최신이 좋다라는 생각에 아래와 같이 했습니다. ^^;;

#define _WIN32_WINNT 0x0600

 

그랬더니 이제는 아래와 같은 에러가 나오더군요. -_-;

c:\program files\microsoft visual studio 10.0\vc\atlmfc\include\afxcmn3.inl(29): error C2065: 'CCM_SETWINDOWTHEME' : 선언되지 않은 식별자입니다.

 

그래서 바로 구글링 들어갔습니다.

쉽게 저와 같은 에러가 나와서 질문을 올린 글을 찾았고 답변도 보았습니다.

문제 해결은 stdafx.h 파일에 정의된 버전의 숫자를 아래와 같이 하면 된다고 하더군요

// Modify the following defines if you have to target a platform prior to the ones specified below.
// Refer to MSDN for the latest info on corresponding values for different platforms.
#ifndef WINVER // Allow use of features specific to Windows 95 and Windows NT 4 or later.
#define WINVER 0x0501 // Change this to the appropriate value to target Windows 98 and Windows 2000 or later.
#endif

#ifndef _WIN32_WINNT // Allow use of features specific to Windows NT 4 or later.
#define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target Windows 98 and Windows 2000 or later.
#endif

#ifndef _WIN32_WINDOWS // Allow use of features specific to Windows 98 or later.
#define _WIN32_WINDOWS 0x0501 // Change this to the appropriate value to target Windows Me or later.
#endif

#ifndef _WIN32_IE // Allow use of features specific to IE 4.0 or later.
#define _WIN32_IE 0x0500 // Change this to the appropriate value to target IE 5.0 or later.
#endif

 

이렇게 하니 문제 없이 빌드가 성공 되었습니다.

 

주위에서 VC++의 새로운 버전이 나와도 쉽게 사용하지 못하는 경우가 오래 전에 만들었던 프로젝트를 포팅 할 수 없어서 이전 버전을 어쩔 수 없이 사용한다는 이야기를 종종 듣습니다.

그러나 저는 운이 좋아서인지 2002버전부터 순차적으로 새 버전의 VC++을 사용할 수 있어서 VC++6에서 VS2002로 넘어갈 때만 빌드 문제를 겪었습니다.

그래서 이런 포팅에 대한 문제는 잘 알지 못합니다. 이번에는 예전에 만들었던 코드를 C++0x 코드로 바꾸고 싶어서 오래 전에 만들었던 프로젝트를 VC++ 10로 포팅하면서 정말 정말 오랜만에 이런 문제를 겪어 보게 되고 해결 방법을 포스팅 할 수 있었습니다.

 

혹시 앞으로 또 이런 경우가 발생하면 바로 바로 공유하도록 하겠습니다.