Welcome to Parallel C#(3) - 작업의 기본.

C# Parallel Programming 2010. 5. 31. 09:00 Posted by 알 수 없는 사용자
- 작업해본 적이나 있수?

물론이죠-_-;; 이 나이에 작업해 본 적도 없으면, 마법사 정도가 아니라, 신이 됐겠죠. 어헣. 오늘의 작업은 그 작업은 아니고... 스레드와 관련된 작업입니다. 부디 오해 없으시길 바라고, 작업의 기본은 다른 연예 서적에서 얻으시길.


- Task 시작하기.

지난 포스트에서 했던 예제를 한번 돌아보겠습니다.

using System;
using System.Threading.Tasks;

namespace Exam2
{
    class Program
    {
        static void Main(string[] args)
        {
            const int max = 10000;

            //현재 작업중인 스레드외에 추가로 스레드를 생성
            Task task = new Task(() =>
                {
                    for (int count = 0; count < max; count++)
                    {
                        Console.Write("|");
                    }
                });

            //추가 스레드 시작
            task.Start();

            //현재 작업중인 스레드에서도 반복문 시작
            for (int count = 0; count < max; count++)
            {
                Console.Write("-");
            }

            //혹시 현재 스레드가 빨리 끝나더라도,
            //추가 스레드가 끝날 때 까지 기다리기.           
            task.Wait();
        }
    }
}

<코드1>

Main메서드를 실행하는 스레드 외에 또 하나의 스레드를 추가로 생성해서, 두 개의 스레드로 화면에 다른 문자열을 출력하는 예제였죠. 이 예제를 보면, Task라는 클래스를 사용하고 있습니다. 이 클래스는 닷넷 프레임워크 4.0에 새롭게 추가된 클래스인데요. 기존의 멀티스레드 프로그래밍을 한 단계 높은 추상화를 통해서 프로그래머의 실수를 줄이고, 좀 더 직관적인 코드를 작성할 수 있게 해주는 TPL(Task Parallel Library)에 포함되어서 추가된 클래스입니다. 중심에 있는 클래스라고 볼 수 있죠.

Task클래스는 관리되지 않는 스레드를 한단계 감싸서 추상화를 시킨 클래스입니다. 내부적으로 스레드 풀을 사용하는 데요, 내부적으로는 System.Threading.ThreadPool을 사용해서 요청에 따라 스레드를 새로 생성하거나, 이미 생성된 스레드를 재활용해서 부하를 줄입니다.

새로운 Task가 실행할 동작은 델리게이트를 통해서 명시해주는데요, <코드1>에서 굵게 처리된 부분이 바로 그 부분입니다. 카운트에 따라서 문자열을 출력하는 델리게이트를 생성자에 넘겨주고 있는 거죠. 물론, 이렇게 델리게이트를 넘겨준다고 해서 바로 스레드가 실행되는 건 아닙니다. Start메서드를 통해서 실행을 해줘야만 스레드가 실행되는 것이죠.


- Task가 끝날 때?

그러면, <코드1>은 두개의 스레드가 실행이 되면서 서로 다른 문자열을 번갈아 가면서 출력하겠죠. 여기서 한가지 생각해볼게 있습니다. 콘솔 어플리케이션은 Main메서드의 실행으로 시작하고, Main메서드의 끝과 함께 종료됩니다. 그렇다면, Main메서드의 실행을 맡은 스레드가 종료되었는데, 추가로 생성한 스레드의 작업이 안끝났다면 어떤 일이 벌어질까요?

//현재 작업중인 스레드외에 추가로 스레드를 생성
Task task = new Task(() =>
    {
        for (int count = 0; count < max; count++)
        {
            Console.Write("|");
        }
        Console.WriteLine("추가 스레드 끝");
    });

//추가 스레드 시작
task.Start();

//현재 작업중인 스레드에서도 반복문 시작
for (int count = 0; count < max; count++)
{
    Console.Write("-");               
}
Console.WriteLine("메인 스레드 끝");

