Eşzamanlılık Çalışma Zamanındaki Genel En İyi Yöntemler

Bu belgede Eşzamanlılık Çalışma Zamanı'nın birden çok alanına uygulanan en iyi yöntemler açıklanmaktadır.

Bölümler

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

Mümkün Olduğunda Kooperatif Eşitleme Yapılarını Kullanma

Eşzamanlılık Çalışma Zamanı, dış eşitleme nesnesi gerektirmeyen eşzamanlılık açısından güvenli birçok yapı sağlar. Örneğin, eşzamanlılık::concurrent_vector sınıfı eşzamanlılık açısından güvenli ekleme ve öğe erişim işlemleri sağlar. Burada eşzamanlılık açısından güvenli, işaretçilerin veya yineleyicilerin her zaman geçerli olduğu anlamına gelir. Bu, öğe başlatma veya belirli bir dolaşma sırasının garantisi değildir. Ancak, bir kaynağa özel erişim gerektiren durumlarda çalışma zamanı eşzamanlılık::critical_section, eşzamanlılık::reader_writer_lock ve eşzamanlılık::olay sınıflarını sağlar. Bu türler işbirliğiyle davranır; bu nedenle, ilk görev verileri beklerken görev zamanlayıcı işleme kaynaklarını başka bir bağlama yeniden dağıtabilir. Mümkün olduğunda, işbirliği yapmayan Windows API'sinin sağladığı eşitleme mekanizmaları gibi diğer eşitleme mekanizmaları yerine bu eşitleme türlerini kullanın. Bu eşitleme türleri ve kod örneği hakkında daha fazla bilgi için bkz . Eşitleme Veri Yapıları ve Eşitleme Veri Yapılarını Windows API ile Karşılaştırma.

[Üst]

Verim Vermeyen Uzun Görevlerden Kaçının

Görev zamanlayıcı işbirliğiyle davrandığından, görevler arasında eşitlik sağlamaz. Bu nedenle, bir görev diğer görevlerin başlatılmasını engelleyebilir. Bu bazı durumlarda kabul edilebilir olsa da, diğer durumlarda bu kilitlenmeye veya açlıktan dolayı olabilir.

Aşağıdaki örnek, ayrılan işleme kaynaklarının sayısından daha fazla görev gerçekleştirir. İlk görev, görev zamanlayıcıya teslim etmez ve bu nedenle ilk görev bitene kadar ikinci görev başlatılmaz.

// cooperative-tasks.cpp
// compile with: /EHsc
#include <ppl.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

// Data that the application passes to lightweight tasks.
struct task_data_t
{
   int id;  // a unique task identifier.
   event e; // signals that the task has finished.
};

// A lightweight task that performs a lengthy operation.
void task(void* data)
{   
   task_data_t* task_data = reinterpret_cast<task_data_t*>(data);

   // Create a large loop that occasionally prints a value to the console.
   int i;
   for (i = 0; i < 1000000000; ++i)
   {
      if (i > 0 && (i % 250000000) == 0)
      {
         wstringstream ss;
         ss << task_data->id << L": " << i << endl;
         wcout << ss.str();
      }
   }
   wstringstream ss;
   ss << task_data->id << L": " << i << endl;
   wcout << ss.str();

   // Signal to the caller that the thread is finished.
   task_data->e.set();
}

int wmain()
{
   // For illustration, limit the number of concurrent 
   // tasks to one.
   Scheduler::SetDefaultSchedulerPolicy(SchedulerPolicy(2, 
      MinConcurrency, 1, MaxConcurrency, 1));

   // Schedule two tasks.

   task_data_t t1;
   t1.id = 0;
   CurrentScheduler::ScheduleTask(task, &t1);

   task_data_t t2;
   t2.id = 1;
   CurrentScheduler::ScheduleTask(task, &t2);

   // Wait for the tasks to finish.

   t1.e.wait();
   t2.e.wait();
}

Bu örnek aşağıdaki çıkışı oluşturur:

1: 250000000 1: 500000000 1: 750000000 1: 1000000000 2: 250000000 2: 500000000 2: 750000000 2: 1000000000

İki görev arasında işbirliğini etkinleştirmenin çeşitli yolları vardır. Bunun bir yolu, uzun süre çalışan bir görevde zaman zaman görev zamanlayıcıya verim sağlamaktır. Aşağıdaki örnek işlevi, başka bir görevin çalıştırabilmesi için görev zamanlayıcıya yürütmeyi sağlamak üzere eşzamanlılık::Context::Yield yöntemini çağıracak şekilde değiştirirtask.

