Yazılım mimarisinde kalıp: aktif nesne

Adanali

Active member
Yazılım mimarisinde kalıp: aktif nesne


  1. Yazılım mimarisinde kalıp: aktif nesne

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. Bir Wikipedia girişi, eşzamanlılık modellerine ait aktif nesneyi şu şekilde tanımlar: “Aktif nesne tasarım deseni, her biri kendi kontrol iş parçacığında bulunan nesneler için yöntem çalıştırmayı yöntem çağrısından ayırır. Amaç, eşzamansız yöntem çağırma ve istekleri işlemek için bir zamanlayıcı kullanarak eşzamanlılığı tanıtmaktır..”








Etkin nesne, yöntem çağrısını yöntem yürütmesinden ayırır. Yöntem çağrısı, istemci iş parçacığında gerçekleştirilir, ancak yöntem yürütme, etkin nesnede gerçekleştirilir. Etkin nesnenin kendi iş parçacığı ve yürütülecek yöntem istek nesnelerinin (kısa istekler) bir listesi vardır. İstemci yöntemini çağırmak, istekleri etkin nesne listesinde sıralar. İstekler sunucuya iletilir.







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.







Yarışma modelleri


Aktif nesne aynı zamanda bir eşzamanlılık nesnesi olarak da bilinir.

sorun


Birçok iş parçacığı ortak bir nesneye senkronize bir şekilde eriştiğinde, aşağıdaki sorunların ele alınması gerekir:


  • İşlem açısından yoğun bir üye işlevi çağıran bir iş parçacığı, aynı nesneyi çok uzun süre çağıran diğer iş parçacıklarını engellememelidir.
  • Paylaşılan bir nesneye erişimin eşitlenmesi kolay olmalıdır.
  • Yürütülen sorguların eşzamanlılık özellikleri, belirli donanım ve yazılıma uyarlanabilir olmalıdır.
Çözüm

  • İstemci yöntemi çağrısı, etkin nesnenin arabirimini temsil eden bir proxy’ye gider.
  • Sunucu, bu üye işlevleri uygular ve etkin nesnenin iş parçacığında çalışır. Çalışma zamanında proxy, çağrıyı sunucuya yönelik bir yöntem çağrısına dönüştürür. Zamanlayıcı bu isteği bir aktivasyon listesine koyar.
  • Zamanlayıcı olay döngüsü, sunucuyla aynı iş parçacığında çalışır, tetikleme listesinden istekleri toplar, kaldırır ve sunucuya gönderir.
  • İstemci, yöntem çağrısının sonucunu proxy’den bir gelecek yoluyla alır.
bileşenler


Aktif nesne modeli altı bileşenden oluşur:

  • ONLAR yardımcısı (proxy), etkin nesnenin erişilebilir üye işlevlerine bir arabirim sağlar. Yedek, aktivasyon listesinde bir istek oluşturulmasını başlatır. Proxy, istemci iş parçacığında çalışır.
  • ONLAR yöntem istek sınıfı (request method), etkin bir nesne üzerinde çalışan yöntemin arayüzünü tanımlar. Bu arabirim aynı zamanda işin çalışmaya hazır olup olmadığını gösteren koruma yöntemleri içerir. İstek, daha sonra kullanılacak tüm bağlam bilgilerini içerir.
  • ONLAR aktivasyon listesi (etkinleştirme listesi) bekleyen istekleri yönetir. Aktivasyon listesi, istemci iş parçacığını etkin nesne iş parçacığından ayırır. Proxy, istek nesnesini ekler ve zamanlayıcı onu tekrar kaldırır. Bu nedenle, aktivasyon listesine erişim seri hale getirilmelidir.
  • ONLAR planlamacı aktif nesnenin iş parçacığında çalışır ve aktivasyon listesinden hangi talebin daha sonra yürütüleceğine karar verir. Zamanlayıcı, isteğin yürütülüp yürütülemeyeceğini belirlemek için istek koruyucularını değerlendirir.
  • ONLAR sunucu aktif nesneyi uygular ve aktif nesnenin iş parçacığında çalışır. Sunucu, yöntem istek arabirimini uygular ve zamanlayıcı, üye işlevlerini çağırır.
  • Milletvekili oluşturur Vadeli işlemler. Bu, yalnızca sorgu bir sonuç döndürürse gereklidir. İstemci daha sonra geleceği alır ve etkin nesnedeki yöntem çağrısının sonucunu alabilir. İstemci sonucu bekleyebilir veya sorgulayabilir.
dinamik davranış


Etkin nesnenin dinamik davranışı üç aşamadan oluşur:

  1. İsteklerin oluşturulması ve programlanması: İstemci bir temsilci yöntemi çağırır. Proxy bir istek oluşturur ve bunu planlayıcıya iletir. Zamanlayıcı, isteği aktivasyon listesinde kuyruğa alır. Ayrıca, sorgu bir sonuç döndürürse, proxy istemciye bir gelecek döndürür.
  2. Üye işlevi yürütme: Zamanlayıcı, isteğin koruma yöntemini değerlendirerek hangi isteğin yürütülebilir olduğunu belirler. İsteği aktivasyon listesinden kaldırır ve sunucuya gönderir.
  3. tamamlama: Sorgu bir sonuç döndürürse, gelecekte saklanır. Müşteri sonucu sorabilir. İstemci sonucu aldığında, istek ve gelecek iptal edilebilir.
