Aracılığıyla paylaş


Paralel desen Kitaplığı'nda en iyi yöntemler

Bu belgeye paralel desen kitaplığı (ppl), etkili biçimde kullanmak için en iyi nasıl açıklar. ppl hassas paralellik gerçekleştirmek için genel amaçlı kapsayıcıları ve nesneleri algoritmaları sağlar.

ppl hakkında daha fazla bilgi için bkz: Paralel Desenler kitaplığının (ppl).

Bölümler

Bu belge aşağıdaki bölümleri içerir:

  • Küçük döngü gövdeleri Parallelize değil

  • Olası en yüksek düzeyde paralellik express

  • Bölme ve Conquer Generals sorunları çözme için parallel_invoke kullanın

  • İptal veya özel durum için sonu paralel bir döngü işleme

  • Anlamak iptali ve özel durum işleme etkilemesi nesne yok etme

  • Sürekli olarak paralel bir döngüde geçmeyecek

  • Paralel iş iptal ettiğinizde işlemleri engelleme yapmayın

  • Paylaşılan verilere paralel döngü yazma

  • Mümkün olduğunda, yanlış paylaşıma açmaktan kaçının.

  • Değişkenleri bir görev süresi geçerli olduğundan emin olun

Küçük döngü gövdeleri Parallelize değil

Görece küçük bir halka gövdeleri, parallelization ilgili ek yükü paralel işlemeyi faydaları daha ağır planlama için neden olabilir. İki dizide her öğe çiftlerine ekler aşağıdaki örneği inceleyin.

// small-loops.cpp
// compile with: /EHsc
#include <ppl.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
   // Create three arrays that each have the same size.
   const size_t size = 100000;
   int a[size], b[size], c[size];

   // Initialize the arrays a and b.
   for (size_t i = 0; i < size; ++i)
   {
      a[i] = i;
      b[i] = i * 2;
   }

   // Add each pair of elements in arrays a and b in parallel 
   // and store the result in array c.
   parallel_for<size_t>(0, size, [&a,&b,&c](size_t i) {
      c[i] = a[i] + b[i];
   });

   // TODO: Do something with array c.
}

Paralel döngünün her yineleme için iş yükünü paralel işleme yükü yararlanabilmenizi kadar küçüktür. Daha fazla iş döngüsünün gövdesi gerçekleştirerek veya seri olarak döngü gerçekleştirerek Bu döngü performansını artırabilirsiniz.

Top

Olası en yüksek düzeyde paralellik express

Kodu yalnızca en düşük düzeyde parallelize zaman sayısı arttıkça işlemci ölçeklenmez bir Çatal birleştirme yapısı çıkarabilir. A Çatal JOIN burada bir görev onun daha küçük paralel alt görevlere böler ve bu görevleri tamamlamak beklediği bir yapı yapısıdır. Her görevi yinelemeli olarak bölme kendisini içine ek görevleri yapabilirsiniz.

Çatal birleşim modeli çeşitli sorunları çözmek için yararlı olabilir, ancak bazı durumlarda burada ölçeklenebilirlik eşitleme yükünü azaltabilir. Örneğin, görüntü verilerini işleyen seri aşağıdaki kodu düşünün.

// Calls the provided function for each pixel in a Bitmap object.
void ProcessImage(Bitmap* bmp, const function<void (DWORD&)>& f)
{
   int width = bmp->GetWidth();
   int height = bmp->GetHeight();

   // Lock the bitmap.
   BitmapData bitmapData;
   Rect rect(0, 0, bmp->GetWidth(), bmp->GetHeight());
   bmp->LockBits(&rect, ImageLockModeWrite, PixelFormat32bppRGB, &bitmapData);

   // Get a pointer to the bitmap data.
   DWORD* image_bits = (DWORD*)bitmapData.Scan0;

   // Call the function for each pixel in the image.
   for (int y = 0; y < height; ++y)
   {      
      for (int x = 0; x < width; ++x)
      {
         // Get the current pixel value.
         DWORD* curr_pixel = image_bits + (y * width) + x;

         // Call the function.
         f(*curr_pixel);
      }
   }

   // Unlock the bitmap.
   bmp->UnlockBits(&bitmapData);
}

Her döngü tekrarında bağımsız olduğu için işin çoğunu aşağıdaki örnekte gösterildiği gibi parallelize. Bu örnek kullanır concurrency::parallel_for parallelize Dış döngü için algoritma.

