Aracılığıyla paylaş


Paralel Desen Kitaplığı'ndaki En İyi Yöntemler

Bu belgede, Paralel Desen kitaplığının (PPL) etkili bir şekilde nasıl kullanılacağı açıklanır. PPL, ayrıntılı paralellik gerçekleştirmek için genel amaçlı kapsayıcılar, nesneler ve algoritmalar sağlar.

PPL hakkında daha fazla bilgi için bkz . Paralel Desen kitaplığı (PPL).

Bölümler

Bu belgede aşağıdaki bölümler yer alır:

Küçük Döngü Gövdelerini ParalelLeştirmeyin

Görece küçük döngü gövdelerinin paralelleştirilmesi, ilişkili zamanlama ek yükünün paralel işlemenin avantajlarından daha ağır basmasına neden olabilir. İki dizideki her öğe çiftini ekleyen aşağıdaki örneği göz önünde bulundurun.

// 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.
}

Her paralel döngü yinelemesinin iş yükü, paralel işleme yükünden yararlanamayacak kadar küçüktür. Döngü gövdesinde daha fazla iş gerçekleştirerek veya döngünün seri olarak gerçekleştirilerek bu döngünün performansını geliştirebilirsiniz.

[Üst]

Mümkün Olan En Yüksek Düzeyde Hızlı Paralellik

Kodu yalnızca düşük düzeyde paralelleştirdiğinizde, işlemci sayısı arttıkça ölçeklendirilmeyen bir çatal birleştirme yapısı tanıtabilirsiniz. Çatal birleştirme yapısı, bir görevin çalışmasını daha küçük paralel alt görevlere böldüğü ve bu alt görevlerin tamamlanmasını beklediği bir yapıdır. Her alt görev özyinelemeli olarak kendisini ek alt görevlere bölebilir.

Çatal birleştirme modeli çeşitli sorunları çözmek için yararlı olsa da, eşitleme ek yükünün ölçeklenebilirliği azaltabileceği durumlar vardır. Örneğin, görüntü verilerini işleyen aşağıdaki seri kodu göz önünde bulundurun.

// 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ü yinelemesi bağımsız olduğundan, aşağıdaki örnekte gösterildiği gibi çalışmanın büyük bir kısmını paralelleştirebilirsiniz. Bu örnekte, dış döngüye paralel hale getirmek için concurrency::p arallel_for algoritması kullanılır.

// 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şağıdaki örnekte işlevi döngü içinde çağırarak ProcessImage bir çatal-birleştirme yapısı gösterilmektedir. her çağrısı ProcessImage , her alt görev bitene kadar döndürülmez.

// 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);
   });
}

Paralel döngünün her yinelemesi neredeyse hiç çalışma gerçekleştirmiyorsa veya paralel döngü tarafından gerçekleştirilen iş dengesizse, yani bazı döngü yinelemeleri diğerlerinden daha uzun sürerse, sık sık çatal ve birleştirme çalışması için gereken zamanlama ek yükü paralel yürütmenin avantajından daha ağır basabilir. İşlemci sayısı arttıkça bu ek yük artar.

Bu örnekteki zamanlama ek yükünü azaltmak için iç döngüleri paralelleştirmeden önce dış döngüleri paralelleştirebilir veya ardışık düzen gibi başka bir paralel yapı kullanabilirsiniz. Aşağıdaki örnek, dış döngüye ProcessImages paralel hale getirmek için işlevi concurrency::p arallel_for_each algoritmasını kullanacak şekilde değiştirir.

// 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);
   });
}

Paralel olarak görüntü işleme gerçekleştirmek için işlem hattı kullanan benzer bir örnek için bkz . İzlenecek Yol: Görüntü İşleme Ağı Oluşturma.

[Üst]

Bölme ve Fethet Sorunlarını Çözmek için parallel_invoke Kullanma

Bölme ve fethetme sorunu, bir görevi alt görevlere bölmek için özyineleme kullanan çatal-birleştirme yapısının bir biçimidir. Eşzamanlılık::task_group ve eşzamanlılık::structured_task_group sınıflarına ek olarak, divide-and-conquer sorunlarını çözmek için concurrency::p arallel_invoke algoritmasını da kullanabilirsiniz. Algoritma, parallel_invoke görev grubu nesnelerinden daha kısa bir söz dizimine sahiptir ve sabit sayıda paralel göreviniz olduğunda kullanışlıdır.