//혹시 현재 스레드가 빨리 끝나더라도,
//추가 스레드가 끝날 때 까지 기다리기.           
//task.Wait();

<코드2>

<코드1>을 <코드2>와 같이 수정한 다음에 실행해보죠. 그러면, 둘 중의 어떤 스레드가 빨리 끝날까요? 그건 그때 그때 다릅니다-_- 그래서 아래와 같은 두 경우가 생길 수 있죠.

||||-----||||||||--------------|||||||||||||-------------||||||||||||-----------||||||---------------||||||||||||------------|||||||||||-----------||||||||||||--|||||||||||||-------|||||||||||-------------||||||||||||--------------||||||||||----------------|||---------------||||||||||||||-----||-------------||||||||||||----------------|||||||||||||-------------||||||||||||-------------|||---|------||||||||||||||-----------------|---------------||||||||||||||---------------메인 스레드 끝
|계속하려면 아무 키나 누르십시오 . . .
<결과1> 메인스레드가 먼저 끝나는 경우

|----------------|||||||||||||-----------||||||||||||||---------||||||||||||-----||||||||||||||||---------------||||||||||||||--------------||-----------||||||||------------|||||||||||||||-------------|--------------|||||||-------------|||||---||추가 스레드 끝----------------------------------------------------------------------------------------------------------------------------------------------------------메인스레드 끝
계속하려면 아무 키나 누르십시오 . . .
<결과2> 추가 스레드가 먼저 끝나는 경우

<결과2>는 추가 스레드가 먼저 끝나면서 모든 결과가 출력이 되었지만, <결과1>은 Main메서드의 실행을 맡은 메인 스레드가 먼저 끝나면서 프로그램이 종료되었고, 따라서 추가스레드의 나머지 결과는 날아가 버렸습니다. Wait메서드는 메인 스레드가 먼저 끝나더라도, 추가 스레드가 끝날 때까지 기다리게 하는 역할을 합니다. 그래서 메인 스레드가 빨리 끝나더라도, 항상 추가 스레드의 결과까지 제대로 출력되게 되는 것이죠.

||||||||--------|||||||||||||||--------------|||||||||||||------------|||||||||||||--------||||
||-메인 스레드 끝||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||추가 스레드 끝
계속하려면 아무 키나 누르십시오 . . .
<결과3> Wait메서드를 사용한 경우


- 가는 길은 여러갈래.

앞에서 스레드의 시작은 Start메서드를 통한다고 말씀 드렸지만, 항상 그런 것은 아닙니다. 생성과 동시에 실행을 시킬 수도 있습니다.

Task task = Task.Factory.StartNew(() =>
                {
                    for (int count = 0; count < max; count++)
                    {
                        Console.Write("|");
                    }
                    Console.WriteLine("추가 스레드 끝");
                });
<코드3> 생성과 동시에 스레드 시작

<코드1>의 Task생성 부분을 <코드3>과 같이 수정하고, Start메서드 호출부분을 주석처리하면, 동일한 결과를 얻을 수 있습니다.

그리고 <코드1>에서는 메인스레드가 추가 스레드가 끝날 때까지 기다리기 위해서 Wait메서드를 사용했지만, 다른 방법도 있습니다. 만약에 추가 스레드에 입력된 델리게이트가 결과값을 반환하고, 메인 스레드에서 그 결과값을 사용해야 한다면, 메인 스레드는 추가 스레드의 작업이 끝나서 결과가 나올 때까지 기다립니다. Wait메서드 없이도 말이죠. 어찌보면 당연한 이야기죠 ㅋ

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Exam3
{
    class Program
    {
        static void Main(string[] args)
        {
            Task<string> task = Task.Factory.StartNew<string>(
                () =>
                {
                    long sum = 0;
                    for (long i = 0; i < 100000000; i++)
                    {
                        sum += i;
                    }
                    return sum.ToString();
                });

            foreach (char busySymbol in BusySymbols())
            {
                if (task.IsCompleted)
                {
                    Console.WriteLine('\b');
                    break;
                }
                Console.WriteLine(busySymbol);
            }

            Console.WriteLine();
            //여기서 추가 스레드가 끝날 때 까지 기다린다.
            Console.WriteLine(task.Result);
            System.Diagnostics.Trace.Assert(
                task.IsCompleted);
        }

        private static IEnumerable<char> BusySymbols()
        {
            string busySymbols = @"-\|/-\|/";
            int next = 0;
            while (true)
            {
                yield return busySymbols[next];
                next = (++next) % busySymbols.Length;
                yield return '\b';
            }
        }
    }
}