// Calls the provided function for each pixel in a Bitmap object.
void ProcessImage(Bitmap* bmp, const function<void (DWORD&)>& f)
{
   int width = bmp->GetWidth();
   int height = bmp->GetHeight();

   // Lock the bitmap.
   BitmapData bitmapData;
   Rect rect(0, 0, bmp->GetWidth(), bmp->GetHeight());
   bmp->LockBits(&rect, ImageLockModeWrite, PixelFormat32bppRGB, &bitmapData);

   // Get a pointer to the bitmap data.
   DWORD* image_bits = (DWORD*)bitmapData.Scan0;

   // Call the function for each pixel in the image.
   parallel_for (0, height, [&, width](int y)
   {      
      for (int x = 0; x < width; ++x)
      {
         // Get the current pixel value.
         DWORD* curr_pixel = image_bits + (y * width) + x;

         // Call the function.
         f(*curr_pixel);
      }
   });

   // Unlock the bitmap.
   bmp->UnlockBits(&bitmapData);
}

Çağırarak bir Çatal birleştirme yapısı aşağıdaki örnekte gösterilmiştir ProcessImage işlevi bir döngü içinde. Her çağrı ProcessImage her alt görev bitinceye kadar dönmez.

// Processes each bitmap in the provided vector.
void ProcessImages(vector<Bitmap*> bitmaps, const function<void (DWORD&)>& f)
{
   for_each(begin(bitmaps), end(bitmaps), [&f](Bitmap* bmp) {
      ProcessImage(bmp, f);
   });
}

Neredeyse hiçbir çalışma ya da paralel döngü tarafından gerçekleştirilen iş yapar ya da paralel döngünün her yineleme, diğer bir deyişle, imbalanced ise, bazı döngü yinelemelerinin diğerlerine göre daha uzun sürer, ek yükü zamanlaması sık fork için gereklidir ve birleşim iş paralel yürütme avantaja daha ağır. Bu ek yükü sayısı arttıkça işlemci artırır.

Planlama Bu örnekte yükünü azaltmak için iç döngü parallelize veya ardışık düzen oluşturma gibi başka bir paralel yapıyı kullanmadan önce dış döngü parallelize. Aşağıdaki örnek ProcessImages işlevi kullanmak için concurrency::parallel_for_each parallelize Dış döngü için algoritma.

// Processes each bitmap in the provided vector.
void ProcessImages(vector<Bitmap*> bitmaps, const function<void (DWORD&)>& f)
{
   parallel_for_each(begin(bitmaps), end(bitmaps), [&f](Bitmap* bmp) {
      ProcessImage(bmp, f);
   });
}

Bir ardışık düzeni paralel olarak görüntü işleme gerçekleştirmek için kullandığı benzer bir örnek için bkz: İzlenecek yol: görüntü işleme ağ oluşturma.

Top

Bölme ve Conquer Generals sorunları çözme için parallel_invoke kullanın

A bölün ve Conquer Generals bir form bir görevi alt görevlere bölmek için özyineleme kullanıp Çatal birleştirme yapının bir sorundur. Ek olarak concurrency::task_group ve concurrency::structured_task_group sınıfları, kullanabileceğiniz concurrency::parallel_invoke bölün ve Conquer Generals sorunlarını çözmek için algoritma. parallel_invoke Algoritması görev grup nesneleri daha fazla Sözün bir sözdizimine sahiptir ve sabit sayıda Paralel Görevler olduğunda kullanışlıdır.

Aşağıdaki örnek kullanımını gösterir parallel_invoke algoritma algoritma sıralama bitonic uygulamak için.

// Sorts the given sequence in the specified order.
template <class T>
void parallel_bitonic_sort(T* items, int lo, int n, bool dir)
{   
   if (n > 1)
   {
      // Divide the array into two partitions and then sort 
      // the partitions in different directions.
      int m = n / 2;

      parallel_invoke(
         [&] { parallel_bitonic_sort(items, lo, m, INCREASING); },
         [&] { parallel_bitonic_sort(items, lo + m, m, DECREASING); }
      );

      // Merge the results.
      parallel_bitonic_merge(items, lo, n, dir);
   }
}

Giderleri, azaltmak için parallel_invoke algoritması arama içeriğine son dizi görevleri gerçekleştirir.

