Aracılığıyla paylaş


Nasıl yapılır: Bağlam Sınıfını İşbirlikçi Semafor Uygulamak için Kullanma

Bu konu, işbirliğine dayalı semafor sınıfı uygulamak için concurrency::Context sınıfının nasıl kullanılacağını gösterir.

Açıklamalar

sınıfı, Context geçerli yürütme bağlamını engellemenize veya vermenizi sağlar. Geçerli bağlamı engellemek veya oluşturmak, bir kaynak kullanılamadığından geçerli bağlam devam edemiyorsa yararlıdır. Semafor, geçerli yürütme bağlamının bir kaynağın kullanılabilir olmasını beklemesi gereken bir duruma örnektir. Kritik bölüm nesnesi gibi semafor, bir bağlamdaki kodun kaynağa özel erişime sahip olmasını sağlayan bir eşitleme nesnesidir. Ancak, kritik bir bölüm nesnesinin aksine, semafor birden fazla bağlamın kaynağa eşzamanlı olarak erişmesini sağlar. Bağlam sayısı üst sınırı semafor kilidi barındırıyorsa, her ek bağlamın kilidi serbest bırakmak için başka bir bağlam beklemesi gerekir.

Semafor sınıfını uygulamak için

  1. adlı semaphorebir sınıf bildirin. private Ve bölümlerini bu sınıfa ekleyinpublic.
// A semaphore type that uses cooperative blocking semantics.
class semaphore
{
public:
private:
};
  1. private sınıfının bölümündesemaphore, semafor sayısını ve semaforu almak için beklemesi gereken bağlamları tutan bir eşzamanlılık::concurrent_queue nesnesini tutan bir std::atomic değişkeni bildirin.
// The semaphore count.
atomic<long long> _semaphore_count;

// A concurrency-safe queue of contexts that must wait to 
// acquire the semaphore.
concurrent_queue<Context*> _waiting_contexts;
  1. public sınıfının bölümünde semaphore oluşturucuyu uygulayın. Oluşturucu, kilidi eşzamanlı olarak tutabilecek bağlam sayısı üst sınırını belirten bir long long değer alır.
explicit semaphore(long long capacity)
   : _semaphore_count(capacity)
{
}
  1. public sınıfının bölümünde semaphore yöntemini uygulayınacquire. Bu yöntem semafor sayısını atomik işlem olarak azaltmaya çalışır. Semafor sayısı negatif olursa, bekleme kuyruğunun sonuna geçerli bağlamı ekleyin ve geçerli bağlamı engellemek için eşzamanlılık::Context::Block yöntemini çağırın.
// Acquires access to the semaphore.
void acquire()
{
   // The capacity of the semaphore is exceeded when the semaphore count 
   // falls below zero. When this happens, add the current context to the 
   // back of the wait queue and block the current context.
   if (--_semaphore_count < 0)
   {
      _waiting_contexts.push(Context::CurrentContext());
      Context::Block();
   }
}
  1. public sınıfının bölümünde semaphore yöntemini uygulayınrelease. Bu yöntem semafor sayısını atomik işlem olarak artırır. Artış işleminden önce semafor sayısı negatifse, kilidi bekleyen en az bir bağlam vardır. Bu durumda bekleme kuyruğunun önündeki bağlamın engelini kaldırın.
// Releases access to the semaphore.
void release()
{
   // If the semaphore count is negative, unblock the first waiting context.
   if (++_semaphore_count <= 0)
   {
      // A call to acquire might have decremented the counter, but has not
      // yet finished adding the context to the queue. 
      // Create a spin loop that waits for the context to become available.
      Context* waiting = NULL;
      while (!_waiting_contexts.try_pop(waiting))
      {
         Context::Yield();
      }

      // Unblock the context.
      waiting->Unblock();
   }
}

Örnek

Çalışma zamanının semaphore diğer görevleri gerçekleştirebilmesi için Context::Block ve Context::Yield yöntemleri yürütmeyi sağladığından, bu örnekteki sınıf işbirliği içinde davranır.

acquire yöntemi sayacını geri alır, ancak başka bir bağlam yöntemi çağırmadan release önce bekleme kuyruğuna bağlam eklemeyi tamamlayameyebilir. Bunu hesaba katmak için yöntemi, yöntemin release bağlam eklemeyi tamamlanmasını beklemek için acquire eşzamanlılık::Context::Yield yöntemini çağıran bir döndürme döngüsü kullanır.

release yöntemi yöntemini çağırmadan önce acquire yöntemini çağırabilir Context::UnblockContext::Block. Çalışma zamanı bu yöntemlerin herhangi bir sırada çağrılmasına izin verdiğinden bu yarış durumuna karşı koruma yapmanız gerekmez. yöntemi aynı bağlamı release çağırmadan önce acquire yöntemini çağırırsa Context::UnblockContext::Block, bu bağlam engellenmemiş olarak kalır. Çalışma zamanı yalnızca her çağrısının Context::Block öğesine karşılık gelen bir çağrıyla eşleşmesini Context::Unblockgerektirir.