// A lightweight task that performs a lengthy operation.
void task(void* data)
{   
   task_data_t* task_data = reinterpret_cast<task_data_t*>(data);

   // Create a large loop that occasionally prints a value to the console.
   int i;
   for (i = 0; i < 1000000000; ++i)
   {
      if (i > 0 && (i % 250000000) == 0)
      {
         wstringstream ss;
         ss << task_data->id << L": " << i << endl;
         wcout << ss.str();

         // Yield control back to the task scheduler.
         Context::Yield();
      }
   }
   wstringstream ss;
   ss << task_data->id << L": " << i << endl;
   wcout << ss.str();

   // Signal to the caller that the thread is finished.
   task_data->e.set();
}

Bu örnek aşağıdaki çıkışı oluşturur:

1: 250000000
2: 250000000
1: 500000000
2: 500000000
1: 750000000
2: 750000000
1: 1000000000
2: 1000000000

Context::Yield yöntemi, geçerli iş parçacığının ait olduğu zamanlayıcıda yalnızca başka bir etkin iş parçacığı, basit bir görev veya başka bir işletim sistemi iş parçacığı verir. Bu yöntem, eşzamanlılık::task_group veya eşzamanlılık::structured_task_group nesnesinde çalıştırılacak şekilde zamanlanmış ancak henüz başlatılmamış olan çalışmayı sağlamaz.

Uzun süre çalışan görevler arasında işbirliğini etkinleştirmenin başka yolları da vardır. Büyük bir görevi daha küçük alt görevlere bölebilirsiniz. Ayrıca uzun bir görev sırasında fazla aboneliği etkinleştirebilirsiniz. Fazla abonelik, kullanılabilir sayıda donanım iş parçacığından daha fazla iş parçacığı oluşturmanıza olanak tanır. Fazla abonelik, uzun bir görev, örneğin diskten veya ağ bağlantısından veri okuma gibi yüksek gecikme süresi içerdiğinde özellikle yararlıdır. Basit görevler ve fazla abonelik hakkında daha fazla bilgi için bkz . Görev Zamanlayıcı.

[Üst]

Yüksek Gecikme Süresi Olan veya Engelleyen İşlemleri Dengelemek için Fazla Abonelik Kullanma

Eşzamanlılık Çalışma Zamanı, eşzamanlılık::critical_section gibi görevlerin işbirliğiyle engellenmesini ve birbirine verim sağlamasını sağlayan eşitleme temel öğelerini sağlar. Bir görev işbirliğiyle engellendiğinde veya veridiğinde, ilk görev verileri beklerken görev zamanlayıcı işlem kaynaklarını başka bir bağlama yeniden yerleştirebilir.

Eşzamanlılık Çalışma Zamanı tarafından sağlanan işbirliği engelleme mekanizmasını kullanamayacağınız durumlar vardır. Örneğin, kullandığınız bir dış kitaplık farklı bir eşitleme mekanizması kullanabilir. Başka bir örnek, örneğin ağ bağlantısından veri okumak için Windows API ReadFile işlevini kullandığınızda yüksek gecikme süresine sahip olabilecek bir işlem gerçekleştirdiğinizdedir. Bu gibi durumlarda, fazla abonelik başka bir görev boşta olduğunda diğer görevlerin çalıştırılmasını sağlayabilir. Fazla abonelik, kullanılabilir sayıda donanım iş parçacığından daha fazla iş parçacığı oluşturmanıza olanak tanır.

Verilen URL'de dosyayı indiren aşağıdaki işlevini downloadgöz önünde bulundurun. Bu örnekte etkin iş parçacığı sayısını geçici olarak artırmak için eşzamanlılık::Context::Oversubscribe yöntemi kullanılır.

// Downloads the file at the given URL.
string download(const string& url)
{
   // Enable oversubscription.
   Context::Oversubscribe(true);

   // Download the file.
   string content = GetHttpFile(_session, url.c_str());
   
   // Disable oversubscription.
   Context::Oversubscribe(false);

   return content;
}

GetHttpFile İşlev olası bir gizli işlem gerçekleştirdiğinden, aşırı abonelik, geçerli görev verileri beklerken diğer görevlerin çalıştırılmasını sağlayabilir. Bu örneğin tam sürümü için bkz . How to: Use Oversubscription to Offset Latency.

[Üst]

Mümkün Olduğunda Eşzamanlı Bellek Yönetimi İşlevleri Kullanma

Görece kısa bir ömrü olan küçük nesneleri sık sık ayıran ayrıntılı görevleriniz olduğunda, bellek yönetimi işlevlerini (eşzamanlılık::Ayırma ve eşzamanlılık::Ücretsiz) kullanın. Eşzamanlılık Çalışma Zamanı, çalışan her iş parçacığı için ayrı bir bellek önbelleği tutar. Alloc ve Free işlevleri, kilitler veya bellek engelleri kullanmadan bu önbelleklerden bellek ayırır ve boşaltır.