Bu örnek tam sürümü için bkz: Nasıl yapılır: paralel sıralama rutin yazmak için parallel_invoke kullanın. Hakkında daha fazla bilgi için parallel_invoke algoritması Bkz: Paralel algoritmalar.

Top

İptal veya özel durum için sonu paralel bir döngü işleme

ppl bir görev grubu veya paralel algoritması tarafından gerçekleştirilen paralel iş iptal etmek için iki yöntem sağlar. Tarafından sağlanan iptal mekanizmasını kullanmanız için bir yolu olan concurrency::task_group ve concurrency::structured_task_group sınıfları. Başka bir şekilde görev iş işlevinin gövdesinde bir özel durum oluşturmaktır. Özel durum işleme paralel çalışma ağacı iptal etme sırasında daha verimli iptali mekanizmasıdır. A paralel çalışma ağacı ilgili görev grupları görev grupları bazı içeren diğer görev grupları oluşan bir gruptur. İptali mekanizması bir görev grubu ve onun alt görev gruplarını yukarıdan aşağıya doğru bir şekilde iptal eder. Diğer yandan, özel durum işleme aşağıdan yukarıya bir şekilde çalışır ve her alt görev grubu, bağımsız olarak özel durum yukarı doğru yayar gibi iptal etmelisiniz.

Doğrudan bir görev grubu nesnesiyle çalışırken, kullanın concurrency::task_group::cancel veya concurrency::structured_task_group::cancel bu görev grubuna ait iş iptal etmek için yöntemler. Bir paralel algoritması, örneğin, iptal etmek için parallel_for, bir üst görev grubu oluşturun ve bu görev grubu iptal etmek. Örneğin, aşağıdaki işlevi göz önünde parallel_find_any, bir dizi paralel bir değeri arar.

// Returns the position in the provided array that contains the given value, 
// or -1 if the value is not in the array.
template<typename T>
int parallel_find_any(const T a[], size_t count, const T& what)
{
   // The position of the element in the array. 
   // The default value, -1, indicates that the element is not in the array.
   int position = -1;

   // Call parallel_for in the context of a cancellation token to search for the element.
   cancellation_token_source cts;
   run_with_cancellation_token([count, what, &a, &position, &cts]()
   {
      parallel_for(std::size_t(0), count, [what, &a, &position, &cts](int n) {
         if (a[n] == what)
         {
            // Set the return value and cancel the remaining tasks.
            position = n;
            cts.cancel();
         }
      });
   }, cts.get_token());

   return position;
}

Üst görev grubu paralel yinelemeleri birini iptal ettiğinde paralel algoritmalar görev grupları kullandığından, genel görev iptal edildi. Bu örnek tam sürümü için bkz: Nasıl yapılır: iptali için sonu paralel bir döngü kullanın..

Özel durum işleme iptali mekanizması daha paralel iş iptal etmek için daha az etkili bir yol olsa da, özel durum işleme uygun olduğu durumlar vardır. Örneğin, aşağıdaki yöntemi, for_all, yinelemeli olarak her düğümde çalışma işlevini gerçekleştiren bir tree yapısı. Bu örnekte, _children veri üyesi olan bir std::list içeren tree nesneler.

// Performs the given work function on the data element of the tree and
// on each child.
template<class Function>
void tree::for_all(Function& action)
{
   // Perform the action on each child.
   parallel_for_each(begin(_children), end(_children), [&](tree& child) {
      child.for_all(action);
   });

   // Perform the action on this node.
   action(*this);
}

Çağıranın tree::for_all yöntemi bir özel durum iş ağacının her öğe üzerinde çağrılacak işlev gerektirmiyorsa. Aşağıdaki örnekte gösterildiği search_for_value sağlanan bir değeri arar işlevi, tree nesne. search_for_value İşlevi geçerli öğe ağacının sağlanan değerle eşleşen bir özel durum oluşturduğunda, iş işlevi kullanır. search_for_value İşlevini kullanan bir try-catch özel durum yakalamak ve konsola sonucu yazdırmak için blok.

// Searches for a value in the provided tree object.
template <typename T>
void search_for_value(tree<T>& t, int value)
{
   try
   {
      // Call the for_all method to search for a value. The work function
      // throws an exception when it finds the value.
      t.for_all([value](const tree<T>& node) {
         if (node.get_data() == value)
         {
            throw &node;
         }
      });
   }
   catch (const tree<T>* node)
   {
      // A matching node was found. Print a message to the console.
      wstringstream ss;
      ss << L"Found a node with value " << value << L'.' << endl;
      wcout << ss.str();
      return;
   }

   // A matching node was not found. Print a message to the console.
   wstringstream ss;
   ss << L"Did not find node with value " << value << L'.' << endl;
   wcout << ss.str();   
}