<코드 4> 결과 기다리기.

<코드4>는 추가 스레드의 결과를 계속 기다리다가, 결과가 나오는 순간, 출력하고 끝납니다.


- 멀티스레드 쉽고만?

이라고 생각하시면 곤란하구요-_-;; 열심히 공부 중인데, 역시 어렵습니다. 다만, Task클래스가 상당히 많은 부분을 간소화 시켜 주기 때문에, 한 층 더 편해진 느낌이랄까요? 오늘 여기까지!


- 참고자료

1. Essential C# 4.0, Mark Michaelis, Addison Wesley

Welcome to Parallel C#(2) - 계속 되는 개념 찾기.

C# Parallel Programming 2010. 5. 27. 09:00 Posted by 알 수 없는 사용자

- 개념!!

군 생활을 하다 보면, 개념 없는 사람들을 많이 보게 됩니다. 가끔은 '내가 평생 이런 놈을 다시 볼 수 있을까?'싶은 사람도 보게되죠. 그리고 인간관계란 과연 무엇인지 원점에서 다시 생각해 보게 됩니다. 개념 없는 사람들도 사회에서 담당하는 역할이 있는 셈이죠. 오늘은 병렬 프로그래밍에 필요한 개념들을 같이 채워볼까 합니다. 개념 충만한 사람들은 훠이훠이~ 절로 가시고(저는 종교의 다양성을 존중합니당. 교회로 가실 분은 교회로..), 개념 없는 사람들만 저랑 같이 개념을 채워 보시져. 훗.


- 기본 개념!!!

스레드(Thread)

스레드는 다른 순차적 명령 집합(sequence of instructions)과 동시적으로 실행 가능한 순차적 명령 집합을 이야기 합니다. 예를 들면, 뭔가 작업을 처리하는 스레드가 있고, 그 작업의 현황을 화면에 표시하는 스레드가 있는 것 처럼 말이죠. 두 스레드는 서로 다른 순차적 명령 집합을 가지며, 동시적으로 실행 가능하죠. 그리고 두 개 이상의 스레드를 동시적으로 실행하는 것을 멀티스레딩(multithreading)이라고 합니다. 흔히 이야기 하는 동시성이나 병렬성도 최대한 효율적으로 스레드를 여러개 사용하는 것이므로, 멀티스레드의 범주에 들어간다고 할 수 있겠습니다. 운영체제는 이런 멀티 스레드를 동시적으로 처리하는 것 처럼 보여주기 위해서 시간 쪼개기(time slicing)이라는 기법을 활용합니다. 즉, 사람이 눈치채지 못할 정도로 빠른 시간만큼 한번에 하나씩 스레드를 처리하는 것이죠. 단순하게 생각해서, 음악을 들으면서 비주얼 스튜디오로 코딩을 한다면, 사람이 눈치채지 못할 정도의 속도로, 예를 들면 1/24초 정도의 속도로 한번은 음악재생, 한번은 비주얼 스튜디오, 또 한번은 음악 재생, 또 한번은 비주얼 스튜디오 이런식으로 번갈아 가면서 처리하는 것이죠. 이걸 문맥 교환(context switching)이라고 합니다. <그림1>을 보시죠.

<그림1>본격 OS 스케쥴링 그림.jpg