Bu bellek yönetimi işlevleri hakkında daha fazla bilgi için bkz . Görev Zamanlayıcı. Bu işlevleri kullanan bir örnek için bkz . Nasıl yapılır: Bellek Performansını Geliştirmek için Ayırma ve Ücretsiz Kullanma.

[Üst]

Eşzamanlılık Nesnelerinin Ömrünü Yönetmek için RAII Kullanma

Eşzamanlılık Çalışma Zamanı, iptal gibi özellikleri uygulamak için özel durum işlemeyi kullanır. Bu nedenle, çalışma zamanına çağırdığınızda veya çalışma zamanına çağıran başka bir kitaplığı çağırdığınızda özel durum açısından güvenli kod yazın.

Kaynak Alımı Başlatma (RAII) deseni, belirli bir kapsam altındaki eşzamanlılık nesnesinin ömrünü güvenli bir şekilde yönetmenin bir yoludur. RAII deseni altında, yığında bir veri yapısı ayrılır. Bu veri yapısı oluşturulduğunda bir kaynağı başlatır veya alır ve veri yapısı yok edildiğinde bu kaynağı yok eder veya serbest bırakır. RAII deseni, kapsayan kapsam çıkmadan önce yıkıcının çağrılmasını garanti eder. Bu düzen, bir işlev birden çok return deyim içerdiğinde kullanışlıdır. Bu desen, özel durum açısından güvenli kod yazmanıza da yardımcı olur. Bir throw deyim yığının gevşemesine neden olduğunda, RAII nesnesinin yıkıcısı çağrılır; bu nedenle kaynak her zaman doğru bir şekilde silinir veya serbest bırakılır.

Çalışma zamanı, EŞZAMANLıLIK::critical_section::scoped_lock ve eşzamanlılık::reader_writer_lock::scoped_lock gibi RAII desenini kullanan birkaç sınıf tanımlar. Bu yardımcı sınıflar kapsamlı kilitler olarak bilinir. Bu sınıflar eşzamanlılık::critical_section veya eşzamanlılık::reader_writer_lock nesneleriyle çalışırken çeşitli avantajlar sağlar. Bu sınıfların oluşturucu sağlanan veya reader_writer_lock nesnesine critical_section erişim alır; yıkıcı bu nesneye erişimi serbest bırakır. Kapsamlı bir kilit, yok edildiğinde karşılıklı dışlama nesnesine erişimi otomatik olarak serbest bıraktığından, temel alınan nesnenin kilidini el ile açmazsınız.

Bir dış kitaplık tarafından tanımlanan ve bu nedenle değiştirilemeyen aşağıdaki sınıfı accountgöz önünde bulundurun.

// account.h
#pragma once
#include <exception>
#include <sstream>

// Represents a bank account.
class account
{
public:
   explicit account(int initial_balance = 0)
      : _balance(initial_balance)
   {
   }

   // Retrieves the current balance.
   int balance() const
   {
      return _balance;
   }

   // Deposits the specified amount into the account.
   int deposit(int amount)
   {
      _balance += amount;
      return _balance;
   }

   // Withdraws the specified amount from the account.
   int withdraw(int amount)
   {
      if (_balance < 0)
      {
         std::stringstream ss;
         ss << "negative balance: " << _balance << std::endl;
         throw std::exception((ss.str().c_str()));
      }

      _balance -= amount;
      return _balance;
   }

private:
   // The current balance.
   int _balance;
};

Aşağıdaki örnek, bir account nesne üzerinde paralel olarak birden çok işlem gerçekleştirir. Sınıfı eşzamanlılık açısından güvenli olmadığından, nesneye erişimi eşitlemek için account örnek bir critical_section nesne account kullanır. Her paralel işlem, işlem başarılı olduğunda veya başarısız olduğunda nesnenin critical_section kilidinin açılmasını sağlamak için bir critical_section::scoped_lock nesne kullanır. Hesap bakiyesi negatif olduğunda, withdraw işlem bir özel durum oluşturarak başarısız olur.

// account-transactions.cpp
// compile with: /EHsc
#include "account.h"
#include <ppl.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

int wmain()
{
   // Create an account that has an initial balance of 1924.
   account acc(1924);

   // Synchronizes access to the account object because the account class is 
   // not concurrency-safe.
   critical_section cs;

   // Perform multiple transactions on the account in parallel.   
   try
   {
      parallel_invoke(
         [&acc, &cs] {
            critical_section::scoped_lock lock(cs);
            wcout << L"Balance before deposit: " << acc.balance() << endl;
            acc.deposit(1000);
            wcout << L"Balance after deposit: " << acc.balance() << endl;
         },
         [&acc, &cs] {
            critical_section::scoped_lock lock(cs);
            wcout << L"Balance before withdrawal: " << acc.balance() << endl;
            acc.withdraw(50);
            wcout << L"Balance after withdrawal: " << acc.balance() << endl;
         },
         [&acc, &cs] {
            critical_section::scoped_lock lock(cs);
            wcout << L"Balance before withdrawal: " << acc.balance() << endl;
            acc.withdraw(3000);
            wcout << L"Balance after withdrawal: " << acc.balance() << endl;
         }
      );
   }
   catch (const exception& e)
   {
      wcout << L"Error details:" << endl << L"\t" << e.what() << endl;
   }
}