Bu örnek tam sürümü için bkz: Nasıl yapılır: özel durum için sonu paralel bir döngü işleme kullanın.

İptal ve ppl tarafından sağlanan özel durum işleme düzeneklerinden hakkında daha fazla genel bilgi için bkz: ppl iptali ve Özel durum işleme eşzamanlılık çalışma zamanında.

Top

Anlamak iptali ve özel durum işleme etkilemesi nesne yok etme

Paralel çalışma ağacında iptal edilirse bir görevi alt görevleri çalışmasını engeller. Alt görevlerden birini kaynak boşaltma gibi uygulamanız için önemli olan bir işlemi gerçekleştirir, bu sorunlara neden olabilir. Ayrıca, görev iptali ile bir nesne yıkıcısının yaymak ve uygulamanızda tanımsız davranışlara neden için bir özel durum neden olabilir.

Aşağıdaki örnekte, Resource bir kaynak sınıfı tanımlar ve Container kaynakları tutan bir kapsayıcı sınıfı tanımlar. Kendi yıkıcı, Container sınıfı çağrıları cleanup iki yöntemi, Resource paralel ve çağrıları üyeleri cleanup yöntemi, üçüncü Resource üye.

// parallel-resource-destruction.h
#pragma once
#include <ppl.h>
#include <sstream>
#include <iostream>

// Represents a resource.
class Resource
{
public:
   Resource(const std::wstring& name)
      : _name(name)
   {
   }

   // Frees the resource.
   void cleanup()
   {
      // Print a message as a placeholder.
      std::wstringstream ss;
      ss << _name << L": Freeing..." << std::endl;
      std::wcout << ss.str();
   }
private:
   // The name of the resource.
   std::wstring _name;
};

// Represents a container that holds resources.
class Container
{
public:
   Container(const std::wstring& name)
      : _name(name)
      , _resource1(L"Resource 1")
      , _resource2(L"Resource 2")
      , _resource3(L"Resource 3")
   {
   }

   ~Container()
   {
      std::wstringstream ss;
      ss << _name << L": Freeing resources..." << std::endl;
      std::wcout << ss.str();

      // For illustration, assume that cleanup for _resource1
      // and _resource2 can happen concurrently, and that 
      // _resource3 must be freed after _resource1 and _resource2.

      concurrency::parallel_invoke(
         [this]() { _resource1.cleanup(); },
         [this]() { _resource2.cleanup(); }
      );

      _resource3.cleanup();
   }

private:
   // The name of the container.
   std::wstring _name;

   // Resources.
   Resource _resource1;
   Resource _resource2;
   Resource _resource3;
};

Bu desen bir sorun yok, kendi sahip olsa da, aşağıdaki kod iki görev paralel olarak çalışan göz önünde bulundurun. İlk görev oluşturur bir Container nesnesi ve İkinci görev genel görev iptal eder. Gösterim amacıyla bu örnek iki kullanır concurrency::event sonra iptali oluştuğunu emin olmak için nesneleri Container nesnesi oluşturulur ve Container iptal işlemi gerçekleştikten sonra nesne yok.

// parallel-resource-destruction.cpp
// compile with: /EHsc
#include "parallel-resource-destruction.h"

using namespace concurrency;
using namespace std;

static_assert(false, "This example illustrates a non-recommended practice.");

int main()
{  
   // Create a task_group that will run two tasks.
   task_group tasks;

   // Used to synchronize the tasks.
   event e1, e2;

   // Run two tasks. The first task creates a Container object. The second task
   // cancels the overall task group. To illustrate the scenario where a child 
   // task is not run because its parent task is cancelled, the event objects 
   // ensure that the Container object is created before the overall task is 
   // cancelled and that the Container object is destroyed after the overall 
   // task is cancelled.

   tasks.run([&tasks,&e1,&e2] {
      // Create a Container object.
      Container c(L"Container 1");

      // Allow the second task to continue.
      e2.set();

      // Wait for the task to be cancelled.
      e1.wait();
   });

   tasks.run([&tasks,&e1,&e2] {
      // Wait for the first task to create the Container object.
      e2.wait();

      // Cancel the overall task.
      tasks.cancel();      

      // Allow the first task to continue.
      e1.set();
   });

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

   wcout << L"Exiting program..." << endl;
}

