Compartilhar via


Instruções passo a passo: usando join para Evitar Deadlock

Este tópico usa o problema de jantar de filósofos para ilustrar como usar a classe de concurrency::join para evitar o deadlock em seu aplicativo. Em um aplicativo de software, o deadlock ocorre quando duas ou mais processos cada propriedade um recurso e mutuamente espera para que outro processo libere qualquer outro recurso.

O problema de jantar de filósofos específico é um exemplo de conjunto geral de problemas que podem ocorrer quando um conjunto de recursos é compartilhado entre vários processos simultâneos.

Pré-requisitos

Leia os tópicos a seguir antes de começar este passo a passo:

Seções

Essa explicação passo a passo contém as seguintes seções:

  • O Problema do Jantar dos Filósofos

  • Uma Implementação Ingênua

  • Usando join para Evitar Deadlock

O Problema do Jantar dos Filósofos

O problema de jantar de filósofos ilustra como o deadlock ocorre em um aplicativo. Neste problema, cinco filósofos eles ficam em uma mesa redonda. Cada filósofo alterna entre o pensamento e ter. Cada filósofo deve compartilhar um hashi com o vizinho à esquerda e outro hashi com o vizinho à direita. A ilustração a seguir mostra esse layout.

O Problema do Jantar dos Filósofos

Para ter, um filósofo deve manter dois hashis. Se cada filósofo contém apenas um hashi e está aguardando outro, nenhum filósofo pode ter e todos morrem de fome.

[Superior]

Uma Implementação Ingênua

O exemplo a seguir mostra uma implementação de naïve do problema de jantar de filósofos. A classe de philosopher , que se deriva de concurrency::agent, permite que cada filósofo para atuar independente. O exemplo usa uma matriz de objetos de concurrency::critical_section compartilhada para dar a cada objeto de philosopher acesso exclusivo para um par de hashis.

Para relacionar a implementação à ilustração, a classe de philosopher representa um filósofo. Uma variável de int representa cada hashi. Os objetos de critical_section servem como o oferece suporte em que os hashis descansam. O método de run simula a vida do filósofo. O método de think simula o ato de pensamento e o método de eat simula o ato de ter.

Um objeto de philosopher bloqueia os dois objetos de critical_section para simular a remoção de hashis da suporte antes de chamar o método de eat . Depois da chamada a eat, o objeto de philosopher retorna os hashis a suporte à definição dos objetos de critical_section de volta ao estado desbloqueado.

O método de pickup_chopsticks ilustra em que o deadlock pode ocorrer. Se cada objeto de philosopher obtém acesso a um dos bloqueios, nenhum objeto de philosopher pode continuar porque o outro bloqueio é controlado por outro objeto de philosopher .

Exemplo

Código

// philosophers-deadlock.cpp 
// compile with: /EHsc
#include <agents.h>
#include <string>
#include <array>
#include <iostream>
#include <algorithm>
#include <random>

using namespace concurrency;
using namespace std;

// Defines a single chopstick. 
typedef int chopstick;

// The total number of philosophers. 
const int philosopher_count = 5;

// The number of times each philosopher should eat. 
const int eat_count = 50;

// A shared array of critical sections. Each critical section  
// guards access to a single chopstick.
critical_section locks[philosopher_count];

// Implements the logic for a single dining philosopher. 
class philosopher : public agent 
{
public:
   explicit philosopher(chopstick& left, chopstick& right, const wstring& name)
      : _left(left)
      , _right(right)
      , _name(name)
      , _random_generator(42)
   {
      send(_times_eaten, 0);
   }

   // Retrieves the number of times the philosopher has eaten. 
   int times_eaten()
   {
      return receive(_times_eaten);
   }

   // Retrieves the name of the philosopher.
   wstring name() const
   {
      return _name;
   }

protected:
   // Performs the main logic of the dining philosopher algorithm. 
   void run()
   {
      // Repeat the thinks/eat cycle a set number of times. 
      for (int n = 0; n < eat_count; ++n)
      {
         think();

         pickup_chopsticks(); 
         eat();
         send(_times_eaten, n+1);
         putdown_chopsticks();
      }

      done();
   }