<그림1>에서 나온 것 처럼, 사용자가 눈치채지 못 할만큼의 속도로 두 작업을 번갈아 가면서 CPU가 처리하도록 OS가 스케쥴을 조절하는 것이죠. 그런데 만약, 스레드가 너무 많아서 제대로 CPU를 점거하지도 못하고, 작업을 계속해서 교체하게 되면 어떻게 될까요? 실제로 CPU가 작업을 처리하는 시간보다, 문맥 교환에 더 많은 시간이 들어가게 되므로, 음악이 벅벅 끊기는 등의 지름유발상황이 생기겠죠. 그럼, 시간 쪼개기를 한번 시뮬레이트 해 볼까영? 어헣.

using System;
using System.Threading.Tasks;

namespace Exam2
{
    class Program
    {
        static void Main(string[] args)
        {
            const int max = 10000;

            //현재 작업중인 스레드외에 추가로 스레드를 생성
            Task task = new Task(() =>
                {
                    for (int count = 0; count < max; count++)
                    {
                        Console.Write("|");
                    }
                });

            //추가 스레드 시작
            task.Start();

            //현재 작업중인 스레드에서도 반복문 시작
            for (int count = 0; count < max; count++)
            {
                Console.Write("-");
            }

            //혹시 현재 스레드가 빨리 끝나더라도,
            //추가 스레드가 끝날 때 까지 기다리기.           
            task.Wait();
        }
    }
}

<코드1>아주 단순한 코드

<코드1>은 아주 단순한 코드입니다. 콘솔 어플리케이션의 실행을 담당하는 스레드 외에, 추가로 스레드를 하나 더 생성해서, 두 스레드에서 똑같은 반복문을 돌면서 서로 다른 문자를 출력하게 하는 거죠. 결과를 볼까요?

-|---||||||||||||||||------|||||||||||||||---------------|||||||||||||-----------||||||--------------|||||||||||||--------------|||||||||||||------------||||||||--------------||||||||||||----------||||||||||||||---------|||||||||||||--------|||||||||||||||--------------|||||||||||||||-------------||||||||||||-||||||||||-----------------|||||||||||||||-------------||||||||||||||--|||||||||||||-------|||||||---------------|||||||||||||||----------||||||||||||||---------|||||||||-----------------||||||||||||||-------------|||||||||||||-------------||||||||||-----------------|||||||||||||------------|||||||||||||-----------||||||||------|||||||||||||--------|||||||||||||||-----||||||||||||||-------------||||||||||||----------------|||||||||||||---------------|||||--------------|||||||||||||----||||||||||||||||--------------|||||||||||||||---------||||||||||||-----------|||---------------||||||||||||||||----------------||||||-------------|||||||||----|||||||||||||||||-------------||||||||||||||-||||||||||||||-------------||||||||-------|||||||||||||||--------|--|||---------|||||||||||||--------|||||||||||---|||||||||||||||----------------|||||||||||||||--------------||||-----------------------------------------------------------------------------------------------
계속하려면 아무 키나 누르십시오 . . .

<결과1>평범한 결과

서로 다른 문자가 출력되는 곳이 바로, 시간 쪼개기로 인한 문맥 교환이 일어나는 곳입니다. 정해진 시간만큼 한 스레드가 CPU를 점거하고 작업을 하는 것이죠. 그러면, 약간 방해를 해볼까요? 코드를 다음과 같이 약간 수정해봅니다.

Console.ReadLine();

//추가 스레드 시작
task.Start();

<코드2>수정된 아주 평범한 코드

그리고 이 프로그램이 사용자의 입력을 기다리는 동안, 다음과 같이 프로세스의 우선순위를 낮춰버립니다.

<그림2>프로세스 우선순위 낮추기 ㅋ

그리고 한번 실행해 볼까요? 프로세스의 우선순위가 낮아지면, 실행중인 작업이 우선순위가 높은 다른 프로세스에게 방해를 받을 가능성이 높아지는 거죠.