Bu örnek aşağıdaki çıktıyı üretir:

     

Bu kod örneği, beklediğinizden farklı bir şekilde davranmasına neden olabilir, aşağıdaki sorunları içerir:

  • Üst görev iptaline neden olan çağrısı alt görev concurrency::parallel_invoke, aynı zamanda iptal edilecek. Bu nedenle, bu iki kaynağı serbest değil.

  • İptal üst görevin alt görevi bir iç özel durum neden olur. Çünkü Container bu özel yıkıcı işlemiyor, özel durum yukarı doğru yayılır ve üçüncü kaynak serbest.

  • Alt görev tarafından atılan özel durum üzerinden yayar Container yıkıcı. N bir yıkıcı üretilmesini uygulama tanımsız bir durumuna geçirir.

Bu görevler değil iptal edilmesini garanti edemediği sürece kaynakları, görevleri boşaltma gibi önemli işlemleri gerçekleştirmeyin öneririz. Ayrıca, kendi türleri yıkıcı atabilirsiniz çalışma zamanı işlevlerini kullanmayın öneririz.

Top

Sürekli olarak paralel bir döngüde geçmeyecek

Paralel bir döngü gibi concurrency::parallel_for veya concurrency::parallel_for_each engelleyerek dominated işlemleri kısa bir süre içinde çok sayıda iş parçacığı oluşturmak çalışma zamanı neden olabilir.

Eşzamanlılık Çalışma zamanı görev bitene ya da cooperatively engeller ya da verir ek iş yapar. Çalışma Zamanı paralel bir döngüye girdiğinde Yineleme bloğu başka bir yineleme başlayabilir. Çalışma zamanı kullanılabilir boş iş parçacığı olduğunda, yeni bir iş parçacığı oluşturur.

Bazen blokları paralel gövdesi döngüye girdiğinde, bu mekanizma genel görev akış verimi en üst düzeye çıkarmanıza yardımcı olur. Ancak, çok sayıda yineleme engellediğinizde, çalışma zamanı ek iş çalıştırmak için çok sayıda iş parçacığı oluşturabilir. Bu koşullar düşük bellek veya zayıf donanım kaynaklarının kullanımını neden olabilir.

Çağıran aþaðýdaki örneði ele alalým concurrency::send işlevi her tekrarında bir parallel_for döngü. Çünkü send cooperatively, engeller çalışma zamanı her zaman ek bir iş çalıştırmak için yeni bir iş parçacığı oluşturan send olarak adlandırılır.

// repeated-blocking.cpp
// compile with: /EHsc
#include <ppl.h>
#include <agents.h>

using namespace concurrency;

static_assert(false, "This example illustrates a non-recommended practice.");

int main()
{
   // Create a message buffer.
   overwrite_buffer<int> buffer;

   // Repeatedly send data to the buffer in a parallel loop.
   parallel_for(0, 1000, [&buffer](int i) {

      // The send function blocks cooperatively. 
      // We discourage the use of repeated blocking in a parallel
      // loop because it can cause the runtime to create 
      // a large number of threads over a short period of time.
      send(buffer, i);
   });
}

Bu desen önlemek için kodunuzu yeniden öneririz. Bu örnekte, ek iş parçacığı oluşturma çağırarak önleyebilirsiniz send seri içinde for döngü.

Top

Paralel iş iptal ettiğinizde işlemleri engelleme yapmayın

Çağırmadan önce mümkün olduğunda, engelleme işlemleri gerçekleştirmeyin concurrency::task_group::cancel veya concurrency::structured_task_group::cancel paralel iş iptal etmek için yöntem.

Bir görevi bir cooperative engelleme işlemi gerçekleştirdiğinde, çalışma zamanı veri için ilk görev beklerken diğer iş gerçekleştirebilirsiniz. Bunu engelini kaldırır, çalışma zamanı bekleyen görev reschedules. Çalışma zamanı genellikle daha az engellenmemiş görevleri reschedules daha engellenmemiş görevleri reschedules. Bu nedenle, çalışma zamanı için performansın müşteri adayları engelleyici bir işlem sırasında gereksiz çalışma zamanlayabilirsiniz. Buna göre paralel iş iptal önce engelleyici bir işlem gerçekleştirdiğinizde, çağrı engelleme işlemi geciktirebilir cancel. Bu gereksiz iş gerçekleştirmek için diğer görevler neden olur.

