Megosztás a következőn keresztül:


Útmutató: A Context osztály használata kooperatív szemafor implementálásához

Ez a témakör bemutatja, hogyan használható a concurrency::Context osztály kooperatív szemapóra osztály implementálásához.

Megjegyzések

Az Context osztály lehetővé teszi az aktuális végrehajtási környezet blokkolását vagy átengedését. Az aktuális környezet blokkolása vagy engedése akkor hasznos, amikor az aktuális környezet nem folytatható, mert egy erőforrás nem érhető el. A szemaphore egy példa egy olyan helyzetre, amikor az aktuális végrehajtási környezetnek várnia kell, amíg egy erőforrás elérhetővé válik. A szemafor, mint egy kritikus szakaszobjektum, olyan szinkronizálási objektum, amely lehetővé teszi, hogy egy adott környezetben lévő kód kizárólagos hozzáféréssel rendelkezzen egy erőforráshoz. A kritikus szakaszobjektumokkal ellentétben azonban a szemaphore több környezet számára is lehetővé teszi az erőforrás egyidejű elérését. Ha a környezetek maximális száma tartalmaz szemaphore-zárolást, minden további környezetnek várnia kell, amíg egy másik környezet feloldja a zárolást.

A szemafor osztály implementálása

  1. Deklaráljon egy elnevezett semaphoreosztályt. Adjunk hozzá public és private szakaszokat ehhez az osztályhoz.
// A semaphore type that uses cooperative blocking semantics.
class semaphore
{
public:
private:
};
  1. private A(z) semaphore osztály szakaszában deklaráljon egy std::atomic változót, amely tartalmazza a szemafor számát, és egy concurrency::concurrent_queue objektumot, amely tartalmazza azokat a környezeteket, amelyeknek várniuk kell a szemafor megszerzésére.
// 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. Az public osztály semaphore szakaszában implementálja a konstruktort. A konstruktor olyan long long értéket vesz fel, amely meghatározza a zárolás egyszerre fenntartására képes környezetek maximális számát.
explicit semaphore(long long capacity)
   : _semaphore_count(capacity)
{
}
  1. A public osztály semaphore szakaszában implementálja a acquire metódust. Ez a módszer atomi műveletként mérsékeli a szemapórák számát. Ha a szemafor számláló negatívvá válik, adja hozzá az aktuális környezetet a várakozási sor végéhez, és hívja meg a concurrency::Context::Block metódust az aktuális környezet blokkolásához.
// 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. A public osztály semaphore szakaszában implementálja a release metódust. Ez a módszer atomi műveletként növeli a szemapórák számát. Ha a szemafor száma negatív az inkrementálás előtt, legalább egy folyamat vár a zárolásra. Ebben az esetben oldja fel a várakozási sor elején lévő kontextus letiltását.
// 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élda

A semaphore példában szereplő osztály együttműködően viselkedik, mert az és Context::Block a Context::Yield metódusok végrehajtást eredményeznek, így a futtatókörnyezet más feladatokat is végrehajthat.

A acquire metódus csökkenti a számlálót, de előfordulhat, hogy nem fejezi be a környezet hozzáadását a várakozási sorba, mielőtt egy másik környezet meghívja a release metódust. Annak figyelembevételére a release metódus egy spin ciklust használ, amely meghívja a konkurencia::Context::Yield metódust, hogy a acquire metódus befejezze a környezet hozzáadását.

A release metódus meghívhatja a metódust, Context::Unblock mielőtt a acquire metódus meghívja a metódust Context::Block . Nem kell védekezni a versenyfeltétel ellen, mert a futtatókörnyezet lehetővé teszi, hogy ezeket a módszereket bármilyen sorrendben meghívják. Ha a release metódus meghívja a Context::Unblock-t, mielőtt a acquire metódus meghívná a Context::Block-t ugyanarra a kontextusra, akkor a kontextus nem lesz letiltva. A futtatókörnyezet csak azt követeli meg, hogy minden egyes Context::Block hívás egy megfelelő Context::Unblock hívással legyen egyeztetve.

Az alábbi példa a teljes semaphore osztályt mutatja be. A wmain függvény az osztály alapszintű használatát jeleníti meg. A wmain függvény a Concurrency::parallel_for algoritmus használatával több olyan feladatot hoz létre, amely hozzáférést igényel a szemaforhoz. Mivel a zárolást egyszerre három szál is képes megtartani, egyes tevékenységeknek várniuk kell, amíg egy másik tevékenység befejeződik, és feloldják a zárolást.

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

Ez a példa a következő mintakimenetet hozza létre.

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

Az osztályról további információt a concurrent_queuePárhuzamos tárolók és objektumok című témakörben talál. Az algoritmusról további információt a parallel_forPárhuzamos algoritmusok című témakörben talál.

A kód összeállítása

Másolja ki a példakódot, és illessze be egy Visual Studio-projektbe, vagy illessze be egy elnevezett cooperative-semaphore.cpp fájlba, majd futtassa a következő parancsot egy Visual Studio parancssori ablakban.

cl.exe /EHsc cooperative-semaphore.cpp

Robusztus programozás

Az erőforrás-beszerzés inicializálási (RAII) mintával korlátozhatja az semaphore objektumhoz való hozzáférést egy adott hatókörre. A RAII-minta alatt egy adatstruktúra van lefoglalva a veremen. Ez az adatstruktúra inicializál vagy szerez be egy erőforrást, amikor létrejön, és megsemmisíti vagy felszabadítja az erőforrást az adatstruktúra megsemmisítésekor. A RAII-minta garantálja, hogy a destruktort a rendszer meghívja, mielőtt a beágyazási hatókör kilép. Ezért az erőforrást megfelelően kezeli a rendszer, ha kivételt ad ki, vagy ha egy függvény több return utasítást tartalmaz.

Az alábbi példa meghatároz egy scoped_lock nevű osztályt, amely a public osztály semaphore szakaszában van definiálva. Az scoped_lock osztály hasonlít a concurrency::critical_section::scoped_lock és a concurrency::reader_writer_lock::scoped_lock osztályokra. Az semaphore::scoped_lock osztály konstruktora hozzáférést szerez az adott semaphore objektumhoz, a destruktor pedig felszabadítja az objektumhoz való hozzáférést.

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

Az alábbi példa módosítja az parallel_for algoritmusnak átadott munkafüggvény törzsét úgy, hogy RAII-t használ a szeparofor felszabadításának biztosítására, mielőtt a függvény visszatérne. Ez a technika biztosítja, hogy a munkafüggvény biztonságosan kezelje a kivételeket.

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

Lásd még

Környezetben
párhuzamos tárolók és objektumok