İzlenecek yol: Kilitlenmeyi Önlemek için birleştirme kullanma

Bu konu, uygulamanızda kilitlenmeyi önlemek için concurrency::join sınıfının nasıl kullanılacağını göstermek için yemek filozofları sorununu kullanır. Yazılım uygulamasında, her biri bir kaynağı tutan ve başka bir işlemin başka bir kaynağı serbest bırakmasını karşılıklı olarak bekleyen iki veya daha fazla işlem olduğunda kilitlenme oluşur.

Yemek filozofları sorunu, bir dizi kaynak birden çok eşzamanlı işlem arasında paylaşıldığında ortaya çıkabilecek genel sorun kümesinin belirli bir örneğidir.

Önkoşullar

Bu kılavuza başlamadan önce aşağıdaki konuları okuyun:

Bölümler

Bu izlenecek yol aşağıdaki bölümleri içerir:

Yemek Filozofları Sorunu

Yemek filozofları sorunu, bir uygulamada kilitlenmenin nasıl gerçekleştiğini göstermektedir. Bu problemde beş filozof yuvarlak bir masada oturuyor. Her filozof düşünmekle yemek arasında geçiştir. Her filozof, soldaki komşuyla bir yemek çubuğu ve sağda komşuyla başka bir yemek çubuğu paylaşmalıdır. Aşağıdaki çizimde bu düzen gösterilmektedir.

Yemek Filozofları Sorunu.

Bir filozof yemek için iki yemek çubuğu tutmalı. Her filozof sadece bir yemek çubuğu tutarsa ve başka bir tane bekliyorsa, hiçbir filozof yemek yiyemez ve açlıktan ölür.

[Üst]

Saf Bir Uygulama

Aşağıdaki örnekte yemek filozofları sorununun basit bir uygulaması gösterilmektedir. philosopher concurrency::agent öğesinden türetilen sınıfı, her filozofu bağımsız olarak hareket etmeye olanak tanır. Örnek, her philosopher nesneye bir çift yemek çubuğuna özel erişim vermek için paylaşılan eşzamanlılık::critical_section nesneleri dizisi kullanır.

Uygulamayı çizimle ilişkilendirmek için sınıfı philosopher bir filozofu temsil eder. Değişken int her bir yemek çubuğunu temsil eder. Nesneler, critical_section çubukların dayandığı tutucular görevi görür. yöntemi, run filozofun hayatının simülasyonunu oluşturur. think yöntemi, düşünme eyleminin simülasyonunu, yöntem ise eat yeme eylemini simüle eder.

Bir philosopher nesne, yöntemi çağırmadan eat önce çubukların tutuculardan kaldırılmasının benzetimini yapmak için her iki nesneyi de critical_section kilitler. çağrısından eatphilosopher sonra nesne, nesneleri kilitsiz duruma geri ayarlayarak critical_section çubukları tutuculara döndürür.

pickup_chopsticks yöntemi, kilitlenmenin nerede gerçekleşebileceğini gösterir. Her philosopher nesne kilitlerden birine erişim kazanırsa, diğer kilit başka bir philosopher nesne tarafından denetlendiğinden hiçbir philosopher nesne devamlenemez.

Örnek

// 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;
   });
}

Kod Derleniyor

Örnek kodu kopyalayıp bir Visual Studio projesine yapıştırın veya adlı philosophers-deadlock.cpp bir dosyaya yapıştırın ve ardından bir Visual Studio Komut İstemi penceresinde aşağıdaki komutu çalıştırın.

cl.exe /EHsc philosophers-deadlock.cpp

[Üst]

Kilitlenmeyi Önlemek için birleştirme kullanma

Bu bölümde kilitlenme olasılığını ortadan kaldırmak için ileti arabelleklerinin ve ileti geçirme işlevlerinin nasıl kullanılacağı gösterilmektedir.

Bu örneği önceki örnekle ilişkilendirmek için, sınıfı her nesnenin yerine eşzamanlılık::unbounded_buffer nesnesi ve bir join nesne critical_section ekler.philosopher Nesne, join filozofa çubuklar sağlayan bir arbiter görevi görür.

