작업 병렬 처리(동시성 런타임)

업데이트: 2011년 3월

이 문서에서는 동시성 런타임에서 작업 및 작업 그룹의 역할에 대해 설명합니다. 동시에 실행할 두 개 이상의 독립적인 작업 항목이 있는 경우 작업 그룹을 사용합니다. 예를 들어 남은 작업을 두 개의 파티션으로 나누는 재귀 알고리즘이 있다고 가정해 봅니다. 작업 그룹을 사용하여 이러한 파티션을 동시에 실행할 수 있습니다. 반대로 컬렉션의 각 요소에 동일한 루틴을 병렬로 적용하려는 경우에는 Concurrency::parallel_for와 같은 병렬 알고리즘을 사용합니다. 병렬 알고리즘에 대한 자세한 내용은 병렬 알고리즘을 참조하십시오.

작업 및 작업 그룹

작업(task)은 특정 업무를 수행하는 작업(work) 단위입니다. 일반적으로 작업을 다른 작업과 병렬로 실행할 수 있으며 작업을 더 세분화된 추가 작업으로 분해할 수 있습니다. 작업 그룹은 작업 컬렉션을 구성합니다. 작업 그룹은 작업 가로채기 큐에 작업을 넣습니다. 스케줄러는 이 큐에서 작업을 제거하고 사용 가능한 컴퓨팅 리소스에서 해당 작업을 실행합니다. 작업 그룹에 작업을 추가한 후 모든 작업이 끝날 때까지 기다리거나 아직 시작되지 않은 작업을 취소할 수 있습니다.

PPL은 Concurrency::task_groupConcurrency::structured_task_group 클래스를 사용하여 작업 그룹을 나타내고 Concurrency::task_handle 클래스를 사용하여 작업을 나타냅니다. task_handle 클래스는 작업을 수행하는 코드를 캡슐화합니다. 이 코드는 람다 함수, 람다 포인터 또는 함수 개체의 형태로 제공되며 작업 함수라고도 합니다. 일반적으로 task_handle 개체를 직접 사용할 필요는 없습니다. 대신 작업 함수를 작업 그룹에 전달하면 작업 그룹은 task_handle 개체를 만들고 관리합니다.

PPL은 작업 그룹을 비구조적 작업 그룹 및 구조적 작업 그룹이라는 두 개의 범주로 분할합니다. PPL은 task_group 클래스를 사용하여 비구조적 작업 그룹을 나타내고 structured_task_group 클래스를 사용하여 구조적 작업 그룹을 나타냅니다.

중요

PPL은 structured_task_group 클래스를 사용하여 작업 집합을 병렬로 실행하는 Concurrency::parallel_invoke 알고리즘도 정의합니다. parallel_invoke 알고리즘의 구문이 더 간결하므로 가능하면 structured_task_group 클래스 대신 이 알고리즘을 사용하는 것이 좋습니다. 병렬 알고리즘 항목에서 parallel_invoke에 대해 더 자세히 설명합니다.

동시에 실행할 독립적인 작업이 여러 개 있고 계속하지 않고 모든 작업이 끝날 때까지 기다려야 하는 경우 parallel_invoke를 사용합니다. 동시에 실행할 독립적인 작업이 여러 개 있지만 나중에 작업이 끝날 때까지 기다리려는 경우 task_group을 사용합니다. 예를 들어 task_group개체에 작업을 추가하고 다른 함수 또는 다른 스레드에서 작업이 끝날 때까지 기다릴 수 있습니다.

작업 그룹에서는 취소 개념을 지원합니다. 취소를 사용하면 전체 작업(operation)을 취소하려는 모든 활성 작업(task)에 신호를 보낼 수 있습니다. 취소를 사용하면 아직 시작되지 않은 작업을 시작되지 않게 할 수도 있습니다. 취소에 대한 자세한 내용은 PPL에서의 취소를 참조하십시오.