Aşağıdaki örnekte, bitonik sıralama algoritmasını parallel_invoke uygulamak için algoritmanın kullanımı gösterilmektedir.

// 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);
   }
}

Ek yükü azaltmak için algoritma, parallel_invoke çağrı bağlamındaki son görev serisini gerçekleştirir.

Bu örneğin tam sürümü için bkz . Nasıl yapılır: Paralel Sıralama Yordamı Yazmak için parallel_invoke Kullanma. Algoritma hakkında parallel_invoke daha fazla bilgi için bkz . Paralel Algoritmalar.

[Üst]

Paralel Döngüden Ayrılmak için İptal veya Özel Durum İşleme kullanma

PPL, bir görev grubu veya paralel algoritma tarafından gerçekleştirilen paralel çalışmayı iptal etmek için iki yol sağlar. Bunun bir yolu eşzamanlılık::task_group ve eşzamanlılık::structured_task_group sınıfları tarafından sağlanan iptal mekanizmasını kullanmaktır. Diğer yol, bir görev çalışma işlevinin gövdesinde bir özel durum oluşturmaktır. İptal mekanizması, paralel çalışma ağacını iptal etmede özel durum işlemeden daha verimlidir. Paralel iş ağacı , bazı görev gruplarının başka görev grupları içerdiği ilişkili görev grupları grubudur. İptal mekanizması, bir görev grubunu ve alt görev gruplarını yukarıdan aşağıya doğru iptal eder. Buna karşılık, özel durum işleme aşağıdan yukarıya doğru çalışır ve özel durum yukarı doğru yayıldıkça her alt görev grubunu bağımsız olarak iptal etmelidir.

Doğrudan bir görev grubu nesnesiyle çalışırken, bu görev grubuna ait çalışmayı iptal etmek için eşzamanlılık::task_group::cancel veya concurrency::structured_task_group::cancel yöntemlerini kullanın. Paralel algoritmayı iptal etmek için, örneğin, parallel_forbir üst görev grubu oluşturun ve bu görev grubunu iptal edin. Örneğin, parallel_find_anydizideki bir değeri paralel olarak arayan aşağıdaki işlevini göz önünde bulundurun.

// 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;
}

Paralel algoritmalar görev gruplarını kullandığından, paralel yinelemelerden biri üst görev grubunu iptal ettiğinde, genel görev iptal edilir. Bu örneğin tam sürümü için bkz . How to: Use Cancellation to Break from a Parallel Loop.

Özel durum işleme, paralel çalışmayı iptal etme mekanizmasından daha az verimli bir yol olsa da, özel durum işlemenin uygun olduğu durumlar vardır. Örneğin, aşağıdaki yöntemi, for_allbir yapının her düğümünde özyinelemeli olarak bir tree iş işlevi gerçekleştirir. Bu örnekte, _children veri üyesi nesneleri içeren tree bir std::list'dir.

// 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);
}

yöntemini çağıran tree::for_all , iş işlevinin ağacın her öğesinde çağrılması gerekmemesi durumunda bir özel durum oluşturabilir. Aşağıdaki örnekte, sağlanan tree nesnede search_for_value bir değer arayan işlevi gösterilmektedir. işlevi, search_for_value ağacın geçerli öğesi sağlanan değerle eşleştiğinde özel durum oluşturan bir iş işlevi kullanır. İşlev, search_for_value özel durumu yakalamak ve sonucu konsola yazdırmak için bir try-catch blok kullanır.

// 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 örneğin tam sürümü için bkz . Nasıl yapılır: Paralel Döngüden Ayrılmak için Özel Durum İşlemeyi Kullanma.

PPL tarafından sağlanan iptal ve özel durum işleme mekanizmaları hakkında daha fazla genel bilgi için bkz . PPL ve Özel Durum İşleme'de İptal.

[Üst]

İptal ve Özel Durum İşlemenin Nesne Yok Edilmesini Nasıl Etkilediğini Anlama

Paralel çalışma ağacında, iptal edilen bir görev alt görevlerin çalışmasını engeller. Alt görevlerden biri uygulamanız için önemli olan bir işlemi gerçekleştirirse (örneğin, bir kaynağı boşaltma) bu sorunlara neden olabilir. Ayrıca, görev iptali bir özel durumun bir nesne yok edicisi aracılığıyla yayılmasına ve uygulamanızda tanımsız davranışlara neden olabilir.

