Yazılım mimarisindeki modeller: nesneyi izleyin
Modeller, modern yazılımın geliştirilmesinde ve yazılımın mimarisinde önemli bir soyutlamadır. Açıkça tanımlanmış terminoloji, temiz dokümantasyon ve öğrenme en iyisinden sunarlar. “Desen odaklı yazılım mimarisi: Eşitler ve Ağ Nesneleri için Desenler” kitabı bir monitör nesnesini aşağıdaki gibi tanımlar: “Monitör tasarım modeli, bir seferde yalnızca üyenin işlevinin bir nesne içinde performans göstermesini sağlamak için rakip üyenin işlevinin yürütülmesini senkronize eder. Bu nedenle, nesnelerin üyelerinin işlevlerinin işbirliğinde yürütme dizilerini programlamasına izin verir. “
Rainer Grimm yıllardır yazılım mimarı, ekip ve eğitim müdürü olarak çalıştı. C ++ programlama dilleri, Python ve Haskell hakkında makaleler yazmayı seviyor, ancak uzman konferanslarla konuşmayı da seviyor. Modern C ++ blogunda, C ++ tutkusuyla yoğun bir şekilde ilgileniyor.
Tasarım monitörünün nesnesi, bir nesne içinde yalnızca bir üye işlevinin gerçekleştirilmesini sağlamak için üye işlevlerinin eşzamanlı olarak yürütülmesini senkronize eder. Ayrıca, bir nesnenin üyesinin işlevlerinin yürütme dizilerini birlikte planlamasına izin verir.
Olarak da bilinir
Birçok iş parçacığı aynı anda ortak bir nesneye erişiyorsa, aşağıdaki zorluklar vardır:
Bir istemci (iş parçacığı), monitör nesnesinin senkronize üyelerinin işlevlerine erişebilir ve monitörün bloğu nedeniyle, belirli bir anda yalnızca senkronize bir üye işlevi gerçekleştirilebilir. Her monitör nesnesinin bekleyen istemcileri bildiren bir monitör koşulu vardır.
Bileşenler
Dinamik davranış
Monitör nesnesi ve bileşenleri arasındaki etkileşim farklı aşamalarda çalışır.
Monitör nesnesinin avantajları ve dezavantajları nelerdir?
Avantajlar
Aşağıdaki örnek bir dosyayı tanımlar ThreadSafeQueue.
// monitorObject.cpp
#include <condition_variable>
#include <functional>
#include <queue>
#include <iostream>
#include <mutex>
#include <random>
#include <thread>
class Monitor {
public:
void lock() const {
monitMutex.lock();
}
void unlock() const {
monitMutex.unlock();
}
void notify_one() const noexcept {
monitCond.notify_one();
}
template <typename Predicate>
void wait(Predicate pred) const { // (10)
std::unique_lock<std::mutex> monitLock(monitMutex);
monitCond.wait(monitLock, pred);
}
private:
mutable std::mutex monitMutex;
mutable std::condition_variable monitCond;
};
template <typename T> // (1)
class ThreadSafeQueue: public Monitor {
public:
void add(T val){
lock();
myQueue.push(val); // (6)
unlock();
notify_one();
}
T get(){
wait( [this] { return ! myQueue.empty(); } ); // (2)
lock();
auto val = myQueue.front(); // (4)
myQueue.pop(); // (5)
unlock();
return val;
}
private:
std::queue<T> myQueue; // (3)
};
class Dice {
public:
int operator()(){ return rand(); }
private:
std::function<int()> rand =
std::bind(std::uniform_int_distribution<>(1, 6),
std::default_random_engine());
};
int main(){
std::cout << 'n';
constexpr auto NumberThreads = 10;
ThreadSafeQueue<int> safeQueue; // (7)
auto addLambda = [&safeQueue](int val){
safeQueue.add(val); // (8)
std::cout << val << " "
<< std::this_thread::get_id() << "; ";
};
auto getLambda = [&safeQueue]{ safeQueue.get(); }; // (9)
std::vector<std::thread> addThreads(NumberThreads);
Dice dice;
for (auto& thr: addThreads) thr =
std::thread(addLambda, dice() );
std::vector<std::thread> getThreads(NumberThreads);
for (auto& thr: getThreads) thr = std::thread(getLambda);
for (auto& thr: addThreads) thr.join();
for (auto& thr: getThreads) thr.join();
std::cout << "nn";
}
Örneğin ana fikri, monitör nesnesinin bir sınıfta kapsüllenmesi ve bu nedenle yeniden kullanılabilmesidir. Sınıf Monitor Kullanın std::mutex Monitörün bir bloğu ve bir Monitör Durumu olarak bir std :: couse_variable durum olarak. Sınıf Monitor Bir monitörün desteklemesi gereken minimum arayüzü sunar.
ThreadSafeQueue (1) genişletilmiş std::queue Bir iş parçacığında -SAFE arayüzünde. ThreadSafeQueue Sınıf tarafından yönetiliyor Monitor Senkronize üyelerin işlevleri için üyelerinin işlevlerini kapalı ve kullanın add VE get Desteklemek için. Üye çalışıyor add VE get Monitör nesnesini korumak için monitör bloğunu kullanın. Bu özellikle iplik dışı sicuro için geçerlidir myQueue. add Yeni bir unsur olduğunda iş parçacığını bekletin myQueue Eklendi. Bu bildirim güvenlidir. Üye işlevi get (3) Daha fazla ilgiyi hak ediyor. Önce wait-Altta yatan durumun değişkenlerinin üyelerinin işlevi çağrıldı. Bu wait-Caccification, sizi kayıp ve özelliklerden korumak için ek bir yüklem gerektirir (C ++ Yönergeleri: Koşulların değişkenlerinin tehlikelerinin farkında olun). Dosyayı değiştirmek için işlemler myQueue (4) ve (5) de korunmalıdır çünkü bunlar myQueue.push(val) (6) Çakışabilir. Monitör nesnesi safeQueue (7) (8) ve (9) 'deki lambda işlevlerini senkronize olan bir sayıya kullanın safeQueue Ekle veya kaldırın. ThreadSafeQueue Aynı zamanda bir sınıf sınıfıdır ve her türlü değerleri emebilir. Yüz müşteri safeQueue 1 ila 6 arasında 100 rastgele sayı (satır 7), yüz kişi bu 100 sayıdan safeQueue kaldırmak. Programın programı konu numaralarını ve kimliklerini gösterir.
C ++ 20 ile program olabilir monitorObject.cpp daha da geliştirilecek. Önce başlığı ekliyorum <concepts> ve kavramı kullanır std:
redicate Fonksiyonel modelde sınırlı bir model parametresi olarak wait (10). Kavram std:
redicate fonksiyonel modelin wait Sadece bir yüklemle başlatılabilir. Tahminler buna göre bir boole değeri döndüren canabil'dir.
template <std:
redicate Predicate>
void wait(Predicate pred) const {
std::unique_lock<std::mutex> monitLock(monitMutex);
monitCond.wait(monitLock, pred);
}
İkincisi, std :: jthread yerine kullanıyorum std::thread. std::jthread Bu bir gelişme std::thread C ++ 20'de, yoksulunda otomatik olarak join Bu gerekliyse arayın.
int main() {
std::cout << 'n';
constexpr auto NumberThreads = 100;
ThreadSafeQueue<int> safeQueue;
auto addLambda = [&safeQueue](int val){
safeQueue.add(val);
std::cout << val << " "
<< std::this_thread::get_id() << "; ";
};
auto getLambda = [&safeQueue]{ safeQueue.get(); };
std::vector<std::jthread> addThreads(NumberThreads);
Dice dice;
for (auto& thr: addThreads) thr =
std::jthread(addLambda, dice());
std::vector<std::jthread> getThreads(NumberThreads);
for (auto& thr: getThreads) thr = std::jthread(getLambda);
std::cout << "nn";
}
Aktif nesne ve monitör nesnesi benzerdir, ancak bazı önemli noktalarda farklılık gösterir. Her iki mimari model de ortak bir nesneye erişimi senkronize eder. Aktif bir nesnenin üyesinin işlevleri başka bir iş parçacığında gerçekleştirilir, ancak monitör nesnesinin üyesinin işlevleri aynı iş parçacığında gerçekleştirilir. Etkin nesneler, üyenin çağrı işlevlerini üyenin işlevlerinin yürütülmesinden daha iyi parçalar ve bu nedenle beklemesi daha kolaydır.
Sırada ne var?
Tamamlamak! Tasarım modellerine yaklaşık 50 ürün yazdım. Sonraki makalelerimde, C ++ 17'de oldukça bilinmeyen bir işlev hakkında yazacağım, kendinizi C ++ 20'ye daldıracağım ve yakın yeni C ++-C ++ 23 standardını tanıtacağım. Bu geziye C ++ 23 ile başlıyorum.
(RME)
Yazılım mimarisindeki modeller: nesneyi izleyin
Modeller, modern yazılımın geliştirilmesinde ve yazılımın mimarisinde önemli bir soyutlamadır. Açıkça tanımlanmış terminoloji, temiz dokümantasyon ve öğrenme en iyisinden sunarlar. “Desen odaklı yazılım mimarisi: Eşitler ve Ağ Nesneleri için Desenler” kitabı bir monitör nesnesini aşağıdaki gibi tanımlar: “Monitör tasarım modeli, bir seferde yalnızca üyenin işlevinin bir nesne içinde performans göstermesini sağlamak için rakip üyenin işlevinin yürütülmesini senkronize eder. Bu nedenle, nesnelerin üyelerinin işlevlerinin işbirliğinde yürütme dizilerini programlamasına izin verir. “

