Compartilhar via


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

  1. 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:
    };
    
  2. 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;
    
  3. 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)
    {
    }
    
  4. 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();
       }
    }
    
  5. 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);
});

Consulte também

Referência

Classe de contexto

Conceitos

Contextos

Contêiner e objetos paralelos