Porady: korzystanie z klasy kontekstu do wdrażania a kooperatywnego semafora
W tym temacie przedstawiono sposób użycia concurrency::Context klasy, aby zaimplementować klasę spółdzielni semafora.
Context Klasa pozwala zablokować lub plon bieżącego kontekstu wykonania.Blokowanie lub otrzymania bieżącego kontekstu jest przydatne, gdy bieżący kontekst nie może kontynuować, ponieważ zasób nie jest dostępny.A semafor jest przykładem jednej sytuacji gdzie bieżącego kontekstu wykonania musi czekać na zasób stają się dostępne.Semafor, jak obiekt sekcji krytycznej jest obiektu synchronizacji, który umożliwia kodu w jednym kontekście mieć wyłączny dostęp do zasobu.Jednak w przeciwieństwie do obiektu sekcji krytycznej semafor umożliwia więcej niż jednym kontekście jednocześnie dostęp do zasobu.Jeśli maksymalna liczba kontekstów posiada blokadę semafora, każdy dodatkowy kontekst musi czekać na inny kontekst do zwolnienia blokady.
Aby implementować klasę semafora
Zadeklarować klasy o nazwie semaphore.Dodaj public i private sekcje do tej klasy.
// A semaphore type that uses cooperative blocking semantics. class semaphore { public: private: };
W private części semaphore klasy, oświadczyć, std::atomic zmiennej, która posiada Licznik semafora i concurrency::concurrent_queue obiekt, który przechowuje różne konteksty musi czekać do nabycia semafora.
// 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;
W public części semaphore klasy, implementować konstruktora.Konstruktor ma long long wartość, która określa maksymalną liczbę konteksty, które jednocześnie mogą zawierać blokady.
explicit semaphore(long long capacity) : _semaphore_count(capacity) { }
W public części semaphore klasy, implementować acquire metody.Ta metoda Dekrementuje semafora liczone jako operacja atomowa.Jeśli Licznik semafora staje się ujemna, dodać bieżący kontekst na koniec kolejki i wywołanie concurrency::Context::Block metoda blokady bieżącego kontekstu.
// 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(); } }
W public części semaphore klasy, implementować release metody.Ta metoda zwiększa liczbę semafora jako operacja Atomowej.Jeśli Licznik semafora jest ujemny przed operacją przyrostu, istnieje co najmniej jeden kontekst, który jest oczekiwanie na blokadę.W tym przypadku odblokować kontekst, który znajduje się na wierzchu kolejki.
// 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(); } }
Przykład
semaphore Klasy w tym przykładzie zachowuje się wspólnie, ponieważ Context::Block i Context::Yield metody produktywności wykonanie tak, aby środowisko wykonawcze mogą wykonywać inne zadania.
acquire Dekrementuje metoda licznika, ale może nie zakończyć dodawanie kontekście do kolejki przed innego kontekstu wywołania release metody.Aby uwzględnić to, release metoda wykorzystuje pętli pokrętła, który wywołuje concurrency::Context::Yield metoda czekać na acquire metoda, aby zakończyć dodawanie kontekście.
release Można wywołać metody Context::Unblock metody przed acquire wywołania metody Context::Block metody.Nie masz ochrony przed sytuacja wyścigu, ponieważ środowisko czasu wykonania pozwala na tych metod ma zostać wywołana w dowolnej kolejności.Jeśli release wywołania metody Context::Unblock przed acquire wywołania metody Context::Block w tym samym kontekście odblokowane pozostają tego kontekstu.Środowisko wykonawcze wymaga jedynie, że każdego wywołania do Context::Block jest dopasowywany do odpowiedniego wywołania Context::Unblock.
Poniższy przykład przedstawia pełne semaphore klasy.wmain Funkcja zawiera podstawowe użycie tej klasy.wmain Funkcja używa concurrency::parallel_for algorytmu, który utworzy kilka zadań, które wymagają dostępu do semafora.Ponieważ trzy wątki mogą zawierać blokady w dowolnym momencie, niektóre zadania muszą czekać na innego zadania do końca i zwolnić blokadę.
// 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();
});
}
Ten przykład generuje następujące przykładowe wyniki.
Aby uzyskać więcej informacji dotyczących klasy concurrent_queue, wejdź na Równoległe kontenery oraz obiekty.Aby uzyskać więcej informacji o parallel_for algorytmu, zobacz Algorytmy równoległe.
Kompilowanie kodu
Skopiuj przykładowy kod i wklej go w projekcie programu Visual Studio lub wkleić go w pliku o nazwie spółdzielni semaphore.cpp , a następnie uruchomić następujące polecenie w oknie wiersza polecenia programu Visual Studio.
cl.exe /EHsc cooperative-semaphore.cpp
Stabilne programowanie
Można użyć Inicjowania jest przejęcie zasobu wzór (RAII), aby ograniczyć dostęp do semaphore obiektu do danego zakresu.W obszarze wzorzec RAII struktury danych jest przydzielany na stosie.Że struktura danych inicjuje lub nabywa zasobu, gdy jest tworzony i niszczy lub zwalnia tego zasobu, kiedy niszczony jest struktura danych.Wzór RAII gwarantuje, że destruktor Nazywa się przed zasięgu kończy pracę.W związku z tym, gdy wyjątek lub gdy funkcja zawiera wiele zasobu jest poprawnie zarządzane return instrukcji.
W poniższym przykładzie zdefiniowano klasę o nazwie scoped_lock, który jest zdefiniowany w public części semaphore klasy.scoped_lock Podobny do klasy concurrency::critical_section::scoped_lock i concurrency::reader_writer_lock::scoped_lock klas.Konstruktor semaphore::scoped_lock klasy uzyskuje dostęp do danego semaphore obiektu i destruktor zwalnia dostęp do danego obiektu.
// 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;
};
Poniższy przykład modyfikuje treści funkcji pracy, który jest przekazywany do parallel_for algorytm, tak aby używał RAII do zapewnienia, że Semafor jest zwolniony przed funkcja zwraca.Technika ta zapewnia, że funkcja pracy jest bezpieczny dla wyjątku.
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);
});