Compartilhar via


Como: Use a classe de contexto para implementar um semáforo cooperativo

Este tópico mostra como usar o Concurrency::Context classe para implementar uma classe semaphore cooperativo.

O Context classe permite bloquear ou gerar o contexto de execução atual. Bloqueando ou produzindo o contexto atual é útil quando o contexto atual não pode continuar porque um recurso não está disponível. A semáforo é um exemplo de uma situação onde o contexto de execução atual deve aguardar um recurso se torne disponível. Um semáforo, como um objeto de seção crítica, é um objeto de sincronização permite que o código em um contexto para ter acesso exclusivo a um recurso. Entretanto, diferentemente de um objeto de seção crítica, um semáforo permite mais de um contexto acessar o recurso simultaneamente. Se o número máximo de contextos mantém um bloqueio de semáforo, cada contexto adicional deve aguardar outro contexto liberar o bloqueio.

Para implementar a classe semaphore

  1. Declarar uma classe chamada semaphore. Adicionar public e private seções para esta classe.

    // A semaphore type that uses cooperative blocking semantics.
    class semaphore
    {
    public:
    private:
    };
    
  2. No private seção a semaphore classe, declare uma variável do tipo LONG que contém a contagem de semáforo e um Concurrency::concurrent_queue objeto que mantém os contextos que devem aguardar para adquirir o semáforo.

    // The semaphore count.
    LONG _semaphore_count;
    
    // A concurrency-safe queue of contexts that must wait to 
    // acquire the semaphore.
    concurrent_queue<Context*> _waiting_contexts;
    
  3. No public seção a semaphore classe, implemente o construtor. O construtor obtém uma LONG valor que especifica o número máximo de contextos que podem armazenar simultaneamente o bloqueio.

    explicit semaphore(LONG capacity)
       : _semaphore_count(capacity)
    {
    }
    
  4. No public seção a semaphore da classe, implementar a acquire método. Decrementa este método o semáforo contar como uma operação atômica. Se a contagem de semáforo ficar negativa, adicionar o contexto atual para o final da fila de espera de chamada a Concurrency::Context::Block método 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 (InterlockedDecrement(&_semaphore_count) < 0)
       {
          _waiting_contexts.push(Context::CurrentContext());
          Context::Block();
       }
    }
    
  5. No public seção a semaphore da classe, implementar a release método. Esse método incrementa a contagem de semáforo como uma operação atômica. Se a contagem de sinal negativa antes da operação de incremento, há pelo menos um contexto que está aguardando que 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 (InterlockedIncrement(&_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;
          if (!_waiting_contexts.try_pop(waiting))
          {
             Context::Yield();
          }
    
          // Unblock the context.
          waiting->Unblock();
       }
    }
    

Exemplo

O semaphore classe neste exemplo se comporta cooperativamente porque a Context::Block e Context::Yield métodos de execução permitisse para que o runtime possa realizar outras tarefas.

O acquire diminui de método, o contador, mas talvez não terminar de adicionar o contexto para a fila de espera antes de outra chamada de contexto do release método. Para isso, o release método usa um loop de rotação que chama o Concurrency::Context::Yield método para aguardar o acquire método para concluir a adição de contexto.

O release pode chamar o método a Context::Unblock método antes do acquire chamadas de método de Context::Block método. Você não tem proteção contra essa condição de corrida, porque o tempo de execução permite esses métodos sejam chamados em qualquer ordem. Se o release chamadas de método Context::Unblock antes de acquire chamadas de método Context::Block no mesmo contexto, esse contexto permanecerá desbloqueado. O tempo de execução somente requer que cada chamada para Context::Block é comparado com uma chamada correspondente para Context::Unblock.

O exemplo a seguir mostra o completo semaphore classe. O wmain função mostra o uso básico dessa classe. O wmain função usa o Concurrency::parallel_for o algoritmo para criar várias tarefas que precisam acessar o semáforo. Porque três threads podem manter o bloqueio a qualquer momento, algumas tarefas devem aguardar outra tarefa Concluir e liberar o bloqueio.

// cooperative-semaphore.cpp
// compile with: /EHsc
#include <windows.h>
#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 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 (InterlockedDecrement(&_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 (InterlockedIncrement(&_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;
         if (!_waiting_contexts.try_pop(waiting))
         {
            Context::Yield();
         }

         // Unblock the context.
         waiting->Unblock();
      }
   }

private:
   // The semaphore count.
   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 saída de exemplo a seguir.

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...

For more information about the concurrent_queue class, see Paralelo recipientes e objetos. Para obter mais informações sobre o parallel_for o algoritmo, consulte Algoritmos paralelos.

Compilando o código

Copie o código de exemplo e colá-lo em um Visual Studio do projeto, ou colá-lo em um arquivo que é chamado cooperativo semaphore.cpp e, em seguida, execute o seguinte comando um Visual Studio 2010 janela do Prompt de comando.

cl.exe /EHsc cooperative-semaphore.cpp

Programação robusta

Você pode usar o É inicialização de aquisição de recursos padrão (RAII) para limitar o acesso a uma semaphore o objeto para um determinado escopo. Em padrão de RAII, uma estrutura de dados é alocada na pilha. Essa estrutura de dados inicializa ou adquire um recurso quando ele é criado e destrói ou libera esse recurso quando a estrutura de dados é destruída. O padrão RAII garante que o destruidor é chamado antes que sai do escopo de fechamento. Portanto, o recurso é gerenciado corretamente quando uma exceção é lançada ou uma função contém várias return instruções.

O exemplo a seguir define uma classe chamada scoped_lock, que é definido na public seção a semaphore classe. O scoped_lock classe semelhante a Concurrency::critical_section::scoped_lock e Concurrency::reader_writer_lock::scoped_lock classes. O construtor da semaphore::scoped_lock classe adquire o acesso ao determinado semaphore objeto e o destruidor libera o acesso a 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 seguinte modifica o corpo da função de trabalho que é passado para o parallel_for o algoritmo, de forma que ele usa RAII para garantir que o semáforo está lançado antes da função retorna. Essa técnica garante que a função de trabalho segura de exceção.

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

Paralelo recipientes e objetos

Outros recursos

Contextos