İ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.
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 eat
philosopher
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
- Ö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];
- sınıfının ve
_right
veri üyelerininphilosopher
türünü_left
olarakunbounded_buffer
değiştirin.
// Message buffer for the left chopstick.
unbounded_buffer<chopstick>& _left;
// Message buffer for the right chopstick.
unbounded_buffer<chopstick>& _right;
- Oluşturucuyu
philosopher
, nesneleri parametresi olarak alacakunbounded_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);
}
pickup_chopsticks
Her iki yemek çubuğu için de ileti arabelleklerinden ileti almak üzere doyumsuzjoin
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);
}
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);
}
run
yöntemini, yöntemininpickup_chopsticks
sonuçlarını tutacak ve bu sonuçları yönteme geçirecek şekildeputdown_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();
}
- İşlevdeki değişkenin
chopsticks
bildiriminiwmain
, her birinin bir iletiyi tutan bir nesne dizisiunbounded_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ı