Bu örnek aşağıdaki örnek çıkışı oluşturur:

Balance before deposit: 1924
Balance after deposit: 2924
Balance before withdrawal: 2924
Balance after withdrawal: -76
Balance before withdrawal: -76
Error details:
    negative balance: -76

Eşzamanlılık nesnelerinin yaşam süresini yönetmek için RAII desenini kullanan ek örnekler için bkz . İzlenecek yol: Kullanıcı Arabirimi İş Parçacığından Çalışma Kaldırma, Nasıl yapılır: İşbirliğine Dayalı Semafor Uygulamak için Bağlam Sınıfını Kullanma ve Gecikme Süresini Dengelemek için Aşırı Abonelik Kullanma.

[Üst]

Genel Kapsamda Eşzamanlılık Nesneleri Oluşturma

Genel kapsamda eşzamanlılık nesnesi oluşturduğunuzda, uygulamanızda kilitlenme veya bellek erişim ihlalleri gibi sorunların oluşmasına neden olabilirsiniz.

Örneğin, eşzamanlılık çalışma zamanı nesnesi oluşturduğunuzda, çalışma zamanı henüz oluşturulmamışsa sizin için varsayılan bir zamanlayıcı oluşturur. Genel nesne oluşturma sırasında oluşturulan bir çalışma zamanı nesnesi, çalışma zamanının bu varsayılan zamanlayıcıyı oluşturmasına neden olur. Ancak bu işlem, Eşzamanlılık Çalışma Zamanı altyapısını destekleyen diğer nesnelerin başlatılmasını engelleyebilecek bir iç kilit alır. Bu iç kilit, henüz başlatılmamış başka bir altyapı nesnesi tarafından gerekli olabilir ve bu nedenle uygulamanızda kilitlenme oluşmasına neden olabilir.

Aşağıdaki örnekte genel eşzamanlılık::Scheduler nesnesinin oluşturulması gösterilmektedir. Bu desen yalnızca sınıf için değil Eşzamanlılık Çalışma Zamanı tarafından sağlanan diğer tüm türler için Scheduler de geçerlidir. Uygulamanızda beklenmeyen davranışlara neden olabileceği için bu deseni izlememenizi öneririz.

// global-scheduler.cpp
// compile with: /EHsc
#include <concrt.h>

using namespace concurrency;

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

// Create a Scheduler object at global scope.
// BUG: This practice is not recommended because it can cause deadlock.
Scheduler* globalScheduler = Scheduler::Create(SchedulerPolicy(2,
   MinConcurrency, 2, MaxConcurrency, 4));

int wmain() 
{   
}

Nesne oluşturmanın Scheduler doğru yolu örnekleri için bkz . Görev Zamanlayıcı.

[Üst]

Paylaşılan Veri Kesimlerinde Eşzamanlılık Nesneleri Kullanmayın

Eşzamanlılık Çalışma Zamanı paylaşılan veri bölümünde eşzamanlılık nesnelerinin kullanımını desteklemez; örneğin, data_seg#pragma yönergesi tarafından oluşturulan bir veri bölümü. İşlem sınırları arasında paylaşılan bir eşzamanlılık nesnesi çalışma zamanını tutarsız veya geçersiz bir duruma sokabilir.

[Üst]

Ayrıca bkz.

Eşzamanlılık Çalışma Zamanı En İyi Yöntemleri
Paralel Desen Kitaplığı (PPL)
Zaman Uyumsuz Aracılar Kitaplığı
Görev Zamanlayıcı
Eşitleme Veri Yapıları
Eşitleme Veri Yapılarını Windows API ile Karşılaştırma
Nasıl yapılır: Bellek Performansını Artırmak için Alloc ve Free Kullanma
Nasıl yapılır: Gecikmeyi Dengelemek için Aşırı Aboneliği Kullanma
Nasıl yapılır: Bağlam Sınıfını İşbirlikçi Semafor Uygulamak için Kullanma
İzlenecek Yol: Kullanıcı Arabirimi İş Parçacığından İşi Kaldırma
Paralel Desen Kitaplığı'ndaki En İyi Yöntemler
Zaman Uyumsuz Aracılar Kitaplığı'ndaki En İyi Yöntemler