Aşağıdaki örnekte sınıfın tamamı semaphore gösterilmektedir. işlevi bu wmain sınıfın temel kullanımını gösterir. wmain işlevi semafora erişim gerektiren çeşitli görevler oluşturmak için concurrency::p arallel_for algoritmasını kullanır. Kilidi her zaman üç iş parçacığı tutabildiğinden, bazı görevlerin başka bir görevin bitmesini ve kilidi serbest bırakmasını beklemesi gerekir.

// cooperative-semaphore.cpp
// compile with: /EHsc
#include <atomic>
#include <concrt.h>
#include <ppl.h>
#include <concurrent_queue.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

// A semaphore type that uses cooperative blocking semantics.
class semaphore
{
public:
   explicit semaphore(long long capacity)
      : _semaphore_count(capacity)
   {
   }

   // Acquires access to the semaphore.
   void acquire()
   {
      // The capacity of the semaphore is exceeded when the semaphore count 
      // falls below zero. When this happens, add the current context to the 
      // back of the wait queue and block the current context.
      if (--_semaphore_count < 0)
      {
         _waiting_contexts.push(Context::CurrentContext());
         Context::Block();
      }
   }

   // Releases access to the semaphore.
   void release()
   {
      // If the semaphore count is negative, unblock the first waiting context.
      if (++_semaphore_count <= 0)
      {
         // A call to acquire might have decremented the counter, but has not
         // yet finished adding the context to the queue. 
         // Create a spin loop that waits for the context to become available.
         Context* waiting = NULL;
         while (!_waiting_contexts.try_pop(waiting))
         {
            Context::Yield();
         }

         // Unblock the context.
         waiting->Unblock();
      }
   }

private:
   // The semaphore count.
   atomic<long long> _semaphore_count;

   // A concurrency-safe queue of contexts that must wait to 
   // acquire the semaphore.
   concurrent_queue<Context*> _waiting_contexts;
};

int wmain()
{
   // Create a semaphore that allows at most three threads to 
   // hold the lock.
   semaphore s(3);

   parallel_for(0, 10, [&](int i) {
      // Acquire the lock.
      s.acquire();

      // Print a message to the console.
      wstringstream ss;
      ss << L"In loop iteration " << i << L"..." << endl;
      wcout << ss.str();

      // Simulate work by waiting for two seconds.
      wait(2000);

      // Release the lock.
      s.release();
   });
}

Bu örnek aşağıdaki örnek çıktıyı oluşturur.

In loop iteration 5...
In loop iteration 0...
In loop iteration 6...
In loop iteration 1...
In loop iteration 2...
In loop iteration 7...
In loop iteration 3...
In loop iteration 8...
In loop iteration 9...
In loop iteration 4...

sınıfı hakkında concurrent_queue daha fazla bilgi için bkz . Paralel Kapsayıcılar ve Nesneler. Algoritma hakkında parallel_for daha fazla bilgi için bkz . Paralel Algoritmalar.

Kod Derleniyor

Örnek kodu kopyalayıp bir Visual Studio projesine yapıştırın veya adlı cooperative-semaphore.cpp bir dosyaya yapıştırın ve ardından bir Visual Studio Komut İstemi penceresinde aşağıdaki komutu çalıştırın.

cl.exe /EHsc kooperatif-semaphore.cpp

Güçlü Programlama

Bir nesneye erişimi belirli bir semaphore kapsamla sınırlamak için Kaynak Alma Başlatmadır (RAII) desenini kullanabilirsiniz. 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 nedenle, bir özel durum oluştuğunda veya bir işlev birden çok return deyim içerdiğinde kaynak doğru şekilde yönetilir.

Aşağıdaki örnek, sınıfın bölümünde semaphore tanımlanan public adlı scoped_lockbir sınıfı tanımlar. sınıfı scoped_lock eşzamanlılık::critical_section::scoped_lock ve eşzamanlılık::reader_writer_lock::scoped_lock sınıflarına benzer. sınıfı oluşturucu, semaphore::scoped_lock verilen semaphore nesneye erişim alır ve yıkıcı bu nesneye erişimi serbest bırakır.

// An exception-safe RAII wrapper for the semaphore class.
class scoped_lock
{
public:
   // Acquires access to the semaphore.
   scoped_lock(semaphore& s)
      : _s(s)
   {
      _s.acquire();
   }
   // Releases access to the semaphore.
   ~scoped_lock()
   {
      _s.release();
   }

private:
   semaphore& _s;
};

Aşağıdaki örnek, semaforun işlev döndürülmeden önce serbest bırakılmasını sağlamak için parallel_for RAII kullanması için algoritmaya geçirilen iş işlevinin gövdesini değiştirir. Bu teknik, iş işlevinin özel durum açısından güvenli olmasını sağlar.

parallel_for(0, 10, [&](int i) {
   // Create an exception-safe scoped_lock object that holds the lock 
   // for the duration of the current scope.
   semaphore::scoped_lock auto_lock(s);

   // Print a message to the console.
   wstringstream ss;
   ss << L"In loop iteration " << i << L"..." << endl;
   wcout << ss.str();

   // Simulate work by waiting for two seconds.
   wait(2000);
});

Ayrıca bkz.

Bağlamlar
Paralel Kapsayıcılar ve Nesneler