   // Gains access to the chopsticks. 
   void pickup_chopsticks()
   {
      // Deadlock occurs here if each philosopher gains access to one 
      // of the chopsticks and mutually waits for another to release 
      // the other chopstick.
      locks[_left].lock();
      locks[_right].lock();
   }

   // Releases the chopsticks for others. 
   void putdown_chopsticks()
   {
      locks[_right].unlock();
      locks[_left].unlock();
   }

   // Simulates thinking for a brief period of time. 
   void think()
   {
      random_wait(100);
   }

   // Simulates eating for a brief period of time. 
   void eat()
   { 
      random_wait(100);
   }

private:
   // Yields the current context for a random period of time. 
   void random_wait(unsigned int max)
   {
      concurrency::wait(_random_generator()%max);
   }

private:
   // Index of the left chopstick in the chopstick array.
   chopstick& _left;
   // Index of the right chopstick in the chopstick array.
   chopstick& _right;

   // The name of the philosopher.
   wstring _name;
   // Stores the number of times the philosopher has eaten.
   overwrite_buffer<int> _times_eaten;

   // A random number generator.
   mt19937 _random_generator;
};

int wmain()
{
   // Create an array of index values for the chopsticks. 
   array<chopstick, philosopher_count> chopsticks = {0, 1, 2, 3, 4};

   // Create an array of philosophers. Each pair of neighboring  
   // philosophers shares one of the chopsticks. 
   array<philosopher, philosopher_count> philosophers = {
      philosopher(chopsticks[0], chopsticks[1], L"aristotle"),
      philosopher(chopsticks[1], chopsticks[2], L"descartes"),
      philosopher(chopsticks[2], chopsticks[3], L"hobbes"),
      philosopher(chopsticks[3], chopsticks[4], L"socrates"),
      philosopher(chopsticks[4], chopsticks[0], L"plato"),
   };

   // Begin the simulation.
   for_each (begin(philosophers), end(philosophers), [](philosopher& p) {
      p.start();
   });

   // Wait for each philosopher to finish and print his name and the number 
   // of times he has eaten.
   for_each (begin(philosophers), end(philosophers), [](philosopher& p) {
      agent::wait(&p);
      wcout << p.name() << L" ate " << p.times_eaten() << L" times." << endl;
   });
}

Compilando o código

Copie o código de exemplo e cole-o em um projeto do Visual Studio, ou cole-o em um arquivo chamado philosophers-deadlock.cpp e execute o comando a seguir em uma janela de prompt de comando do Visual Studio.

cl.exe /EHsc philosophers-deadlock.cpp

[Superior]

Usando join para Evitar Deadlock

Esta seção mostra como usar buffers de mensagem e funções mensagem- passar para eliminar a possibilidade de deadlock.

Para relacionar este exemplo a anterior, a classe de philosopher substitui cada objeto de critical_section usando um objeto de concurrency::unbounded_buffer e um objeto de join . O objeto de join serve como um árbitro que fornece os hashis ao filósofo.

Este exemplo usa a classe de unbounded_buffer porque quando um destino recebe uma mensagem de um objeto de unbounded_buffer , a mensagem será removida da fila de mensagens. Isso permite que um objeto de unbounded_buffer que contém uma mensagem para indicar que o hashi está disponível. Um objeto de unbounded_buffer que não contém nenhuma mensagem indica que o hashi está sendo usado.

Este exemplo usa um objeto não ávido de join como uma junção não ávido de cada acesso do objeto de philosopher a ambos os hashis somente quando os dois objetos de unbounded_buffer contêm uma mensagem. Uma junção ávido não impede que exiba o deadlock como uma junção ávido aceita mensagens assim que se tornassem disponíveis. O deadlock pode ocorrer se todos os objetos ávidos de join recebem uma das mensagens mas a esperar indefinidamente para que o outro se torna disponíveis.

Para obter mais informações sobre junções ávidos e não ávidos, e as diferenças entre os vários tipos de buffer de mensagem, consulte Blocos de mensagens assíncronos.