런타임에서는 관련된 작업 그룹이 끝나기를 기다릴 때 작업에서 예외를 throw하고 해당 예외를 처리할 수 있게 하는 예외 처리 모델을 제공합니다. 예외 처리 모델에 대한 자세한 내용은 동시성 런타임에서 예외 처리를 참조하십시오.

task_group을 structured_task_group과 비교

structured_task_group 클래스 대신 task_group 또는 parallel_invoke를 사용하는 것이 좋지만, 예를 들어 여러 가지 작업을 수행하거나 취소에 대한 지원이 필요한 병렬 알고리즘을 작성할 때 structured_task_group을 사용하는 경우도 있습니다. 이 단원에서는 task_group 클래스와 structured_task_group 클래스의 차이점에 대해 설명합니다.

task_group 클래스는 스레드로부터 안전합니다. 따라서 여러 스레드의 task_group 개체에 작업을 추가하고 여러 스레드의 task_group 개체를 기다리거나 취소할 수 있습니다. structured_task_group 개체의 생성과 소멸은 동일한 어휘 범위에서 발생해야 합니다. 또한 structured_task_group 개체에 대한 모든 작업은 동일한 스레드에서 발생해야 합니다. 이 규칙에 대한 예외는 Concurrency::structured_task_group::cancelConcurrency::structured_task_group::is_canceling 메서드입니다. 자식 작업은 언제든지 이러한 메서드를 호출하여 부모 작업 그룹을 취소하거나 취소를 확인할 수 있습니다.

Concurrency::task_group::wait 또는 Concurrency::task_group::run_and_wait 메서드를 호출한 후 task_group 개체에 대한 추가 작업을 실행할 수 있습니다. 반대로 Concurrency::structured_task_group::wait or Concurrency:: structured_task_group::run_and_wait 메서드를 호출한 후에는 structured_task_group에 대한 추가 작업을 실행할 수 없습니다.

structured_task_group 클래스는 스레드 간에 동기화하지 않으므로 task_group 클래스보다 실행 오버헤드가 적습니다. 따라서 여러 스레드에서 작업을 예약할 필요가 없고 parallel_invoke 알고리즘을 사용할 수 없는 경우 structured_task_group 클래스를 사용하면 성능이 높은 코드를 작성할 수 있습니다.

다른 structured_task_group 개체 내에 있는 structured_task_group 개체를 사용하는 외부 개체를 완료하기 전에 먼저 내부 개체를 완료하고 소멸시켜야 합니다. task_group 클래스를 사용하는 경우에는 외부 그룹을 완료하기 전에 중첩된 작업 그룹을 완료할 필요가 없습니다.

비구조적 작업 그룹과 구조적 작업 그룹은 다양한 방식으로 작업 핸들을 사용합니다. 작업 함수를 task_group 개체에 직접 전달할 수 있으며 그러면 task_group 개체에서 자동으로 작업을 만들고 관리합니다. structured_task_group 클래스를 사용하려면 각 작업의 task_handle 개체를 관리해야 합니다. 관련된 structured_task_group 개체의 수명이 지속되는 동안 모든 task_handle 개체가 유효한 상태로 유지되어야 합니다. 다음 기본 예제에서와 같이 Concurrency::make_task 함수를 사용하여 task_handle 개체를 만들 수 있습니다.

// make-task-structure.cpp
// compile with: /EHsc
#include <ppl.h>

using namespace Concurrency;

int wmain()
{
   // Use the make_task function to define several tasks.
   auto task1 = make_task([] { /*TODO: Define the task body.*/ });
   auto task2 = make_task([] { /*TODO: Define the task body.*/ });
   auto task3 = make_task([] { /*TODO: Define the task body.*/ });

   // Create a structured task group and run the tasks concurrently.

   structured_task_group tasks;

   tasks.run(task1);
   tasks.run(task2);
   tasks.run_and_wait(task3);
}