Bir hedef bir nesneden ileti aldığında ileti ileti kuyruğundan unbounded_buffer kaldırıldığından bu örnek sınıfını kullanırunbounded_buffer. Bu, ileti tutan bir unbounded_buffer nesnenin, yemek çubuğunun kullanılabilir olduğunu belirtmesini sağlar. unbounded_buffer İleti içermeyen bir nesne, yemek çubuğunun kullanıldığını gösterir.

Bu örnekte doyumsuz join olmayan bir nesne kullanılır çünkü doyumsuz olmayan bir birleşim her nesnenin her philosopher iki yemek çubuğuna da yalnızca her iki unbounded_buffer nesne de ileti içerdiğinde erişmesini sağlar. Doyumsuz bir birleşim, doyumsuz bir birleşim kullanılabilir duruma gelir gelmez iletileri kabul ettiğinden kilitlenmeyi engellemez. Tüm doyumsuz join nesneler iletilerden birini alır ancak diğerinin kullanılabilir olmasını sonsuza kadar beklerse kilitlenme oluşabilir.

Doyumsuz ve doyumsuz olmayan birleşimler ve çeşitli ileti arabelleği türleri arasındaki farklar hakkında daha fazla bilgi için bkz . Zaman Uyumsuz İleti Blokları.

Bu örnekte kilitlenmeyi önlemek için

  1. Örnekten aşağıdaki kodu kaldırın.
// A shared array of critical sections. Each critical section 
// guards access to a single chopstick.
critical_section locks[philosopher_count];
  1. sınıfının ve _right veri üyelerinin philosopher türünü _left olarak unbounded_bufferdeğiştirin.
// Message buffer for the left chopstick.
unbounded_buffer<chopstick>& _left;
// Message buffer for the right chopstick.
unbounded_buffer<chopstick>& _right;
  1. Oluşturucuyu philosopher , nesneleri parametresi olarak alacak unbounded_buffer şekilde değiştirin.
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);
}
  1. pickup_chopsticks Her iki yemek çubuğu için de ileti arabelleklerinden ileti almak üzere doyumsuz join olmayan bir nesne kullanmak üzere yöntemini değiştirin.
// 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);
}
  1. putdown_chopsticks Her iki yemek çubuğu için de ileti arabelleklerine bir ileti göndererek chopstick'lere erişimi serbest bırakmak için yöntemini değiştirin.
// 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);
}
  1. run yöntemini, yönteminin pickup_chopsticks sonuçlarını tutacak ve bu sonuçları yönteme geçirecek şekilde putdown_chopsticks değiştirin.
// 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();
}
  1. İşlevdeki değişkenin chopsticks bildirimini wmain , her birinin bir iletiyi tutan bir nesne dizisi unbounded_buffer olacak şekilde değiştirin.
// 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);
});

Açıklama

Aşağıda, kilitlenme riskini ortadan kaldırmak için doyumsuz join olmayan nesnelerin kullanıldığı tamamlanmış örnek gösterilmektedir.

Örnek

// 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;
   });
}

Bu örnek aşağıdaki çıkışı oluşturur.

aristotle ate 50 times.
descartes ate 50 times.
hobbes ate 50 times.
socrates ate 50 times.
plato ate 50 times.

Kod Derleniyor

Örnek kodu kopyalayıp bir Visual Studio projesine yapıştırın veya adlı philosophers-join.cpp bir dosyaya yapıştırın ve ardından bir Visual Studio Komut İstemi penceresinde aşağıdaki komutu çalıştırın.

cl.exe /EHsc philosophers-join.cpp

[Üst]

Ayrıca bkz.

Eşzamanlılık Çalışma Zamanı İzlenecek Yollar
Zaman Uyumsuz Aracılar Kitaplığı
Zaman Uyumsuz Aracılar
Zaman Uyumsuz İleti Blokları
İleti Geçirme İşlevleri
Eşitleme Veri Yapıları