Para evitar deadlock neste exemplo

  1. Remova o código de exemplo a seguir.

    // A shared array of critical sections. Each critical section  
    // guards access to a single chopstick.
    critical_section locks[philosopher_count];
    
  2. Alterar o tipo de membros de dados de _left e de _right da classe de philosopher a unbounded_buffer.

    // Message buffer for the left chopstick.
    unbounded_buffer<chopstick>& _left;
    // Message buffer for the right chopstick.
    unbounded_buffer<chopstick>& _right;
    
  3. Modifique o construtor de philosopher para usar objetos de unbounded_buffer como seus parâmetros.

    explicit philosopher(unbounded_buffer<chopstick>& left, 
       unbounded_buffer<chopstick>& right, const wstring& name)
       : _left(left)
       , _right(right)
       , _name(name)
       , _random_generator(42)
    {
       send(_times_eaten, 0);
    }
    
  4. Modifique o método de pickup_chopsticks para usar um objeto não ávido de join para receber mensagens dos buffers de mensagem para ambos os hashis.

    // Gains access to the chopsticks.
    vector<int> pickup_chopsticks()
    {
       // Create a non-greedy join object and link it to the left and right  
       // chopstick.
       join<chopstick, non_greedy> j(2);
       _left.link_target(&j);
       _right.link_target(&j);
    
       // Receive from the join object. This resolves the deadlock situation 
       // because a non-greedy join removes the messages only when a message 
       // is available from each of its sources. 
       return receive(&j);
    }
    
  5. Modifique o método de putdown_chopsticks para liberar o acesso a hashis enviando uma mensagem aos buffers de mensagem para ambos os hashis.

    // Releases the chopsticks for others. 
    void putdown_chopsticks(int left, int right)
    {
       // Add the values of the messages back to the message queue.
       asend(&_left, left);
       asend(&_right, right);
    }
    
  6. Modifique o método de run para manter os resultados do método de pickup_chopsticks e passar esses resultados ao método de putdown_chopsticks .

    // Performs the main logic of the dining philosopher algorithm. 
    void run()
    {
       // Repeat the thinks/eat cycle a set number of times. 
       for (int n = 0; n < eat_count; ++n)
       {
          think();
    
          vector<int> v = pickup_chopsticks(); 
    
          eat();
    
          send(_times_eaten, n+1);
    
          putdown_chopsticks(v[0], v[1]);
       }
    
       done();
    }
    
  7. Modifique a declaração de variável de chopsticks na função de wmain para ser uma matriz de objetos que unbounded_buffer cada mensagem de uma propriedade.

    // Create an array of message buffers to hold the chopsticks. 
    array<unbounded_buffer<chopstick>, philosopher_count> chopsticks;
    
    // Send a value to each message buffer in the array. 
    // The value of the message is not important. A buffer that contains 
    // any message indicates that the chopstick is available.
    for_each (begin(chopsticks), end(chopsticks), 
       [](unbounded_buffer<chopstick>& c) {
          send(c, 1);
    });
    

Exemplo

Descrição

O exemplo a seguir mostra que o exemplo completo que usa join não ávido objetos para eliminar o risco de deadlock.

Código

// philosophers-join.cpp 
// compile with: /EHsc
#include <agents.h>
#include <string>
#include <array>
#include <iostream>
#include <algorithm>
#include <random>

using namespace concurrency;
using namespace std;

// Defines a single chopstick. 
typedef int chopstick;

// The total number of philosophers. 
const int philosopher_count = 5;

// The number of times each philosopher should eat. 
const int eat_count = 50;

// Implements the logic for a single dining philosopher. 
class philosopher : public agent 
{
public:
   explicit philosopher(unbounded_buffer<chopstick>& left, 
      unbounded_buffer<chopstick>& right, const wstring& name)
      : _left(left)
      , _right(right)
      , _name(name)
      , _random_generator(42)
   {
      send(_times_eaten, 0);
   }