여러 가지 작업이 있는 경우에 사용할 작업 핸들을 관리하려면 _malloca와 같은 스택 할당 루틴 또는 std::vector와 같은 컨테이너 클래스를 사용합니다.

task_groupstructured_task_group 모두 취소 기능을 지원합니다. 취소에 대한 자세한 내용은 PPL에서의 취소를 참조하십시오.

예제

다음 기본 예제에서는 작업 그룹을 사용하는 방법을 보여 줍니다. 이 예제에서는 parallel_invoke 알고리즘을 사용하여 두 작업을 병렬로 수행합니다. 각 작업은 하위 작업을 task_group 개체에 추가합니다. task_group 클래스를 사용하면 여러 작업이 이 개체에 작업을 동시에 추가할 수 있습니다.

// using-task-groups.cpp
// compile with: /EHsc
#include <ppl.h>
#include <sstream>
#include <iostream>

using namespace Concurrency;
using namespace std;

// Prints a message to the console.
template<typename T>
void print_message(T t)
{
   wstringstream ss;
   ss << L"Message from task: " << t << endl;
   wcout << ss.str(); 
}

int wmain()
{  
   // A task_group object that can be used from multiple threads.
   task_group tasks;

   // Concurrently add several tasks to the task_group object.
   parallel_invoke(
      [&] {
         // Add a few tasks to the task_group object.
         tasks.run([] { print_message(L"Hello"); });
         tasks.run([] { print_message(42); });
      },
      [&] {
         // Add one additional task to the task_group object.
         tasks.run([] { print_message(3.14); });
      }
   );

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

이 예제를 실행하면 다음과 같은 샘플 결과가 출력됩니다.

Message from task: Hello
Message from task: 3.14
Message from task: 42

parallel_invoke 알고리즘은 작업을 동시에 실행하므로 출력 메시지의 순서가 다를 수 있습니다.

parallel_invoke 알고리즘을 사용하는 방법을 보여 주는 전체 예제를 보려면 방법: parallel_invoke를 사용하여 병렬 정렬 루틴 작성방법: parallel_invoke를 사용하여 병렬 작업 실행을 참조하십시오. task_group 클래스를 사용하여 비동기 미래를 구현하는 전체 예제를 보려면 연습: 미래 구현을 참조하십시오.

강력한 프로그래밍

작업 그룹과 병렬 알고리즘을 사용할 때는 취소 및 예외 처리의 역할을 이해하고 있어야 합니다. 예를 들어 병렬 작업 트리에서 취소된 작업으로 인해 자식 작업이 실행되지 않게 됩니다. 자식 작업 중 하나가 사용자의 응용 프로그램에 중요한 작업(예: 리소스 해제)을 수행하는 경우 이로 인해 문제가 발생할 수 있습니다. 또한 자식 작업에서 예외를 throw하면 해당 예외가 개체 소멸자를 통해 전파되고 응용 프로그램에서 정의되지 않은 동작이 발생할 수 있습니다. 이러한 지점을 보여 주는 예제를 보려면 병렬 패턴 라이브러리의 유용한 정보 문서에 있는 취소 및 예외 처리가 개체 소멸에 미치는 영향 이해 단원을 참조하십시오. PPL의 취소 및 예외 처리 모델에 대한 자세한 내용은 PPL에서의 취소동시성 런타임에서 예외 처리를 참조하십시오.

관련 항목

참조

task_group 클래스

parallel_invoke 함수

structured_task_group 클래스

변경 기록

날짜

변경 내용

이유

2011년 3월

작업 그룹과 병렬 알고리즘을 사용하는 경우 취소 및 예외 처리의 역할에 대한 정보를 추가했습니다.

향상된 기능 관련 정보

2010년 7월

내용을 다시 구성했습니다.

향상된 기능 관련 정보

2010년 5월

지침을 확장했습니다.

향상된 기능 관련 정보