Tanımlayan aþaðýdaki örneði ele alalým parallel_find_answer işlev, sağlanan doðrulama işlev karşılayan sağlanan dizinin bir öğesi için arama yapar. Ne zaman doðrulama fonksiyonunu verir true, paralel iş işlevi oluşturur bir Answer nesne ve genel görev iptal eder.

// blocking-cancel.cpp
// compile with: /c /EHsc
#include <windows.h>
#include <ppl.h>

using namespace concurrency;

// Encapsulates the result of a search operation.
template<typename T>
class Answer
{
public:
   explicit Answer(const T& data)
      : _data(data)
   {
   }

   T get_data() const
   {
      return _data;
   }

   // TODO: Add other methods as needed.

private:
   T _data;

   // TODO: Add other data members as needed.
};

// Searches for an element of the provided array that satisfies the provided
// predicate function.
template<typename T, class Predicate>
Answer<T>* parallel_find_answer(const T a[], size_t count, const Predicate& pred)
{
   // The result of the search.
   Answer<T>* answer = nullptr;
   // Ensures that only one task produces an answer.
   volatile long first_result = 0;

   // Use parallel_for and a task group to search for the element.
   structured_task_group tasks;
   tasks.run_and_wait([&]
   {
      // Declare the type alias for use in the inner lambda function.
      typedef T T;

      parallel_for<size_t>(0, count, [&](const T& n) {
         if (pred(a[n]) && InterlockedExchange(&first_result, 1) == 0)
         {
            // Create an object that holds the answer.
            answer = new Answer<T>(a[n]);
            // Cancel the overall task.
            tasks.cancel();
         }
      });
   });

   return answer;
}

new İþleci bir yığın ayırma engelleyebilir. Çalışma zamanı yalnızca bir cooperative çağrısı gibi çağrı engelleme görevi gerçekleştirdiğinde, diğer iş yapar concurrency::critical_section::lock.

Aşağıdaki örnek, gereksiz iş önlemek ve dolayısıyla performansı artırmak gösterilmiştir. Bu örnek için depolama alanı ayırır önce görev grubu iptal eder Answer nesne.

// Searches for an element of the provided array that satisfies the provided
// predicate function.
template<typename T, class Predicate>
Answer<T>* parallel_find_answer(const T a[], size_t count, const Predicate& pred)
{
   // The result of the search.
   Answer<T>* answer = nullptr;
   // Ensures that only one task produces an answer.
   volatile long first_result = 0;

   // Use parallel_for and a task group to search for the element.
   structured_task_group tasks;
   tasks.run_and_wait([&]
   {
      // Declare the type alias for use in the inner lambda function.
      typedef T T;

      parallel_for<size_t>(0, count, [&](const T& n) {
         if (pred(a[n]) && InterlockedExchange(&first_result, 1) == 0)
         {
            // Cancel the overall task.
            tasks.cancel();
            // Create an object that holds the answer.
            answer = new Answer<T>(a[n]);            
         }
      });
   });

   return answer;
}

Top

Paylaşılan verilere paralel döngü yazma

Örneğin, birkaç veri yapıları, eşzamanlılık çalışma zamanı sağlar concurrency::critical_section, aynı anda paylaşılan verilere erişimi eşitlemek. Birden çok görevi seyrek paylaşılan bir kaynağa erişimi gerekirse, birçok durumda, örneğin, bu veri yapıları yararlıdır.

Kullanan aþaðýdaki örneði ele alalým concurrency::parallel_for_each algoritması ve bir critical_section asal sayıların adedini hesaplamak için nesne bir std::array nesne. Her iş parçacığı paylaşılan değişkene erişmeye beklemeniz gerekir, çünkü bu örnek ölçeklenmez prime_sum.

critical_section cs;
prime_sum = 0;
parallel_for_each(begin(a), end(a), [&](int i) {
   cs.lock();
   prime_sum += (is_prime(i) ? i : 0);
   cs.unlock();
});