Rainer Grimm yıllardır yazılım mimarı, ekip ve eğitim müdürü olarak çalıştı. C ++ programlama dilleri, Python ve Haskell hakkında makaleler yazmayı seviyor, ancak uzman konferanslarla konuşmayı da seviyor. Modern C ++ blogunda, C ++ tutkusuyla yoğun bir şekilde ilgileniyor.

Tasarım monitörünün nesnesi, bir nesne içinde yalnızca bir üye işlevinin gerçekleştirilmesini sağlamak için üye işlevlerinin eşzamanlı olarak yürütülmesini senkronize eder. Ayrıca, bir nesnenin üyesinin işlevlerinin yürütme dizilerini birlikte planlamasına izin verir.
Olarak da bilinir
- İplik -Pasif Nesne
Birçok iş parçacığı aynı anda ortak bir nesneye erişiyorsa, aşağıdaki zorluklar vardır:
- Eşzamanlı erişim nedeniyle, ortak nesne veri yarışmalarından kaçınmak için dengesiz okuma ve yazma işlemlerinden korunmalıdır.
- Gerekli senkronizasyon, arayüzün bir parçası değil, uygulamanın bir parçası olmalıdır.
- Bir iş parçacığı ortak nesne ile sona erdiğinde, bir sonraki iş parçacığının ortak nesneyi kullanabilmesi için bir bildirimi etkinleştirmek gerekir. Bu mekanizma sistem performansını geliştirir.
- Bir üye işlevinin yürütülmesinden sonra, birlikte kullanılan mülkün değişmezliği korunmalıdır.
Bir istemci (iş parçacığı), monitör nesnesinin senkronize üyelerinin işlevlerine erişebilir ve monitörün bloğu nedeniyle, belirli bir anda yalnızca senkronize bir üye işlevi gerçekleştirilebilir. Her monitör nesnesinin bekleyen istemcileri bildiren bir monitör koşulu vardır.
Bileşenler

