Yazılım mimarisinde kalıp: Nesneyi İzle

Adanali

Active member
Yazılım mimarisinde kalıp: Nesneyi İzle


  1. Yazılım mimarisinde kalıp: Nesneyi İzle

Modeller, modern yazılım geliştirme ve yazılım mimarisinde önemli bir soyutlamadır. İyi tanımlanmış terminoloji, açık belgeler sunar ve en iyisinden öğrenirler. “Pattern-Oriented Software Architecture: Patterns for Concurrent and Networked Objects” kitabı bir monitör nesnesini şu şekilde tanımlar: “İzleme nesnesi tasarım modeli, aynı anda bir nesne içinde yalnızca bir üye işlevin yürütülmesini sağlamak için eşzamanlı üye işlev yürütmesini senkronize eder. Ayrıca, nesnenin üye işlevlerinin yürütme dizilerini işbirliği içinde programlamasına izin verir.”

Duyuru








Rainer Grimm, uzun yıllardır yazılım mimarı, ekip lideri ve eğitim yöneticisi olarak çalışmaktadır. C++, Python ve Haskell programlama dilleri üzerine makaleler yazmaktan hoşlanır, aynı zamanda sık sık uzmanlık konferanslarında konuşmaktan da keyif alır. Modernes C++ blogunda yoğun bir şekilde C++ tutkusundan bahsediyor.













Bu nedenle, Nesne İzleme tasarım deseni, bir nesne içinde aynı anda yalnızca bir üye işlevin yürütülmesini sağlamak için üye işlevlerin eşzamanlı yürütülmesini senkronize eder. Ayrıca, bir nesnenin üye işlevlerinin yürütme dizilerini birlikte zamanlamasına olanak tanır.

Ayrıca şöyle bilinir

  • İş parçacığı güvenli pasif nesne
sorun


Birçok iş parçacığı paylaşılan bir nesneye aynı anda eriştiğinde, zorluklar aşağıdaki gibidir:

Duyuru

  • Eşzamanlı erişim nedeniyle, veri çakışmalarını önlemek için paylaşılan nesne eşitlenmemiş okuma ve yazma işlemlerinden korunmalıdır.
  • Gerekli senkronizasyon arayüzün değil, uygulamanın parçası olmalıdır.
  • Bir iş parçacığı paylaşılan nesneyle bittiğinde, bir sonraki iş parçacığının paylaşılan nesneyi kullanabilmesi için bir bildirim yapılmalıdır. Bu mekanizma, sistem performansını artırır.
  • Bir üye işlevi yürüttükten sonra, paylaşılan nesnenin değişmezleri korunmalıdır.
Çözüm

Bir istemci (iş parçacığı), Monitor nesnesinin senkronize edilmiş üye işlevlerine erişebilir ve monitör kilitleme nedeniyle, aynı anda yalnızca bir senkronize edilmiş üye işlevi yürütülebilir. Her izleme nesnesinin, bekleyen istemcileri bilgilendiren bir izleme durumu vardır.

bileşenler









  • nesneyi izle: Monitor nesnesi, bir veya daha fazla üye işlevi destekler. Her istemci, nesneye bu üye işlevler aracılığıyla erişmelidir ve her üye işlev, istemcinin iş parçacığında çalışır.
  • Senkronize üyelerin özellikleri: Eşitlenmiş üye işlevler, izleme nesnesi tarafından desteklenen üye işlevlerdir. Aynı anda yalnızca bir üye işlev yürütülebilir. İş parçacığı korumalı arabirim, arabirimi temsil eden üye işlevler (eşzamanlı üye işlevler) ile izleme nesnesinin uygulanmasını temsil eden üye işlevler arasında ayrım yapar.
  • monitör kilidi: Her monitör nesnesinin, herhangi bir zamanda en fazla bir istemcinin monitör nesnesine erişmesini sağlayan bir monitör kilidi vardır.
  • durumu izle: İzleme koşulu, ayrı iş parçacıklarının kendi çağrılarını İzleme nesnesindeki üye işlevlere planlamasına izin verir. Geçerli istemci eşitlenmiş üye işlevleri çağırmayı bitirdiğinde, bir sonraki bekleyen istemci, Monitor nesnesinin eşitlenmiş üye işlevlerini çağırmak için uyandırılır.
Monitör kilitleme, senkronize üye işlevlerine özel erişim sağlarken, monitör durumu minimum müşteri gecikmesi sağlar. Temel olarak, monitörün kilitlenmesi, hızlı veri akışına ve durum izleyicisini kilitlenmelere karşı korur.

dinamik davranış

İzleme Nesnesi ve bileşenleri arasındaki etkileşim birkaç aşamada gerçekleşir.

  1. Bir istemci, bir izleme nesnesinde eşitlenmiş bir üye işlevi çağırdığında, önce genel izleme kilidini kilitlemesi gerekir. Başarılı kilitlemeden sonra, senkronize üye işlevini yürütür ve sonunda monitörün kilidini açar. Engelleme başarısız olursa, istemci engellenir.
  2. İstemci engellenirse, izleme koşulu bir bildirim gönderene kadar bekler. Bu bildirim, monitör kilidi serbest bırakıldığında gerçekleşir. Bildirim şimdi bir veya tüm bekleyen istemcilere gönderilebilir. Beklemek genellikle, çılgınca beklemenin aksine kaynakları koruyan uyku anlamına gelir.
  3. Bir istemci çalışmaya devam etmek için bildirim aldığında, monitör kilidi uygular ve senkronize üye işlevini gerçekleştirir. Senkronize işlev sona erdikten sonra monitör kilidi tekrar serbest bırakılır. İzleme koşulu, bir sonraki istemcinin senkronize edilmiş üye işlevini yürütebileceğine dair bir bildirim gönderir.