Bu örnek ayrıca sık kilitleme işlemi etkili bir şekilde döngü seri hale getirir çünkü kötü performans neden olabilir. Buna eşzamanlılık çalışma zamanı nesne engelleyici bir işlem gerçekleştirdiğinde, Zamanlayıcı ilk iş parçacığı için verileri beklerken diğer işleri gerçekleştirmek için ek bir iş parçacığı oluşturabilir. Çalışma zamanı paylaşılan veri için bekleyen pek çok görev için birçok iş parçacığı oluşturur, uygulama performansının kötü veya düşük kaynak bir il girin.

ppl tanımlar concurrency::combinable sınıfı, kilidi serbest bir şekilde paylaşılan kaynaklara erişim sağlayarak paylaşılan durum ortadan kaldırmak yardımcı olur. combinable Hassas hesaplamalar ve sonra bu hesaplamaların sonucunu birleştirme sağlayan iş parçacığı yerel depolama sınıfı sağlar. Düşünebilirsiniz bir combinable azaltma değişken olarak nesne.

Aşağıdaki örnek kullanarak öncekinin değiştirir bir combinable yerine nesne bir critical_section toplamı hesaplamak için kullanılan nesne. Bu örnek, her iş parçacığının kendi yerel kopyasını toplam tutar çünkü ölçeklendirir. Bu örnek kullanır concurrency::combinable::combine yerel hesaplamaları nihai sonucu birleştirmek için yöntem.

combinable<int> sum;
parallel_for_each(begin(a), end(a), [&](int i) {
   sum.local() += (is_prime(i) ? i : 0);
});
prime_sum = sum.combine(plus<int>());

Bu örnek tam sürümü için bkz: Nasıl yapılır: performansı artırmak için combinable kullan. combinable sınıfı hakkında daha fazla bilgi için, bkz. Paralel kapsayıcıları ve nesneleri.

Top

Mümkün olduğunda, yanlış paylaşıma açmaktan kaçının.

Yanlış paylaşımı ayrı işlemcilerde çalışan aynı anda birden çok görevi aynı önbellek satırda bulunan değişkenler yazdığınızda ortaya çıkar. Bir görev değişkenlerinden biri yazdığında, her iki değişken için önbellek satırı geçersiz kılınır. Önbellek satırı her işlemci önbellek satırı geçersiz kılınan her zaman yeniden yüklemeniz gerekir. Bu nedenle, yanlış paylaşımı performansın uygulamanızda neden olabilir.

Her bir paylaşılan sayacı değişkeni artan aşağıdaki temel örnek iki eşzamanlı görevleri gösterir.

volatile long count = 0L;
concurrency::parallel_invoke(
   [&count] {
      for(int i = 0; i < 100000000; ++i)
         InterlockedIncrement(&count);
   },
   [&count] {
      for(int i = 0; i < 100000000; ++i)
         InterlockedIncrement(&count);
   }
);

İki görev arasında veri paylaşımı ortadan kaldırmak için örnek iki sayacı değişkeni kullanmak üzere değiştirebilirsiniz. Bu örnek, görevleri tamamladıktan sonra son sayaç değeri hesaplar. Ancak, bu yanlış çünkü paylaşımı öğelesini değişkenleri count1 ve count2 aynı önbellek satırında bulunması olasıdır.

long count1 = 0L;
long count2 = 0L;
concurrency::parallel_invoke(
   [&count1] {
      for(int i = 0; i < 100000000; ++i)
         ++count1;
   },
   [&count2] {
      for(int i = 0; i < 100000000; ++i)
         ++count2;
   }
);
long count = count1 + count2;

Yanlış paylaşımı ortadan kaldırmak için bir sayaç değişkenleri ayrı önbellek satırları üzerinde olduğundan emin olmak için yoludur. Aşağıdaki örnek değişkenleri hizalar count1 ve count2 64 baytlık sınırlarda.

__declspec(align(64)) long count1 = 0L;      
__declspec(align(64)) long count2 = 0L;      
concurrency::parallel_invoke(
   [&count1] {
      for(int i = 0; i < 100000000; ++i)
         ++count1;
   },
   [&count2] {
      for(int i = 0; i < 100000000; ++i)
         ++count2;
   }
);
long count = count1 + count2;

Bu örnek, önbellek boyutu 64 veya daha az sayıda bayt olduğunu varsayar.