---------|||||||----||||||||||||||||-----||||||||||||--------||||||||||||||------------|||||||||----------------||||||||||||||------------||||||||||||||-------------|||||||||||----------------|||||||||||||-----------||||||||||||||-------------||||||||||||--------||||||||||||||------------|||||||||||||||-------------|||||||||||||------||||||||||||||-------------||||||||||||||--------------|||||||||||-----------||||---------------|||||||||||||-------------||||||||||||||-------|||||||||||||----|||||||||||||--------------|||||||||||||||-------------|||||||||||||-|||||||||||---------------||||-------------|||||||||||||-------------|||||||||||||---------|----------------|||||||||||||-------------||||||||||||||-----------||||||||||||---------------|||||---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
계속하려면 아무 키나 누르십시오 . . .

<결과2>낮은 우선순위 결과

그런데, <결과2>를 보니, 얼마나 방해를 받은 건지 솔직히 잘 모르겠네요.('-'가 마지막에 막 출력된 건, '|'를 출력하는 스레드가 빨리 작업을 끝내서 입니당) 제 컴이 쿼드에 2.7GHz짜리인데, 아마도 이정도로는 방해가 안되는 거 같습니다. 그래서! 비주얼 스튜디오 2010 20개랑, 음악을 틀고, 바이러스 검사를 돌리고, 인터넷 익스플로러 8도 창을 7개정도 띄워놓고 실험을 해봤습니다.

-||||||||||||||||-------------|---------------|--------------||||||||||----------||||||||||||||-----------||||||||||||--------------|||||||||||---------||||||||-|||||||----|||||||||------------|||||||||||||-------------|||-----------||||||||---------------||||||||||||||-------------||||||-------------||||||||||--------||||||||||||||----|||||||||||||||--------------||||||||||||-----------||||||||||----------------|||||||||||----------|------------||-|||||||||------||-------|||--------------|||||||||||||------------|||||||||||||-------------|||||||||||----|||||||||||-----||||||||||||||---------------||||||||||||||------------|||||||||----------------||||||||||||--------|||||||||||||-------------|||||||||-------|||------------|||||-----|||||||------||||||||||||||||---------------------||||||------|||||||||||||||-------------||||||||||||||-------------||||||||||||--------||||||||||||||||-------------||||||||||--------------|||||||||-------||||------|----||||||-----||||||||||||||-------------|||||||||------------|||||||||||-------||||||||||||||--------------||||||||||||||--------------||||||||||--------||||
|--------------||||||||||||||||-----------|||||||||||||--|||||||||||------||||||||----------------||----||||||||||||||-----||||||||||||||||||||||||||||||||||||||
계속하려면 아무 키나 누르십시오 . . .

<결과3>갖가지 태클을 가한 결과

<결과3>을 보시면, 어느 정도 패턴이 불안정 해진 걸 보실 수 있습니다. 제대로 처리도 못하고, '|'이거 딱 한개 출력하고 문맥 교환이 일어난 부분도 꽤 보이네요.


원자성(Atomicity)

원자성은 뭘까요? 원자성을 만족하는 조건을 보지요. 아래 조건 중에서 하나를 만족하면 원자성을 만족하는 것입니다.

1. 작업을 처리하기 위한 명령어 집합은 다른 작업이 끼어들기 전에 반드시 완료되어야 한다.
2. 작업을 처리 중인 시스템의 상태는, 작업을 처리하기 전의 시스템의 상태로 돌아갈 수 있어야 한다. 즉, 아무 작업도 처리되지 않은 시스템의 상태로 돌아갈 수 있어야 한다.

예를 들면, 은행 계좌에서 돈을 송금하는 걸 생각해보면요. 돈을 송금하기위한 몇가지 단계가 있습니다. 일단 계좌에 돈이 있는지 확인하고, 보낼 계좌가 존재하는지 확인하고, 그리고 돈을 실제로 송금하는 것이죠. 이렇게 송금을 위한 각 단계를 밟는 와중에, 돈을 인출한다던가 하는 작업이 끼어들면 안되겠죠. 그리고 송금 절차가 완료되기 전에는 계좌에 아무런 변화가 없어야 하는 거죠.