- Nesneyi izleyin: Nesnenin monitörü, üyelerin bir veya daha fazla işlevini destekler. Her istemci bu üye işlevleri aracılığıyla mülke erişmeli ve her üye işlevi istemci iş parçacığında gerçekleştirilir.
- Senkronize üyelerin işlevleri: Senkronize üyelerin işlevleri, Monitör nesnesi tarafından desteklenen üyenin işlevleridir. Belli bir anda tek bir üye işlevi gerçekleştirmek mümkündür. İplik güvenli arayüzü, arayüzü temsil eden üyenin işlevleri (senkronize üyelerin işlevleri) ile uygulamayı temsil eden üyenin işlevleri arasında ayrım yapmaya yardımcı olur.
- Monitör Bloğu: Her Monitör nesnesi, bir zamanda bir istemcinin Monitör nesnesine erişebileceğini garanti eden bir monitör bloğuna sahiptir.
- İzleme Koşulları: Monitörün durumu, ayrı iş parçacıklarının, Monitör nesnesindeki üyenin işlevlerine yönelik çağrılarını planlamasına izin verir. Mevcut istemci senkronize üye işlevlerini çağırmaya hazır olduğunda, bir sonraki istemci monitör nesnesinin senkronize üyelerinin işlevlerine erişmek için uyanır.
Dinamik davranış
Monitör nesnesi ve bileşenleri arasındaki etkileşim farklı aşamalarda çalışır.
- Bir istemci bir Monitör nesnesinde senkronize bir üye işlevini çağırırsa, önce Global Monitör bloğunu oluşturmalıdır. Başarılı bloktan sonra, senkronize üyenin işlevini yerine getirir ve sonunda monitörün bloğunu yükseltir. Bloğun olumlu bir sonucu yoksa, müşteri engellenir.
- Müşteri engellendiğinde, monitör koşulunun bir bildirim göndermesini bekler. Bu bildirim, kontrol monitörü çözüldüğünde gerçekleşir. Bildirim artık bekleyen bir veya tüm müşteriye gönderilebilir. Bu şekilde beklemek genellikle taahhüt edilen beklentinin aksine uykuyu kurtarmak anlamına gelir.
- Bir müşteri işe devam etmesi gereken bildirimi alırsa, monitör bloğunu koyun ve senkronize üyenin işlevini gerçekleştirin. Monitör bloğu, senkronize işlevin sonunda tekrar serbest bırakılır. Monitör koşulları, bir sonraki istemcinin senkronize üye işlevini yerine getirebileceğini bildirmek için bir bildirim gönderir.
Monitör nesnesinin avantajları ve dezavantajları nelerdir?
Avantajlar
- Müşteri, Monitör nesnesinin örtük senkronizasyonu hakkında hiçbir şey bilmiyor ve senkronizasyon uygulamada tamamen takizle.
- Senkronize üye çağrılarının işlevleri nihayet otomatik olarak planlanır. Monitör koşullarının bildirim/bakım mekanizması basit bir programcı olarak davranır.
- Genellikle senkronize üyelerin işlevlerinin senkronizasyon mekanizmasını değiştirmek oldukça zordur, çünkü işlevsellik ve senkronizasyon güçlü bir şekilde birleştirilir.
- Senkronize bir üye işlevi aynı monitör nesnesini doğrudan veya dolaylı olarak çağırırsa, bir kilitlenme oluşabilir.
Aşağıdaki örnek bir dosyayı tanımlar ThreadSafeQueue.
// monitorObject.cpp
#include <condition_variable>
#include <functional>
#include <queue>
#include <iostream>
#include <mutex>
#include <random>
#include <thread>
class Monitor {
public:
void lock() const {
monitMutex.lock();
}
void unlock() const {
monitMutex.unlock();
}
void notify_one() const noexcept {
monitCond.notify_one();
}
template <typename Predicate>
void wait(Predicate pred) const { // (10)
std::unique_lock<std::mutex> monitLock(monitMutex);
monitCond.wait(monitLock, pred);
}
private:
mutable std::mutex monitMutex;
mutable std::condition_variable monitCond;
};
template <typename T> // (1)
class ThreadSafeQueue: public Monitor {
public:
void add(T val){
lock();
myQueue.push(val); // (6)
unlock();
notify_one();
}
T get(){
wait( [this] { return ! myQueue.empty(); } ); // (2)
lock();
auto val = myQueue.front(); // (4)
myQueue.pop(); // (5)
unlock();
return val;
}
private:
std::queue<T> myQueue; // (3)
};
class Dice {
public:
int operator()(){ return rand(); }
private:
std::function<int()> rand =
std::bind(std::uniform_int_distribution<>(1, 6),
std::default_random_engine());
};
int main(){
std::cout << 'n';
constexpr auto NumberThreads = 10;
ThreadSafeQueue<int> safeQueue; // (7)
auto addLambda = [&safeQueue](int val){
safeQueue.add(val); // (8)
std::cout << val << " "
<< std::this_thread::get_id() << "; ";
};
auto getLambda = [&safeQueue]{ safeQueue.get(); }; // (9)
std::vector<std::thread> addThreads(NumberThreads);
Dice dice;
for (auto& thr: addThreads) thr =
std::thread(addLambda, dice() );
std::vector<std::thread> getThreads(NumberThreads);
for (auto& thr: getThreads) thr = std::thread(getLambda);
for (auto& thr: addThreads) thr.join();
for (auto& thr: getThreads) thr.join();
std::cout << "nn";
}
Örneğin ana fikri, monitör nesnesinin bir sınıfta kapsüllenmesi ve bu nedenle yeniden kullanılabilmesidir. Sınıf Monitor Kullanın std::mutex Monitörün bir bloğu ve bir Monitör Durumu olarak bir std :: couse_variable durum olarak. Sınıf Monitor Bir monitörün desteklemesi gereken minimum arayüzü sunar.
ThreadSafeQueue (1) genişletilmiş std::queue Bir iş parçacığında -SAFE arayüzünde. ThreadSafeQueue Sınıf tarafından yönetiliyor Monitor Senkronize üyelerin işlevleri için üyelerinin işlevlerini kapalı ve kullanın add VE get Desteklemek için. Üye çalışıyor add VE get Monitör nesnesini korumak için monitör bloğunu kullanın. Bu özellikle iplik dışı sicuro için geçerlidir myQueue. add Yeni bir unsur olduğunda iş parçacığını bekletin myQueue Eklendi. Bu bildirim güvenlidir. Üye işlevi get (3) Daha fazla ilgiyi hak ediyor. Önce wait-Altta yatan durumun değişkenlerinin üyelerinin işlevi çağrıldı. Bu wait-Caccification, sizi kayıp ve özelliklerden korumak için ek bir yüklem gerektirir (C ++ Yönergeleri: Koşulların değişkenlerinin tehlikelerinin farkında olun). Dosyayı değiştirmek için işlemler myQueue (4) ve (5) de korunmalıdır çünkü bunlar myQueue.push(val) (6) Çakışabilir. Monitör nesnesi safeQueue (7) (8) ve (9) 'deki lambda işlevlerini senkronize olan bir sayıya kullanın safeQueue Ekle veya kaldırın. ThreadSafeQueue Aynı zamanda bir sınıf sınıfıdır ve her türlü değerleri emebilir. Yüz müşteri safeQueue 1 ila 6 arasında 100 rastgele sayı (satır 7), yüz kişi bu 100 sayıdan safeQueue kaldırmak. Programın programı konu numaralarını ve kimliklerini gösterir.