Kullanmanızı öneririz concurrency::combinable sınıfı, görevler arasında verileri paylaşmalıdır. combinable Sınıfı, iş parçacığı yerel değişkenleri yanlış paylaşım olasılığını azaltacak şekilde oluşturur. combinable sınıfı hakkında daha fazla bilgi için, bkz. Paralel kapsayıcıları ve nesneleri.

Top

Değişkenleri bir görev süresi geçerli olduğundan emin olun

Lambda ifadesi bir görev grubu veya paralel algoritması sağladığınızda, yakalama yan tümcesi gövdesi lambda ifadesi, değere veya başvuruya göre kapsayan kapsam değişkenleri erişen olup olmadığını belirtir. Değişkenler başvuruya göre lambda ifadesi olarak geçirdiğiniz zaman görev bitmeden bu değişkenin yaşam devam etmeleri gerekir.

Tanımlayan aþaðýdaki örneði ele alalým object sınıfı ve perform_action işlev. perform_action İşlev oluşturur bir object değişken ve zaman uyumsuz olarak bu değişken üzerindeki bazı eylemleri gerçekleştirir. Görev önce bitirmek için kesin değildir çünkü perform_action işlevini verir, programın çökmesine veya belirsiz Davranışlar, object görevi çalıştırırken değişken yok.

// lambda-lifetime.cpp
// compile with: /c /EHsc
#include <ppl.h>

using namespace concurrency;

// A type that performs an action.
class object
{
public:
   void action() const
   {
      // TODO: Details omitted for brevity.
   }
};

// Performs an action asynchronously.
void perform_action(task_group& tasks)
{
   // Create an object variable and perform some action on 
   // that variable asynchronously.
   object obj;
   tasks.run([&obj] {
      obj.action();
   });

   // NOTE: The object variable is destroyed here. The program
   // will crash or exhibit unspecified behavior if the task
   // is still running when this function returns.
}

Uygulamanızın gereksinimlerine bağlı olarak, aşağıdaki tekniklerden birini değişkenleri her görev süresi geçerli kalmasını güvence altına almak için kullanabilirsiniz.

Aşağıdaki örnekte geçen object görev değerine göre değişken. Bu nedenle, görev değişken kendi kopyası üzerinde çalışır.

// Performs an action asynchronously.
void perform_action(task_group& tasks)
{
   // Create an object variable and perform some action on 
   // that variable asynchronously.
   object obj;
   tasks.run([obj] {
      obj.action();
   });
}

Çünkü object değişken değeri tarafından geçirilen, bu değişken için gerçekleşen durumu değişiklikleri özgün kopya görünmez.

Aşağıdaki örnek concurrency::task_group::wait yöntemi görev önce tamamlanır emin olmak için perform_action işlevini verir.

// Performs an action.
void perform_action(task_group& tasks)
{
   // Create an object variable and perform some action on 
   // that variable.
   object obj;
   tasks.run([&obj] {
      obj.action();
   });

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

İşlev önce şimdi görev biter çünkü, perform_action işlevi artık davranır zaman uyumsuz.

Aşağıdaki örnek perform_action bir başvuru almak için işlev object değişkeni. Arayan, yaşam süresini garanti gerekir object değişkendir geçerli görev bitene kadar.

// Performs an action asynchronously.
void perform_action(object& obj, task_group& tasks)
{
   // Perform some action on the object variable.
   tasks.run([&obj] {
      obj.action();
   });
}

Bir işaretçi bir görev grubu veya paralel algoritması geçirdiğiniz bir nesnenin yaşam süresini denetlemek için de kullanabilirsiniz.

Lambda ifadeleri hakkında daha fazla bilgi için bkz: Lambda ifadeleri C++.

Top

Ayrıca bkz.

Görevler

İzlenecek yol: görüntü işleme ağ oluşturma

Nasıl yapılır: paralel sıralama rutin yazmak için parallel_invoke kullanın

Nasıl yapılır: iptali için sonu paralel bir döngü kullanın.

Nasıl yapılır: performansı artırmak için combinable kullan

Kavramlar

Paralel Desenler kitaplığının (ppl)

Paralel kapsayıcıları ve nesneleri

Paralel algoritmalar

ppl iptali

Özel durum işleme eşzamanlılık çalışma zamanında

Zaman uyumsuz aracıları Kitaplığı'nda en iyi yöntemler

Eşzamanlılık çalışma zamanında genel en iyi yöntemler

Diğer Kaynaklar

Eşzamanlılık çalışma zamanı en iyi yöntemler