C#에서 num++같은 연산을 보면, num의 값을 가져오고, num의 값을 1 증가시키고, num에 증가된 값을 넣는 절차를 밟는데요. ++연산자는 원자성을 만족하지 못합니다. 그래서 num에 증가된 새 값을 넣기 전에, 다른 스레드가 원래 값을 변경 시켜버린다던가 하는 일이 벌어질 수 있죠.


데드락(Deadlock)

데드락은 죽음을 잠근다는 뜻이므로, 죽지 않는다는 뜻입니다. 네, 저를 죽이려고 달려드는 사람들의 모습이 보이네요. 모두들 잠깐만 진정하시고....-_- 이건 서로 볼을 꼬집고서, '니가 먼저 안놓으면, 나도 안놓을 꺼임'하고 외치고 있는 꼬마들을 생각하시면 됩니다. 꼬맹이들은 괜한데서 자존심을 세우죠. 그래서 먼저 놓으면 지는거라고 생각해서 안 놓습니다. 그래서 서로 계속해서 볼을 꼬집고 질질 짜게 되는 거죠. ㅋㅋㅋ

한 스레드는 A라는 자원을 점거하고, B를 가지고 와야 A를 놓아줍니다. 그리고 또 다른 스레드는 B라는 자원을 점거하고, A를 가져와야 B를 놓아준다고 해보죠. 그러면 이 스레드들은 서로 상대방이 가진 자원을 하염없이 기다리는 상황이 됩니다. 죽을 때까지 그 상태로 기다리겠죠. 그래서 이건 데드락입니다. 어헣.


- 정리하면서

위에서 설명드린 원자성이나 데드락은 여러개의 스레드에서 어떤 순서로 명령어를 실행하느냐에 따라서 복잡하게 발생합니당. 이런 프로그램을 짜려면 머리 빠지겠죠. 아이 무셔워라. 그래서 조심해야 하는 것이고, 이런 실수를 최대한 줄여주는 도구가 있어야 하는 것이죠!!

이게 왠 운영체제 수업이냐 하고 반감을 가지시는 분덜도 있으시리라 생각이 됩니다. 모.. 복습했다 생각하시고 어헣. 좋은게 좋은거니깐.. 어헣.


- 참고자료

1. http://www.cafeaulait.org/course/week11/03.html
2. Essential C# 4.0, Mark Michaelis, Addison Wesley

Parallel Patterns Library (PPL)

VC++ 10 Concurrency Runtime 2009. 8. 6. 06:00 Posted by 알 수 없는 사용자

이제 본격적으로 VC++ 10의 병렬 프로그래밍에 대한 이야기를 시작합니다.

첫 번째는 이름만 들어도 딱 '병렬 프로그래밍' 이라는느낌을 주고 가장 많이 사용될 것으로 생각하는 Parallel Patterns Library (PPL)입니다정말 이름에서 딱 느낌이 오죠 ^^



PPL은 크게 세 개의 features로 나누어집니다.

1. Task Parallelism : 병렬적으로 여러 가지 작업 처리

2. Parallel algorithms : 데이터 컬렉션을 제너릭 알고리즘으로병렬 처리

3. Parallel containers and objects :concurrent 접근이 가능한 제너릭 컨테이너

 


PPL 모델은 C++의 Standard Template Library(STL)과비슷합니다.

예를 들면 STL에는 for_each 라는 것이 있는데 PPL에는 이것의 병렬 버전인 parallel_for_each가 있습니다. 뒤에 설명하겠지만 parallel_for_each에 대해서 간단하게 말하면 array의 항목을 순회하는 parallel 알고리즘입니다.



PPL을 사용하기 위해서는 먼저 namespace Concurrency를 선언한 후 ppl.h 파일을 포함합니다.
........
#include <ppl.h>

using namespace Concurrency;
..............


먼저 parallel_for_each를 사용한 코드를 보여 드리겠습니다. parallel_for_each는 다음에 자세히 설명하겠으니 이번은 PPL 이라는 것이 어떻게 사용하는지만 아래 코드를 통해서 보세요^^