Aşağıdaki şekil mesajların sırasını göstermektedir.








Avantajlar ve dezavantajlar


Etkin nesnenin minimal bir örneğini sunmadan önce, artılarının ve eksilerinin kısa bir listesi:

Avantajlar

  • Eşitleme yalnızca etkin nesne iş parçacığında gereklidir, istemci iş parçacıklarında değil.
  • İstemci (kullanıcı) ve sunucu (uygulayıcı) arasındaki ayrımı netleştirin. Senkronizasyonun zorlukları uygulayıcı tarafındadır.
  • İstekleri eşzamansız olarak yürüterek artan sistem verimi. Hesaplama açısından yoğun üye işlevlerin çağrılması tüm sistemi engellemez.
  • Zamanlayıcı, bekleyen istekleri yürütmek için farklı stratejiler kullanabilir. Bu durumda, işler kuyrukta olduğundan farklı bir sırada çalışabilir.
Dezavantajları

  • Sorgular çok ayrıntılıysa proxy, aktivasyon listesi ve zamanlayıcı gibi Etkin Nesne Kalıbının performansı aşırı olabilir.
  • Zamanlayıcı stratejisi ve işletim sistemi planlaması nedeniyle, etkin nesnede sorun giderme genellikle zordur. Bu, özellikle emrin yerine getirilme sırası, emrin yaratılmasından farklı olduğunda doğrudur.
Örnek


Aşağıdaki örnek, etkin nesnenin basitleştirilmiş bir uygulamasını gösterir. Özellikle, proxy ve sunucunun uygulaması gereken aktif nesneye yönelik yöntem istekleri için bir arayüz tanımlamıyorum. Ayrıca, programlayıcı gerektiğinde bir sonraki işi çalıştırır ve etkin nesnenin çalıştırma üye işlevi iş parçacıklarını oluşturur.

İlgili veri türleri future<vector<future<pair<bool, int>>>> genellikle oldukça uzundurlar. Okunabilirlik için, büyük ölçüde bildirimlerin kullanımına güvendim (satır 1). Bu örnek, C++’daki vaatlerin ve geleceklerin sağlam bir şekilde anlaşıldığını varsayar. Daha fazla ayrıntı için etkinlik makalelerime bakın.


// activeObject.cpp

#include <algorithm>
#include <deque>
#include <functional>
#include <future>
#include <iostream>
#include <memory>
#include <mutex>
#include <numeric>
#include <random>
#include <thread>
#include <utility>
#include <vector>

using std::async; // (1)
using std::boolalpha;
using std::cout;
using std::deque;
using std::distance;
using std::for_each;
using std::find_if;
using std::future;
using std::lock_guard;
using std::make_move_iterator;
using std::make_pair;
using std::move;
using std::mt19937;
using std::mutex;
using std::packaged_task;
using std::pair;
using std::random_device;
using std::sort;
using std::jthread;
using std::uniform_int_distribution;
using std::vector;

class IsPrime { // (8)
public:
IsPrime(int num): numb{num} {}
pair<bool, int> operator()() {
for (int j = 2; j * j <= numb; ++j){
if (numb % j == 0) return make_pair(false, numb);
}
return make_pair(true, numb);
}
private:
int numb;
};

class ActiveObject {
public:

future<pair<bool, int>> enqueueTask(int i) {
IsPrime isPrime(i);
packaged_task<pair<bool, int>()> newJob(isPrime);
auto isPrimeFuture = newJob.get_future();
{
lock_guard<mutex> lockGuard(activationListMutex);
activationList.push_back(move(newJob)); // (6)
}
return isPrimeFuture;
}

void run() {
std::jthread j([this] { // (12)
while ( !runNextTask() ); // (13)
});
}

private:

bool runNextTask() { // (14)
lock_guard<mutex> lockGuard(activationListMutex);
auto empty = activationList.empty();
if (!empty) { // (15)
auto myTask= std::move(activationList.front());
activationList.pop_front();
myTask();
}
return empty;
}

deque<packaged_task<pair<bool, int>()>> activationList; //(7)
mutex activationListMutex;
};