   // Retrieves the number of times the philosopher has eaten. 
   int times_eaten()
   {
      return receive(_times_eaten);
   }

   // Retrieves the name of the philosopher.
   wstring name() const
   {
      return _name;
   }

protected:
   // Performs the main logic of the dining philosopher algorithm. 
   void run()
   {
      // Repeat the thinks/eat cycle a set number of times. 
      for (int n = 0; n < eat_count; ++n)
      {
         think();

         vector<int> v = pickup_chopsticks(); 

         eat();

         send(_times_eaten, n+1);

         putdown_chopsticks(v[0], v[1]);
      }

      done();
   }

   // Gains access to the chopsticks.
   vector<int> pickup_chopsticks()
   {
      // Create a non-greedy join object and link it to the left and right  
      // chopstick.
      join<chopstick, non_greedy> j(2);
      _left.link_target(&j);
      _right.link_target(&j);

      // Receive from the join object. This resolves the deadlock situation 
      // because a non-greedy join removes the messages only when a message 
      // is available from each of its sources. 
      return receive(&j);
   }

   // Releases the chopsticks for others. 
   void putdown_chopsticks(int left, int right)
   {
      // Add the values of the messages back to the message queue.
      asend(&_left, left);
      asend(&_right, right);
   }

   // Simulates thinking for a brief period of time. 
   void think()
   {
      random_wait(100);
   }

   // Simulates eating for a brief period of time. 
   void eat()
   {      
      random_wait(100);      
   }

private:
   // Yields the current context for a random period of time. 
   void random_wait(unsigned int max)
   {
      concurrency::wait(_random_generator()%max);
   }

private:
   // Message buffer for the left chopstick.
   unbounded_buffer<chopstick>& _left;
   // Message buffer for the right chopstick.
   unbounded_buffer<chopstick>& _right;

   // The name of the philosopher.
   wstring _name;
   // Stores the number of times the philosopher has eaten.
   overwrite_buffer<int> _times_eaten;

   // A random number generator.
   mt19937 _random_generator;
};

int wmain()
{
   // Create an array of message buffers to hold the chopsticks. 
   array<unbounded_buffer<chopstick>, philosopher_count> chopsticks;

   // Send a value to each message buffer in the array. 
   // The value of the message is not important. A buffer that contains 
   // any message indicates that the chopstick is available.
   for_each (begin(chopsticks), end(chopsticks), 
      [](unbounded_buffer<chopstick>& c) {
         send(c, 1);
   });

   // Create an array of philosophers. Each pair of neighboring  
   // philosophers shares one of the chopsticks. 
   array<philosopher, philosopher_count> philosophers = {
      philosopher(chopsticks[0], chopsticks[1], L"aristotle"),
      philosopher(chopsticks[1], chopsticks[2], L"descartes"),
      philosopher(chopsticks[2], chopsticks[3], L"hobbes"),
      philosopher(chopsticks[3], chopsticks[4], L"socrates"),
      philosopher(chopsticks[4], chopsticks[0], L"plato"),
   };

   // Begin the simulation.
   for_each (begin(philosophers), end(philosophers), [](philosopher& p) {
      p.start();
   });

   // Wait for each philosopher to finish and print his name and the number 
   // of times he has eaten.
   for_each (begin(philosophers), end(philosophers), [](philosopher& p) {
      agent::wait(&p);
      wcout << p.name() << L" ate " << p.times_eaten() << L" times." << endl;
   });
}

Comentários

O exemplo produz a seguinte saída.

  

Compilando o código

Copie o código de exemplo e cole-o em um projeto do Visual Studio, ou cole-o em um arquivo chamado philosophers-join.cpp e execute o comando a seguir em uma janela de prompt de comando do Visual Studio.

cl.exe /EHsc philosophers-join.cpp

[Superior]

Consulte também

Conceitos

Biblioteca de Agentes Assíncronos

Agentes assíncronos

Blocos de mensagens assíncronos

Funções de transmissão de mensagem

Estruturas de dados de sincronização

Outros recursos

Instruções passo a passo do Tempo de Execução de Simultaneidade