Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Det här avsnittet visar hur du använder samtidighet::Kontextklass för att implementera en kooperativ semaforklass.
Anmärkningar
Med Context-klassen kan du blockera eller tillåta den aktuella exekveringskontexten. Att avstå eller blockera den aktuella kontexten kan vara användbart när den aktuella kontexten inte kan fortsätta eftersom en resurs inte är tillgänglig. En semafor är ett exempel på en situation där den nuvarande körningskontext måste vänta på att en resurs ska bli tillgänglig. En semafor, som ett kritiskt avsnittsobjekt, är ett synkroniseringsobjekt som gör att kod i en kontext kan ha exklusiv åtkomst till en resurs. Men till skillnad från ett kritiskt avsnittsobjekt ger en semafor mer än en kontext åtkomst till resursen samtidigt. Om det maximala antalet kontexter har ett semaforlås måste varje ytterligare kontext vänta tills en annan kontext släpper låset.
Implementera semaforklassen
- Deklarera en klass med namnet semaphore. Lägg tillpublicochprivateavsnitt i den här klassen.
// A semaphore type that uses cooperative blocking semantics.
class semaphore
{
public:
private:
};
- I avsnittet privateisemaphoreklassen deklarerar du en std::atomic-variabel som innehåller semaforantalet och en samtidighet::concurrent_queue objekt som innehåller de kontexter som måste vänta för att hämta semaforen.
// 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;
- I avsnittet publicisemaphoreklassen implementerar du konstruktorn. Konstruktorn tar ettlong longvärde som anger det maximala antalet kontexter som samtidigt kan hålla låset.
explicit semaphore(long long capacity)
   : _semaphore_count(capacity)
{
}
- I avsnittet publicisemaphoreklassen implementerar duacquiremetoden. Den här metoden minskar semaforantalet som en atomisk åtgärd. Om semaforantalet blir negativt lägger du till den aktuella kontexten i slutet av väntekön och anropar concurrency::Context::Block-metoden för att blockera den aktuella kontexten.
// 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();
   }
}
- I avsnittet publicisemaphoreklassen implementerar dureleasemetoden. Den här metoden ökar antalet semaforer som en atomisk åtgärd. Om semaforantalet är negativt före inkrementsåtgärden finns det minst en kontext som väntar på låset. I det här fallet avblockera kontexten som finns längst fram i väntekö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();
   }
}
Exempel
Klassen semaphore i det här exemplet uppför sig samarbetsvilligt eftersom metoderna Context::Block och Context::Yield ger upp körningen så att körexekveringen kan utföra andra uppgifter.
Metoden acquire minskar räknaren, men den kanske inte har lagt till kontexten i väntekön innan en annan kontext anropar release metoden. För att ta hänsyn till detta använder release-metoden en spin-loop som anropar metoden concurrency::Context::Yield för att vänta tills acquire-metoden har lagt till kontext.
Metoden release kan anropa Context::Unblock metoden innan acquire metoden anropar Context::Block metoden. Du behöver inte skydda dig mot det här konkurrenstillståndet eftersom körtiden tillåter att dessa metoder anropas i valfri ordning. 
              release Om metoden anropar Context::Unblock innan acquire metoden anropar Context::Block för samma kontext förblir den kontexten avblockerad. Körningen kräver bara att varje anrop till Context::Block matchas med ett motsvarande anrop till Context::Unblock.
I följande exempel visas hela semaphore klassen. Funktionen wmain visar grundläggande användning av den här klassen. Funktionen wmain använder algoritmen concurrency::parallel_for för att skapa flera arbetsuppgifter som kräver åtkomst till semaforen. Eftersom tre trådar kan hålla låset när som helst måste vissa uppgifter vänta tills en annan uppgift har slutförts och frigöra låset.
// 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();
   });
}
Det här exemplet genererar följande exempelutdata.
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...
Mer information om klassen finns i concurrent_queueParallella containrar och objekt. Mer information om algoritmen finns i parallel_forParallella algoritmer.
Kompilera koden
Kopiera exempelkoden och klistra in den i ett Visual Studio-projekt, eller klistra in den i en fil med namnet cooperative-semaphore.cpp och kör sedan följande kommando i ett Visual Studio-kommandotolkfönster.
cl.exe /EHsc cooperative-semaphore.cpp
Robust Programmering
Du kan använda mönstret Resource Acquisition Is Initialization (RAII) för att begränsa åtkomsten till ett semaphore objekt till ett visst omfång. Under RAII-mönstret allokeras en datastruktur på stacken. Datastrukturen initierar eller hämtar en resurs när den skapas och förstör eller frigör resursen när datastrukturen förstörs. RAII-mönstret garanterar att destruktorn anropas innan det omgivande omfånget avslutas. Därför hanteras resursen korrekt när ett undantag utlöses eller när en funktion innehåller flera return instruktioner.
I följande exempel definieras en klass med namnet scoped_lock, som definieras i public avsnittet i semaphore klassen. Klassen scoped_lock liknar concurrency::critical_section::scoped_lock och concurrency::reader_writer_lock::scoped_lock-klassen. Konstruktorn för semaphore::scoped_lock klassen får åtkomst till det angivna semaphore objektet och destruktören släpper åtkomst till objektet.
// 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;
};
I följande exempel ändras brödtexten för den arbetsfunktion som skickas till algoritmen parallel_for så att den använder RAII för att säkerställa att semaforen frigörs innan funktionen returneras. Den här tekniken säkerställer att arbetsfunktionen är undantagssäker.
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);
});