Sdílet prostřednictvím


Postupy: Použití třídy kontextu pro implementaci semaforu pro spolupráci

Toto téma ukazuje, jak použít concurrency::Context třídy k implementaci třídy semaphore spolupráce.

Poznámky

Třída Context umožňuje blokovat nebo přinést aktuální kontext spuštění. Blokování nebo výnos aktuálního kontextu je užitečné, když aktuální kontext nemůže pokračovat, protože prostředek není k dispozici. Semaphore je příkladem jedné situace, kdy aktuální kontext spuštění musí čekat na zpřístupnění prostředku. Semaphore, podobně jako kritický objekt oddílu, je synchronizační objekt, který umožňuje kódu v jednom kontextu mít výhradní přístup k prostředku. Na rozdíl od kritického objektu oddílu však semaphore umožňuje souběžnému přístupu k prostředku více než jeden kontext. Pokud maximální počet kontextů obsahuje zámek semaphore, musí každý další kontext počkat na uvolnění zámku jiným kontextem.

Implementace třídy semaphore

  1. Deklarujte třídu s názvem semaphore. Přidejte public do private této třídy oddíly.
// A semaphore type that uses cooperative blocking semantics.
class semaphore
{
public:
private:
};
  1. private V části semaphore třídy deklarujte proměnnou std::atomic, která obsahuje semaphore count a concurrency::concurrent_queue objekt, který obsahuje kontexty, které musí čekat na získání semaphore.
// 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 V části semaphore třídy implementujte konstruktor. Konstruktor přebírá long long hodnotu, která určuje maximální počet kontextů, které mohou současně držet zámek.
explicit semaphore(long long capacity)
   : _semaphore_count(capacity)
{
}
  1. public V části semaphore třídy implementujte metoduacquire. Tato metoda dekrementuje semaphore count jako atomická operace. Pokud se počet semaphore změní na záporný, přidejte aktuální kontext na konec fronty čekání a zavolejte concurrency::Context::Block metoda blokovat aktuální kontext.
// 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 V části semaphore třídy implementujte metodurelease. Tato metoda zvýší počet semaphore jako atomické operace. Pokud je počet semaphore záporný před operací přírůstku, existuje alespoň jeden kontext, který čeká na zámek. V tomto případě odblokujte kontext, který je před frontou čeká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();
   }
}

Příklad

Třída semaphore v tomto příkladu se chová spolu, protože Context::Block metody a Context::Yield metody poskytují provádění, aby modul runtime mohl provádět další úlohy.

Metoda acquire dekrementuje čítač, ale nemusí dokončit přidání kontextu do fronty čekání předtím, než jiný kontext volá metodu release . K tomuto účtu metoda používá spinovou smyčku, release která volá concurrency::Context::Yield metoda, aby počkala, acquire až metoda dokončí přidání kontextu.

Metoda release může volat metodu Context::Unblockacquire před voláním Context::Block metody. Před tímto stavem časování nemusíte chránit, protože modul runtime umožňuje volat tyto metody v libovolném pořadí. release Pokud metoda volá Context::Unblock před acquire voláním Context::Block metody pro stejný kontext, zůstane tento kontext odblokovaný. Modul runtime vyžaduje, aby se každé volání Context::Block shodoval s odpovídajícím voláním Context::Unblock.

Následující příklad ukazuje kompletní semaphore třídu. Funkce wmain zobrazuje základní použití této třídy. Funkce wmain používá algoritmus concurrency::p arallel_for k vytvoření několika úloh, které vyžadují přístup k semaphore. Vzhledem k tomu, že zámek můžou kdykoli uchovávat tři vlákna, některé úlohy musí počkat na dokončení jiného úkolu a uvolnit zámek.

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

Tento příklad vytvoří následující ukázkový výstup.

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

Další informace o concurrent_queue třídě naleznete v tématu Paralelní kontejnery a objekty. Další informace o parallel_for algoritmu naleznete v tématu Paralelní algoritmy.

Probíhá kompilace kódu

Zkopírujte ukázkový kód a vložte ho do projektu sady Visual Studio nebo ho vložte do pojmenovaného cooperative-semaphore.cpp souboru a potom v okně příkazového řádku sady Visual Studio spusťte následující příkaz.

cl.exe /EHsc cooperative-semaphore.cpp

Robustní programování

Model Inicializace prostředků (RAII) můžete použít k omezení přístupu k objektu semaphore na daný obor. V rámci vzoru RAII je datová struktura přidělena v zásobníku. Tato datová struktura inicializuje nebo získá prostředek při jeho vytvoření a zničí nebo uvolní tento prostředek při zničení datové struktury. Vzor RAII zaručuje, že destruktor je volána před uzavřením rozsahu. Prostředek se proto správně spravuje, pokud je vyvolána výjimka nebo když funkce obsahuje více return příkazů.

Následující příklad definuje třídu s názvem scoped_lock, která je definována v public oddílu semaphore třídy. Třída scoped_lock se podobá souběžnosti::critical_section::scoped_lock a souběžnosti::reader_writer_lock::scoped_lock třídy. Konstruktor semaphore::scoped_lock třídy získá přístup k danému semaphore objektu a destruktoru uvolní přístup k ho objektu.

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

Následující příklad upraví tělo pracovní funkce, která je předána parallel_for algoritmu tak, aby pomocí RAII zajistil, že se semafor uvolní před vrácením funkce. Tato technika zajišťuje, že pracovní funkce je bezpečná pro výjimky.

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

Viz také

Kontexty
Paralelní kontejnery a objekty