Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
In dit onderwerp wordt beschreven hoe u de concurrency::Context-klasse kunt gebruiken voor het implementeren van een coöperatieve semafore-klasse.
Opmerkingen
Met Context de klasse kunt u de huidige uitvoeringscontext blokkeren of opleveren. Het blokkeren of opleveren van de huidige context is handig wanneer de huidige context niet kan worden voortgezet omdat er geen resource beschikbaar is. Een semafore is een voorbeeld van een situatie waarin de huidige uitvoeringscontext moet wachten tot een resource beschikbaar is. Een semaphore, zoals een kritiek sectieobject, is een synchronisatieobject waarmee code in één context exclusieve toegang heeft tot een resource. In tegenstelling tot een kritiek sectieobject maakt een semafore echter meer dan één context mogelijk om gelijktijdig toegang te krijgen tot de resource. Als het maximum aantal contexten een semaphore-vergrendeling bevat, moet elke extra context wachten totdat een andere context de vergrendeling loslaat.
De semaforenklasse implementeren
- Declareer een klasse met de naam
semaphore. Voeg de sectiespublicenprivatetoe aan deze klasse.
// A semaphore type that uses cooperative blocking semantics.
class semaphore
{
public:
private:
};
- Declareer in de
privatesectie van desemaphoreklasse een std::atomic-variabele die het aantal semaforen bevat en een concurrency::concurrent_queue object dat de contexten bevat die moeten wachten om de semafoor te verkrijgen.
// 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;
- Implementeer de constructor in de
publicsectie van desemaphoreklasse. De constructor gebruikt eenlong longwaarde die het maximum aantal contexten aangeeft dat gelijktijdig de vergrendeling kan bevatten.
explicit semaphore(long long capacity)
: _semaphore_count(capacity)
{
}
- Implementeer de
publicmethode in desemaphoresectie van deacquireklasse. Met deze methode wordt de semafoortelling als een atomische operatie verlaagd. Als het aantal semaphore negatief wordt, voegt u de huidige context toe aan het einde van de wachtwachtrij en roept u de gelijktijdigheid::Context::Blokmethode aan om de huidige context te blokkeren.
// 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();
}
}
- Implementeer de
publicmethode in desemaphoresectie van dereleaseklasse. Met deze methode wordt het aantalemaforen verhoogd als een atomische bewerking. Als het aantal semaforen negatief is vóór de incrementele bewerking, is er ten minste één context die wacht op de vergrendeling. In dit geval deblokkeert u de context die zich aan het begin van de wachtqueue bevindt.
// 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();
}
}
Voorbeeld
De semaphore klasse in dit voorbeeld gedraagt zich gezamenlijk omdat de Context::Block en Context::Yield methoden de uitvoering opleveren, zodat de runtime andere taken kan uitvoeren.
De acquire-methode verlaagt de teller, maar het is mogelijk dat het toevoegen van de context aan de wachtrij niet is voltooid voordat een andere context de release-methode aanroept. Hiervoor gebruikt de release methode een spinlus die de concurrency::Context::Yield-methode aanroept om te wachten totdat de acquire methode klaar is met het toevoegen van de context.
De release methode kan de Context::Unblock methode aanroepen voordat de acquire methode de Context::Block methode aanroept. U hoeft zich niet te beschermen tegen deze racevoorwaarde, omdat de runtime toestaat dat deze methoden in elke volgorde worden aangeroepen. Als de release methode Context::Unblock aanroept voordat de acquire methode Context::Block voor dezelfde context aanroept, blijft die context gedeblokkeerd. Voor de runtime is alleen vereist dat elke aanroep Context::Block overeenkomt met een bijbehorende aanroep naar Context::Unblock.
In het volgende voorbeeld ziet u de volledige semaphore klasse. De wmain functie toont het basisgebruik van deze klasse. De wmain functie maakt gebruik van het concurrency::parallel_for algoritme om verschillende taken te maken dat toegang tot de semaphore vereist. Omdat drie threads de vergrendeling op elk gewenst moment kunnen bevatten, moeten sommige taken wachten totdat een andere taak is voltooid en de vergrendeling vrijgeven.
// 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();
});
}
In dit voorbeeld wordt de volgende voorbeelduitvoer geproduceerd.
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...
Zie concurrent_queue voor meer informatie over de klasse. Zie parallel_for voor meer informatie over het algoritme.
De code compileren
Kopieer de voorbeeldcode en plak deze in een Visual Studio-project, of plak deze in een bestand met de naam cooperative-semaphore.cpp en voer vervolgens de volgende opdracht uit in een Visual Studio-opdrachtpromptvenster.
cl.exe /EHsc-cooperative-semaphore.cpp
Robuuste programmering
U kunt het RAII-patroon ( Resource Acquisition Is Initialization ) gebruiken om de toegang tot een semaphore object tot een bepaald bereik te beperken. Onder het RAII-patroon wordt een gegevensstructuur toegewezen aan de stack. Deze gegevensstructuur initialiseert of verkrijgt een resource wanneer deze wordt gemaakt en vernietigt of publiceert die resource wanneer de gegevensstructuur wordt vernietigd. Het RAII-patroon garandeert dat de destructor wordt aangeroepen voordat het omsluitbereik wordt afgesloten. Daarom wordt de resource correct beheerd wanneer er een uitzondering wordt gegenereerd of wanneer een functie meerdere return instructies bevat.
In het volgende voorbeeld wordt een klasse gedefinieerd met de naam scoped_lock, die is gedefinieerd in de public sectie van de semaphore klasse. De scoped_lock klasse lijkt op de concurrency::critical_section::scoped_lock en concurrency::reader_writer_lock::scoped_lock klassen. De constructor van de semaphore::scoped_lock klasse verkrijgt toegang tot het opgegeven semaphore object en de destructor geeft toegang tot dat object.
// 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;
};
In het volgende voorbeeld wordt de hoofdtekst van de werkfunctie gewijzigd, die aan het parallel_for algoritme wordt doorgegeven, zodat RAII wordt gebruikt om ervoor te zorgen dat de semaphore wordt vrijgegeven voordat de functie terugkeert. Deze techniek zorgt ervoor dat de werkfunctie uitzonderingsveilig is.
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);
});