< 리스트 1. parallel_for_each 예제 >

#include <ppl.h>

#include <array>

#include <algorithm>

 

using namespace std;

using namespace std::tr1;

using namespace Concurrency;

 

int main()

{

   // Create anarray object that contains a few elements.

   array<int, 3> a = {13, 26, 39};

 

   // Use thefor_each algorithm to perform an operation on each element

   // of the arrayserially.

  for_each(a.begin(), a.end(), [&](int n) {

      // TODO:Perform some operation on n.

   });

 

   // Use theparallel_for_each algorithm to perform the same operation

   // in parallel.

  parallel_for_each(a.begin(), a.end(), [&](int n) {

      // TODO:Perform some operation on n.

   });

}


<리스트 1>의 코드를 보면 람다를 사용한 부분도 보이죠? 예전에 제가 C++0x의 새로운 기능에 의해 C++의 성능과 표현력이 향상 되었다고 이야기 했습니다. 이런 장점들이 PPL에 많은 기여를 하였습니다.




PPL과 OpenMP

예전에 PPL이 MSDN 매거진을 통해서 공개 되었을 때 많은 분들이 OpenMP와 비슷하게 보시고 왜 기존에 있는 것과 같은 것을 또 만드냐 라는 이야기를 하는 것을 들은 적이 있습니다.

PPL과 OpenMP는 같은 것이 아닙니다. 표현 방법이 얼핏 비슷하게 보일지 몰라도 개념이나 기반은 많이 다릅니다.

OpenMP는 pragma 지신문이고 PPL은 순수 C++ 템플릿으로 만들어진 라이브러리입니다.
그래서 PPL은 표현성과 유연성이 OpenMP에서 비해서 훨씬 더 뛰어납니다.
또한 PPL은 Concurrency Runtime 기반 위에 구축되므로 동일한 런타임을 기반으로 하는 다른 라이브러리와 잠재적 상호 운용성이 제공됩니다.

PPL은 어떤 것인지, 왜 OpenMP 보다 더 좋은지 이후에 제가 적을 글을 보면 쉽게 알 수 있으리라 생각합니다.


오늘은 PPL의 개념에 대한 이야기로 마치고 다음에는 PPL의 하나인 task에 대해서 이야기 하겠습니다.
시간 여유가 있거나 task에 대해서 빨리 알고 싶은 분들은 일전에 정재원님이 task 예제를 설명한 글을 올린 적이 있으니 먼저 그것을 보면서 예습을 하는 것도 좋습니다.



Concurrency Runtime

VC++ 10 Concurrency Runtime 2009. 7. 30. 06:00 Posted by 알 수 없는 사용자

VSTS 2010 VC++ 10의 큰 핵심 feature 두 가지를 뽑으라고 하면 저는 C++0x와 Concurrency Runtime 두 가지를 뽑고 싶습니다.

VC++ 10
은 시대의 변화에 맞추어 새로운 C++ 표준과 병렬 프로그래밍을 받아들였습니다.

현재도 Win32 API에 있는 Thread  관련 API를 사용하여 병렬 프로그래밍을 할수 있습니다. 하지만 이것만으로 병렬 프로그래밍을 하기에는 너무 불편합니다.
그래서 VC++ 10에는 Concurrency Runtime 이라는 것이 생겼습니다.



Concurrency
Parallel의 차이


Concurrency는 병행, Parallel은 병렬이라고 합니다.

Concurrency는 독립된 요구를 동시에 처리하고, Parallel은 하나의 task를 가능한 Concurrency로 실행할 수 있도록 분해하여 처리합니다.

< 그림 출처 : http://blogs.msdn.com/photos/hiroyuk/picture9341188.aspx >


VSTS 2010에서는 Concurrency는 런타임 용어 Paralell은 프로그래밍 모델 용어가 됩니다.
이를테면 프로그래밍 때에 분해하여 런타팀에 넘기면(이것이 병렬화), 런타임은 그것을 Parallel로 실행합니다. Concurrency Runtime은 Parallel 런타임으로 이해하면 될 것 같습니다.