Aşağıdaki örnekte sınıfı Resource bir kaynağı, sınıfı ise kaynakları barındıran bir kapsayıcıyı Container açıklar. yıkıcısındaContainer, sınıfı iki üyesinde Resource yöntemini paralel olarak çağırır cleanup ve ardından üçüncü Resource üyesinde cleanup yöntemini çağırır.

// 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 düzenin kendi başına hiçbir sorunu olmasa da, iki görevi paralel olarak çalıştıran aşağıdaki kodu göz önünde bulundurun. İlk görev bir Container nesne oluşturur ve ikinci görev genel görevi iptal eder. Örnek, çizim için iki eşzamanlılık::olay nesnesi kullanarak iptalin nesne oluşturulduktan sonra Container gerçekleştiğinden ve iptal işlemi gerçekleştikten sonra nesnenin Container yok olduğundan emin olur.

// 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 çıkışı oluşturur:

Container 1: Freeing resources...Exiting program...

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

  • Üst görevin iptali alt görevin, concurrency::p arallel_invoke çağrısının da iptal edilmesine neden olur. Bu nedenle, bu iki kaynak serbest bırakılmaz.

  • Üst görevin iptali, alt görevin iç özel durum oluşturmasına neden olur. Container Yıkıcı bu özel durumu işlemediğinden, özel durum yukarı doğru yayılır ve üçüncü kaynak serbest bırakılır.

  • Alt görev tarafından oluşan özel durum, yıkıcı aracılığıyla Container yayılır. Bir yıkıcıdan oluşturma, uygulamayı tanımsız bir duruma getirir.

Bu görevlerin iptal edilmeyeceklerini garanti etmediğiniz sürece görevlerde kaynakların boşaltılması gibi kritik işlemleri gerçekleştirmemenizi öneririz. Ayrıca, türlerinizin yıkıcısını atabilecek çalışma zamanı işlevselliğini kullanmamanızı öneririz.

[Üst]

Paralel Döngüde Art Arda Engelleme

Engelleme işlemlerinin hakim olduğu eşzamanlılık::p arallel_for veya concurrency::p arallel_for_each gibi paralel bir döngü, çalışma zamanının kısa bir süre içinde çok sayıda iş parçacığı oluşturmasına neden olabilir.

Eşzamanlılık Çalışma Zamanı, bir görev tamamlandığında veya işbirliğiyle engellendiğinde veya verimlendiğinde ek iş gerçekleştirir. Bir paralel döngü yinelemesi engellendiğinde çalışma zamanı başka bir yinelemeye başlayabilir. Kullanılabilir boşta iş parçacığı olmadığında, çalışma zamanı yeni bir iş parçacığı oluşturur.

Paralel döngünün gövdesi bazen blokladığında, bu mekanizma genel görev aktarım hızını en üst düzeye çıkarmaya yardımcı olur. Ancak, birçok yineleme engellendiğinde, çalışma zamanı ek çalışmayı çalıştırmak için birçok iş parçacığı oluşturabilir. Bu, yetersiz bellek koşullarına veya donanım kaynaklarının kötü kullanımına neden olabilir.

Bir döngünün her yinelemesinde parallel_for eşzamanlılık::send işlevini çağıran aşağıdaki örneği göz önünde bulundurun. İşbirlikçi send olarak engellendiğinden, çalışma zamanı her send çağrıldığında ek iş çalıştırmak için yeni bir iş parçacığı oluşturur.

// 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 düzeni önlemek için kodunuzu yeniden düzenlemenizi öneririz. Bu örnekte, seri for döngüde çağırarak send ek iş parçacıklarının oluşturulmasını önleyebilirsiniz.

[Üst]

Paralel Çalışmayı İptal Ettiğinizde Engelleme İşlemleri Gerçekleştirmeyin

Mümkün olduğunda, paralel çalışmayı iptal etmek için eşzamanlılık::task_group::cancel veya eşzamanlılık::structured_task_group::cancel yöntemini çağırmadan önce engelleme işlemleri gerçekleştirmeyin.

Bir görev işbirliğine dayalı bir engelleme işlemi gerçekleştirdiğinde, ilk görev verileri beklerken çalışma zamanı başka bir iş gerçekleştirebilir. Çalışma zamanı, engellemeyi kaldırdığında bekleyen görevi yeniden zamanlar. Çalışma zamanı genellikle daha kısa süre önce engeli kaldırılan görevleri yeniden zamanlamadan önce engeli kaldırılan görevleri yeniden zamanlar. Bu nedenle, çalışma zamanı engelleme işlemi sırasında gereksiz çalışma zamanlayabilir ve bu da performansın düşmesine neden olur. Buna göre, paralel çalışmayı iptal etmeden önce bir engelleme işlemi gerçekleştirdiğinizde, engelleme işlemi çağrısının gecikmesine cancelneden olabilir. Bu, diğer görevlerin gereksiz işler gerçekleştirmesine neden olur.