Avantajlar ve dezavantajlar


Monitor nesnesinin artıları ve eksileri nelerdir?

Avantajlar

  • İstemci, Monitor nesnesinin zımni eşitlemesinden habersizdir ve eşitleme, uygulamada tamamen kapsüllenmiştir.
  • Son olarak, çağrılan senkronize edilmiş üye fonksiyonlar otomatik olarak planlanır. Monitör durum bildirimi/bakım mekanizması basit bir programlayıcı gibi davranır.
Dezavantajları

  • İşlevsellik ve senkronizasyon yakından ilişkili olduğundan, senkronize üye fonksiyonların senkronizasyon mekanizmasını değiştirmek genellikle oldukça zordur.
  • Eşitlenmiş bir üye işlev doğrudan veya dolaylı olarak aynı izleme nesnesini çağırırsa bir kilitlenme oluşabilir.
Örnek


Aşağıdaki örnek birini 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ıf içinde kapsüllenmiş olması ve dolayısıyla yeniden kullanılabilmesidir. Sınıf Monitor birini kullan std::mutex monitör bloğu olarak ve std::condition_variable olarak monitör koşulu olarak. Sınıf Monitor bir izleme nesnesinin desteklemesi gereken minimum arabirimi sağlar.

ThreadSafeQueue (1)’de genişletilmiştir. std::queue iş parçacığı güvenli bir arayüze sahip olmak. ThreadSafeQueue sınıftan geliyor Monitor ve üye işlevlerini senkronize etmek için üye işlevlerini kullanın add VE get desteklemek. Üyelerin işlevleri add VE get monitör nesnesini sabitlemek için monitör kilidini kullanın. Bu, özellikle iş parçacığı olmayan güvenli olanlar için geçerlidir. myQueue. add yeni bir öğe geldiğinde bekleyen ileti dizisini bilgilendir myQueue Eklendi. Bu bildirim iş parçacığı güvenlidir. üye işlevi get (3) daha fazla ilgiyi hak ediyor. Önce wait– Temel koşul değişkeninin çağrılan üye işlevi. Bu wait-call, kayıp ve sahte uyandırmalara karşı koruma sağlamak için ek bir yükleme ihtiyaç duyar (C++ Temel Yönergeleri: Koşul değişkenlerinin tehlikelerinin farkında olun). değiştirmek için yapılan işlemler myQueue (4) ve (5) de aramayı yönettikleri için korunmalıdır. myQueue.push(val) (6) örtüşebilir. monitör nesnesi safeQueue (7) senkronize edilmiş dosyadan bir sayı almak için (8) ve (9)’daki lambda fonksiyonlarını kullanın safeQueue Ekle veya sil. ThreadSafeQueue kendisi bir sınıf şablonudur ve herhangi bir türden değer alabilir. Yüz müşteri ekliyor safeQueue 1 ile 6 arasında 100 rasgele sayı (satır 7), yüz müşteri bu 100 sayıyı aynı anda safeQueue KALDIRILDI. Program çıktısı, iş parçacığı numaralarını ve kimliklerini gösterir.








C++20 ile program şunları yapabilir: monitorObject.cpp daha da geliştirilebilir. önce başlığı ekliyorum <concepts> ve kullanım konsepti std::predicate işlev şablonunda sınırlı bir şablon parametresi olarak wait (10). Kavram std::predicate modelin çalışmasını sağlar wait yalnızca bir yüklem ile somutlaştırılabilir. Tahminler, sonuç olarak bir boole değeri döndüren çağrılabilir öğelerdir.


template <std::predicate Predicate>
void wait(Predicate pred) const {
std::unique_lock<std::mutex> monitLock(monitMutex);
monitCond.wait(monitLock, pred);
}


İkincisi, yerine std::jthread kullanıyorum std::thread. std::jthread gelisti std::thread C++20’de, otomatik olarak yıkıcısında join gerekirse 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";

}


Etkin nesne ve izleme nesnesi benzerdir ancak bazı önemli açılardan farklılık gösterir. Her iki mimari model de ortak bir nesneye erişimi senkronize eder. Etkin bir nesnenin üye işlevleri farklı bir iş parçacığında yürütülür, ancak izleme nesnesinin üye işlevleri aynı iş parçacığında yürütülür. Etkin nesne, üye işlevlerin çağrılmasını üye işlevlerin yürütülmesinden daha iyi ayırır ve bu nedenle bakımı daha kolaydır.

Sıradaki ne?


Tamamlamak! Tasarım kalıpları üzerine yaklaşık 50 makale yazdım. Sonraki makalelerimde, C++17’deki oldukça bilinmeyen bir özellik hakkında yazacağım, C++20’yi derinlemesine inceleyeceğim ve yeni C++ standardı olan C++23’ü tanıtacağım. Bu yolculuğa C++23 ile başlıyorum.


(rm)



Haberin Sonu
 
Üst