Concurrency Runtime

< 그림 출처 : http://blogs.msdn.com/photos/hiroyuk/picture9341189.aspx >

Cuncurrency Runtime은 C++ 병행 프로그래밍 프레임워크입니다. Cuncurrency Runtime복잡한 parallel code 작성을 줄여주고, 간단하게 강력하고, 확장성 있고 응답성 좋은 parallel 애플리케이션을 만듭니다. 또한 공통 작업 스케줄러를 제공하며 이것은 work-stealing 알고리즘을 사용하여 프로세싱 리소스를 증가시켜 애플리케이션의 확장성을 높여줍니다.

 


Cuncurrency Runtime에 의해 다음의 이점을 얻을 수 있습니다.

1. data parallelism 향상 : Parallel algorithms은 컬럭션이나 데이터 모음을 복수의 프로세서를 사용하여 배분하여 처리합니다.

2. Task parallelism : Task objects는 프로세서 처리에 독립적으로 복수 개로 배분합니다.

3. Declarative data parallelism : Asynchronous agents와 메시지 전달로 어떻게 실행하지 몰라도 계산을 선언하면 실행됩니다.

4. Asynchrony : Asynchronous agents는 데이터에 어떤 일을 처리하는 동안 기다리게 합니다.

 

 

Cuncurrency Runtime 컴포넌트는 네 가지로 나누어집니다.

1. Parallel Patterns Library (PPL)

2. Asynchronous Agents Library (AAL)

3. work scheduler

4. resource manager

 

이 컴포넌트는 OS와 애플리케이션 사이에 위치합니다.


< 그림 출처 : MSDN >


Cuncurrency Runtime의 각 컴포넌트는 아래의 네 개의 헤더 파일과 관련 되어집니다.

컴포넌트

헤더 파일

Parallel Patterns Library (PPL)

ppl.h

Asynchronous Agents Library (AAL)

agents.h

Concurrency Runtime work scheduler

concrt.h

Concurrency Runtime resource manager

concrtrm.h

 

 

Concurrency Runtime을 사용하기 위해서는  namespace Concurrency를 선업합니다.

Concurrency RuntimeC Runtime Library (CRT)를 제공합니다.


Concurrency Runtime의 대부분의 type와 알고리즘은 C++의 템플릿으로 만들어졌습니다. 또한 이 프레임워크에는 C++0x의 새로운 기능이 많이 사용되었습니다.

대부분의 알고리즘은 파라메터 루틴을 가지고 작업을 실행합니다. 이 파라메터는 람다 함수, 함수 오브젝트, 함수 포인터입니다.



처음 들어보는 단어를 처음부터 막 나오기 시작해서 잘 이해가 안가는 분들이 있지 않을까 걱정이 되네요. 그래서 핵심만 한번 더 추려 보겠습니다.^^

1. Concurrency는 병행, Parallel은 병렬.
2. VSTS 2010에서는 Concurrency는 런타임 용어로 Paralell은 프로그래밍 모델 용어.
3. 프로그래밍 때에 분해하여 런타팀에 넘기면(이것이 병렬화), 런타임은 그것을 Parallel로 실행.
4. Cuncurrency Runtime은 C++ 병행 프로그래밍 프레임워크로 복잡한 parallel code 작성을 줄여주고, 간단하게 강력하고, 확장성 있고 응답성 좋은 parallel 애플리케이션을 만들수 있으며 공통 작업 스케줄러를 제공하며 이것은 work-stealing 알고리즘을 사용하여 프로세싱 리소스를 증가시켜 애플리케이션의 확장성을 높여준다.

5. Cuncurrency Runtime 컴포넌트는 네 가지로 나누어진다.

  1. Parallel Patterns Library (PPL)

  2. Asynchronous Agents Library (AAL)

  3. work scheduler

  4. resource manager



그럼 다음에는 Parallel Patterns Library(PPL)에 대해서 이야기 하겠습니다.^^