C ++ 20 ile program olabilir monitorObject.cpp daha da geliştirilecek. Önce başlığı ekliyorum <concepts> ve kavramı kullanır std:
template <std:
void wait(Predicate pred) const {
std::unique_lock<std::mutex> monitLock(monitMutex);
monitCond.wait(monitLock, pred);
}
İkincisi, std :: jthread yerine kullanıyorum std::thread. std::jthread Bu bir gelişme std::thread C ++ 20'de, yoksulunda otomatik olarak join Bu gerekliyse arayın.
int main() {
std::cout << 'n';
constexpr auto NumberThreads = 100;
ThreadSafeQueue<int> safeQueue;
auto addLambda = [&safeQueue](int val){
safeQueue.add(val);
std::cout << val << " "
<< std::this_thread::get_id() << "; ";
};
auto getLambda = [&safeQueue]{ safeQueue.get(); };
std::vector<std::jthread> addThreads(NumberThreads);
Dice dice;
for (auto& thr: addThreads) thr =
std::jthread(addLambda, dice());
std::vector<std::jthread> getThreads(NumberThreads);
for (auto& thr: getThreads) thr = std::jthread(getLambda);
std::cout << "nn";
}
Aktif nesne ve monitör nesnesi benzerdir, ancak bazı önemli noktalarda farklılık gösterir. Her iki mimari model de ortak bir nesneye erişimi senkronize eder. Aktif bir nesnenin üyesinin işlevleri başka bir iş parçacığında gerçekleştirilir, ancak monitör nesnesinin üyesinin işlevleri aynı iş parçacığında gerçekleştirilir. Etkin nesneler, üyenin çağrı işlevlerini üyenin işlevlerinin yürütülmesinden daha iyi parçalar ve bu nedenle beklemesi daha kolaydır.
Sırada ne var?
Tamamlamak! Tasarım modellerine yaklaşık 50 ürün yazdım. Sonraki makalelerimde, C ++ 17'de oldukça bilinmeyen bir işlev hakkında yazacağım, kendinizi C ++ 20'ye daldıracağım ve yakın yeni C ++-C ++ 23 standardını tanıtacağım. Bu geziye C ++ 23 ile başlıyorum.
(RME)