vector<int> getRandNumbers(int number) {
random_device seed;
mt19937 engine(seed());
uniform_int_distribution<> dist(1'000'000, 1'000'000'000); // (4)
vector<int> numbers;
for (long long i = 0 ; i < number; ++i) numbers.push_back(dist(engine));
return numbers;
}

future<vector<future<pair<bool, int>>>> getFutures(ActiveObject& activeObject,
int numberPrimes) {
return async([&activeObject, numberPrimes] {
vector<future<pair<bool, int>>> futures;
auto randNumbers = getRandNumbers(numberPrimes); // (3)
for (auto numb: randNumbers){
futures.push_back(activeObject.enqueueTask(numb)); // (5)
}
return futures;
});
}


int main() {

cout << boolalpha << 'n';

ActiveObject activeObject;

// a few clients enqueue work concurrently // (2)
auto client1 = getFutures(activeObject, 1998);
auto client2 = getFutures(activeObject, 2003);
auto client3 = getFutures(activeObject, 2011);
auto client4 = getFutures(activeObject, 2014);
auto client5 = getFutures(activeObject, 2017);

// give me the futures // (9)
auto futures = client1.get();
auto futures2 = client2.get();
auto futures3 = client3.get();
auto futures4 = client4.get();
auto futures5 = client5.get();

// put all futures together // (10)
futures.insert(futures.end(),make_move_iterator(futures2.begin()),
make_move_iterator(futures2.end()));

futures.insert(futures.end(),make_move_iterator(futures3.begin()),
make_move_iterator(futures3.end()));

futures.insert(futures.end(),make_move_iterator(futures4.begin()),
make_move_iterator(futures4.end()));

futures.insert(futures.end(),make_move_iterator(futures5.begin()),
make_move_iterator(futures5.end()));

// run the promises // (11)
activeObject.run();

// get the results from the futures
vector<pair<bool, int>> futResults;
futResults.reserve(futures.size());
for (auto& fut: futures) futResults.push_back(fut.get()); // (16)

sort(futResults.begin(), futResults.end()); // (17)

// separate the primes from the non-primes
auto prIt = find_if(futResults.begin(), futResults.end(),
[](pair<bool, int> pa){ return pa.first == true; });

cout << "Number primes: " << distance(prIt, futResults.end()) << 'n'; // (19)
cout << "Primes:" << 'n';
for_each(prIt, futResults.end(), [](auto p){ cout << p.second << " ";} ); // (20)

cout << "nn";

cout << "Number no primes: " << distance(futResults.begin(), prIt) << 'n'; // (18)
cout << "No primes:" << 'n';
for_each(futResults.begin(), prIt, [](auto p){ cout << p.second << " ";} );

cout << 'n';

}


Örneğin temel fikri, müşterilerin aynı anda aktivasyon listesindeki işleri kuyruğa alabilmesidir. Sunucu hangi sayıların asal olduğunu belirler ve aktivasyon listesi aktif nesnenin bir parçasıdır. Aktif nesne, aktivasyon listesindeki işleri ayrı bir iş parçacığında çalıştırır ve istemciler sonuçları sorgulayabilir.

Detaylar burada


Beş müşteri işi (2. satır) işlevin üstüne koydu getFutures kuyruğunda activeObjects. getFutures al bunu activeObject ve bir numara numberPrimes aksine. numberPrimes arasında rasgele sayılar (satır 3) oluştur 1'000'000 VE 1'000'000'000 (satır 4) ve onu dönüş değerine iter: vector<future<pair<bool, int>>. future<pair<bool, int> içerir bool bu bir int. ONLAR bool sayının asal sayı olup olmadığını gösterir. Satır (5)’e daha yakından bakalım: futures.push_back(activeObject.enqueueTask(numb)). Bu çağrı, aktivasyon listesine (satır 6) yerleştirilecek yeni bir aktiviteyi aktive eder. Aktivasyon listesindeki tüm çağrılar korunmalıdır. Aktivasyon listesi bir deque Sözlerden (satır 7): deque<packaged_task<pair<bool, int>()>>. Her söz, çağrıldığında işlev nesnesini taşır IsPrime (satır 8) devre dışı bırakıldı. Dönen değer bir çift birdir bool biridir int. ONLAR bool sayı olup olmadığını gösterir int bir asal sayıdır.

Artık iş paketleri hazırlandı, hesaplamaya başlanabilir. Tüm müşteriler, tutamaçlarını (9) satırındaki ilişkili vadeli işlemlere geri gönderir. Tüm gelecekleri özetlemek (satır 10) işi kolaylaştırır. Arama activeObject.run() (11) satırında yürütme başlar. üye işlevi run (satır 12) üye işlevini kullanmak için iş parçacığı oluşturun runNextTask (satır 13) yürütmek için. runNextTask (satır 14) deque boş değil (satır 15) ve yeni aktiviteyi oluşturun. Telefonda futResults.push_back(fut.get()) (satır 16) her gelecek için tüm sonuçlar istenir ve devam edilir futResults itti. Satır (17) çift vektörü sıralar: vector<pair<bool, int>>. Hesaplama kalan satırlarda gösterilir. yineleyici prIt (18) satırında, asal sayıya sahip bir çiftin ilk yineleyicisini içerir.

Aşağıdaki ekran görüntüsü asal sayısını göstermektedir distance(prIt, futResults.end()) (satır 19) ve asal sayılar (satır 20). Yalnızca ilk asal olmayan sayılar görüntülenir:








Sıradaki ne?


Etkin nesne ve izleme nesnesi, üye işlevlerin çağrılmasını senkronize eder ve planlar. Ana fark, aktif nesnenin üye işlevini farklı bir iş parçacığında yürütmesi, izleme nesnesinin ise istemci ile aynı iş parçacığında olmasıdır. Bir sonraki yazımda Monitor nesnesini daha detaylı tanıtacağım.


(harita)



Haberin Sonu
 
Üst