Como: Use a classe de contexto para implementar um semáforo cooperativo
Este tópico mostra como usar a classe de concurrency::Context para implementar uma classe cooperativa de semáforo.
A classe de Context permite bloquear ou produzir o contexto atual de execução.Bloquear ou produzir o contexto atual é útil quando o contexto atual não pode continuar como um recurso não está disponível.Um semáforo é um exemplo de uma situação onde o contexto atual de execução deve aguardar um recurso para estejam disponíveis.Um semáforo, como um objeto de seção crítica, é um objeto de sincronização que permite que o código em um contexto para ter acesso exclusivo a um recurso.No entanto, diferentemente de um objeto de seção crítica, um semáforo permite mais de um contexto para acessar o recurso simultaneamente.Se o número máximo de contextos contém um bloqueio de semáforo, cada contexto extra deve aguardar outro contexto para liberar o bloqueio.
Para implementar a classe de semáforo
Declare uma classe que é chamada semaphore.Adicione public e seções de private a essa classe.
// A semaphore type that uses cooperative blocking semantics. class semaphore { public: private: };
Na seção de private da classe de semaphore , declare uma variável de std::atomic que contém a contagem de semáforo e um objeto de concurrency::concurrent_queue que contém os contextos que devem aguardar para adquirir o semáforo.
// 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;
Na seção de public da classe de semaphore , implemente o construtor.O construtor aceita um valor de long long que especifica o número máximo de contextos que podem conter simultaneamente o bloqueio.
explicit semaphore(long long capacity) : _semaphore_count(capacity) { }
Na seção de public da classe de semaphore , implemente o método de acquire .Este método diminui a contagem de semáforo uma operação atomic como.Se a contagem de semáforo se torna negativo, adicione o contexto atual para o final da fila de espera e chame o método de concurrency::Context::Block para bloquear o contexto atual.
// 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(); } }
Na seção de public da classe de semaphore , implemente o método de release .Este método incrementa o resultado de uma operação atomic semáforo como.Se a contagem de semáforo é negativo antes da operação de incremento, esteja lá pelo menos um contexto que está aguardando o bloqueio.Nesse caso, desbloquear o contexto que está na frente da fila de espera.
// 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(); } }
Exemplo
A classe de semaphore nesse exemplo cooperativa se comporta como os métodos de Context::Block e de Context::Yield produzem a execução para que o tempo de execução pode realizar outras tarefas.
O método de acquire diminui o contador, mas não pode concluir adicionar o contexto a fila de espera antes que outro contexto chama o método de release .Para considerar esse, o método de release usa um loop de rotação que chama o método de concurrency::Context::Yield para esperar o método de acquire para concluir adicionar o contexto.
O método de release pode chamar o método de Context::Unblock antes que o método de acquire chama o método de Context::Block .Você não tem que se proteger contra essa condição de corrida porque o tempo de execução permite esses métodos ser chamado em qualquer ordem.Se o método de release chama Context::Unblock antes de chamar o método de acquireContext::Block para o mesmo contexto, a permanecerá desbloqueadas do contexto.O tempo de execução requer somente que cada chamada a Context::Block é compatível com um correspondente chamada a Context::Unblock.
O exemplo a seguir mostra a classe completa de semaphore .A função de wmain mostra o uso mais básico dessa classe.A função de wmain usa o algoritmo de concurrency::parallel_for para criar várias tarefas que exigem acesso ao semáforo.Porque três segmentos podem conter o bloqueio a qualquer momento, algumas tarefas devem aguardar outra tarefa termina e liberar o bloqueio.
// 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();
});
}
Este exemplo produz a seguinte saída de exemplo.
Para obter mais informações sobre a classe concurrent_queue, consulte Contêiner e objetos paralelos.Para obter mais informações sobre o algoritmo de parallel_for , consulte Algoritmos paralelos.
Compilando o código
Copie o código de exemplo e cole-o em um projeto do Visual Studio, ou cole em um arquivo denominado cooperative-semaphore.cpp e execute o seguinte comando em uma janela de prompt de comando do Visual Studio.
cl.exe /EHsc cooperative-semaphore.cpp
Programação robusta
Você pode usar o padrão Aquisição de recurso é inicialização (RAII) para limitar o acesso a um objeto semaphore a um determinado escopo.No padrão de RAII, uma estrutura de dados é atribuída na pilha.A estrutura de dados ou inicializa obtém um recurso quando é criada e o for ou versões esse recurso quando a estrutura de dados seja destruída.O padrão de RAII garante que o destrutor é chamado antes que o escopo delimitador sair.Como consequência, o recurso é gerenciado corretamente quando uma exceção é lançada ou quando uma função contiver várias instruções de return .
O exemplo a seguir define uma classe que é chamada scoped_lock, que é definido na seção de public da classe de semaphore .A classe de scoped_lock é semelhante à classes de concurrency::critical_section::scoped_lock e de concurrency::reader_writer_lock::scoped_lock .O construtor da classe de semaphore::scoped_lock obtém acesso ao objeto dado de semaphore e para versões do destrutor acesso ao objeto.
// 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;
};
O exemplo a seguir altera o corpo da função de trabalho que é passada para o algoritmo de parallel_for que ele use RAII para garantir que o semáforo será liberado antes que a função retorna.Essa técnica garante que a função trabalho é exceção de segurança.
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);
});