Sağlanan koşul işlevini karşılayan sağlanan dizinin bir öğesini arayan işlevi tanımlayan parallel_find_answer aşağıdaki örneği göz önünde bulundurun. Koşul işlevi döndürdüğünde trueparalel çalışma işlevi bir Answer nesne oluşturur ve genel görevi 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;
}

işleci new , engelleyebilecek bir yığın ayırması gerçekleştirir. Çalışma zamanı diğer işleri yalnızca görev eşzamanlılık::critical_section::lock çağrısı gibi bir işbirliği engelleme çağrısı gerçekleştirdiğinde gerçekleştirir.

Aşağıdaki örnek, gereksiz çalışmayı önlemeyi ve böylece performansı iyileştirmeyi gösterir. Bu örnek, nesne için depolamayı ayırmadan önce görev grubunu iptal eder Answer .

// 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;
}

[Üst]

Paralel Döngüde Paylaşılan Verilere Yazma

Eşzamanlılık Çalışma Zamanı, paylaşılan verilere eşzamanlı erişimi eşitleyen eşzamanlılık::critical_section gibi çeşitli veri yapıları sağlar. Bu veri yapıları, birçok durumda, örneğin birden çok görev bir kaynağa sık sık paylaşılan erişim gerektirdiğinde yararlıdır.

Concurrency::p arallel_for_each algoritmasını ve std::array nesnesindeki asal sayı sayısını hesaplamak için bir critical_sectionnesneyi kullanan aşağıdaki örneği düşünün. Her iş parçacığının paylaşılan değişkene prime_sumerişmek için beklemesi gerektiğinden bu örnek ölçeklendirilmiyor.

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();
});

Sık kilitleme işlemi döngünün etkili bir şekilde seri hale getirildiği için bu örnek performansın düşmesine de neden olabilir. Buna ek olarak, eşzamanlılık çalışma zamanı nesnesi bir engelleme işlemi gerçekleştirdiğinde zamanlayıcı, ilk iş parçacığı verileri beklerken diğer işleri gerçekleştirmek için ek bir iş parçacığı oluşturabilir. Çalışma zamanı, paylaşılan verileri bekleyen birçok görev olduğundan çok sayıda iş parçacığı oluşturursa, uygulama kötü performans sergileyebilir veya düşük kaynak durumu girebilir.

PPL, paylaşılan kaynaklara kilitsiz bir şekilde erişim sağlayarak paylaşılan durumu ortadan kaldırmanıza yardımcı olan eşzamanlılık::combinable sınıfını tanımlar. sınıfı, combinable ayrıntılı hesaplamalar gerçekleştirmenize ve ardından bu hesaplamaları nihai bir sonuçla birleştirmenize olanak tanıyan iş parçacığı yerel depolama alanı sağlar. Nesneyi combinable azaltma değişkeni olarak düşünebilirsiniz.

Aşağıdaki örnek, toplamı hesaplamak için nesne combinable yerine bir nesne kullanarak öncekini critical_section değiştirir. Her iş parçacığının toplamın kendi yerel kopyasını tutması nedeniyle bu örnek ölçeklendirilir. Bu örnekte, yerel hesaplamaları nihai sonuçla birleştirmek için concurrency::combinable::combine yöntemi kullanılır.

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 örneğin tam sürümü için bkz . Nasıl yapılır: Performansı Artırmak için Birleştirilebilir'i kullanma. sınıfı hakkında combinable daha fazla bilgi için bkz . Paralel Kapsayıcılar ve Nesneler.

[Üst]

Mümkün Olduğunda Yanlış Paylaşımdan Kaçının

Yanlış paylaşım , ayrı işlemcilerde çalışan birden çok eşzamanlı görev aynı önbellek satırında bulunan değişkenlere yazıldığında oluşur. Bir görev değişkenlerden birine yazdığında, her iki değişkenin önbellek satırı geçersiz kılınır. Önbellek satırı her geçersiz kılınışında her işlemcinin önbellek satırını yeniden yüklemesi gerekir. Bu nedenle, hatalı paylaşım uygulamanızda performansın düşmesine neden olabilir.

Aşağıdaki temel örnekte, her birinin paylaşılan bir sayaç değişkenini artırdığını gösteren iki eşzamanlı görev gösterilmektedir.

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ını ortadan kaldırmak için, örneği iki sayaç değişkeni kullanacak şekilde değiştirebilirsiniz. Bu örnek, görevler tamamlandıktan sonra son sayaç değerini hesaplar. Ancak, değişkenlerin count1 ve count2 aynı önbellek satırında bulunma olasılığı yüksek olduğundan, bu örnekte hatalı paylaşım gösterilir.

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ırmanın bir yolu, sayaç değişkenlerinin ayrı önbellek satırlarında olduğundan emin olmaktır. Aşağıdaki örnek, değişkenleri ve count2 64 baytlık sınırları hizalarcount1.

__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 örnekte, bellek önbelleği boyutunun 64 veya daha az bayt olduğu varsayılır.

Görevler arasında veri paylaşmanız gerektiğinde eşzamanlılık::combinable sınıfını kullanmanızı öneririz. sınıfı, combinable yanlış paylaşımın daha az olası olduğu şekilde iş parçacığı yerel değişkenleri oluşturur. sınıfı hakkında combinable daha fazla bilgi için bkz . Paralel Kapsayıcılar ve Nesneler.

[Üst]

Bir Görevin Ömrü Boyunca Değişkenlerin Geçerli Olduğundan Emin Olun

Bir görev grubuna veya paralel algoritmaya lambda ifadesi sağladığınızda, capture yan tümcesi lambda ifadesinin gövdesinin kapsayan kapsamdaki değişkenlere değere veya başvuruya göre erişip erişmeyeceğini belirtir. Başvuruya göre bir lambda ifadesine değişken geçirdiğinizde, görev bitene kadar bu değişkenin ömrünün devam ettiğini garanti etmeniz gerekir.

sınıfını ve perform_action işlevini tanımlayan object aşağıdaki örneği göz önünde bulundurun. perform_action işlevi bir object değişken oluşturur ve bu değişken üzerinde zaman uyumsuz olarak bazı eylemler gerçekleştirir. İşlev dönmeden önce perform_action görevin bitmesi garanti edilmediğinden, görev çalışırken değişken yok edilirse object program kilitlenir veya belirtilmeyen davranışlar sergiler.

// 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, değişkenlerin her görevin ömrü boyunca geçerli kalmasını sağlamak için aşağıdaki tekniklerden birini kullanabilirsiniz.

Aşağıdaki örnek, değişkeni değere object göre göreve geçirir. Bu nedenle, görev değişkenin 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();
   });
}

Değişken değere göre geçirildiğinden object , bu değişkende gerçekleşen durum değişiklikleri özgün kopyada görünmez.

Aşağıdaki örnek, işlevin döndürülmeden önce perform_action görevin tamamlandığından emin olmak için eşzamanlılık::task_group::wait yöntemini kullanır.

// 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();
}

Görev artık işlev döndürülmeden önce sona erdiğinden, perform_action işlev artık zaman uyumsuz olarak davranmaz.

Aşağıdaki örnek işlevi değişkene başvuru alacak şekilde object değiştirirperform_action. Çağıran, görev bitene kadar değişkenin object ömrünün geçerli olduğunu garanti etmelidir.

// 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 görev grubuna veya paralel algoritmaya geçirdiğiniz nesnenin ömrünü denetlemek için de işaretçi kullanabilirsiniz.

Lambda ifadeleri hakkında daha fazla bilgi için bkz . Lambda İfadeleri.

[Üst]

Ayrıca bkz.

Eşzamanlılık Çalışma Zamanı En İyi Yöntemleri
Paralel Desen Kitaplığı (PPL)
Paralel Kapsayıcılar ve Nesneler
Paralel Algoritmalar
PPL'de İptal
Özel Durum İşleme
İzlenecek yol: Görüntü İşleme Ağı Oluşturma
Nasıl yapılır: Paralel Sıralama Rutini Yazmak için parallel_invoke Kullanma
Nasıl yapılır: Paralel Bir Döngüden Kurtulmak için İptal
Nasıl yapılır: Performansı arttırmak için combinable Kullanma
Zaman Uyumsuz Aracılar Kitaplığı'ndaki En İyi Yöntemler
Eşzamanlılık Çalışma Zamanındaki Genel En İyi Yöntemler