Yazılım geliştirme – değişiklik yönetimi: Kilitleme

Adanali

Active member
Yazılım geliştirme – değişiklik yönetimi: Kilitleme


  1. Yazılım geliştirme – değişiklik yönetimi: Kilitleme

Kalıplar, modern yazılım geliştirmede önemli bir soyutlamadır. İyi tanımlanmış terminoloji, açık belgeler sunar ve en iyisinden öğrenirler. Bu gönderi, eşzamanlılık modellerine daha da giriyor. Kilitleme, paylaşılan ve değişen durumu korumanın klasik bir yoludur. Bugün size iki çeşidi sunuyorum: Kapsamlı Kilitleme ve Stratejik Kilitleme.







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.













Kritik bir bölümü korumak için kilitlemeyle ilgili basit bir fikir var. Kritik bölüm, bir iş parçacığının özel olarak kullanması gereken bir kod parçasıdır.

hedeflenen blok


Kapsamlı kilit, bir mutekse uygulanan RAII kavramıdır. Kapsamlı kilitleme, senkronize kilitleme ve koruma olarak da bilinir. Bu dilin özü, kaynakların elde edilmesini ve serbest bırakılmasını bir nesnenin yaşamına bağlamaktır. Adından da anlaşılacağı gibi, nesnenin ömrü kapsamı vardır. Kapsamlı, C++ çalışma zamanının nesneyi yok etmekten ve ardından kaynağı serbest bırakmaktan sorumlu olduğu anlamına gelir.

Sınıf ScopedLock Kapsamlı kilitleme uygular.


// scopedLock.cpp

#include <iostream>
#include <mutex>
#include <new>
#include <string>

class ScopedLock{
private:
std::mutex& mut;
public:
explicit ScopedLock(std::mutex& m): mut(m){ // (1)
mut.lock(); // (2)
std::cout << "Lock the mutex: " << &mut << 'n';
}
~ScopedLock(){
std::cout << "Release the mutex: " << &mut << 'n';
mut.unlock(); // (3)
}
};

int main(){

std::cout << 'n';

std::mutex mutex1;
ScopedLock scopedLock1{mutex1};

std::cout << "nBefore local scope" << 'n';
{
std::mutex mutex2;
ScopedLock scopedLock2{mutex2};
} // (4)
std::cout << "After local scope" << 'n';

std::cout << "nBefore try-catch block" << 'n';
try{
std::mutex mutex3;
ScopedLock scopedLock3{mutex3};
throw std::bad_alloc();
} // (5)
catch (std::bad_alloc& e){
std::cout << e.what();
}
std::cout << "nAfter try-catch block" << 'n';

std::cout << 'n';

}


ScopedLock muteksini başvuru (1) ile alır. Mutex yapıcıda (2) kilitlenir ve yıkıcıda (3) kilidi açılır. RAII deyimi sayesinde nesne yok edilir ve muteks otomatik olarak serbest bırakılır.


dökülmesi scopedLock1 sonunda biter main-İşlev. buna göre olacak mutex1 kilidi açıldı. aynı değer mutex2 VE mutex3. Yerel kapsamlarının sonunda (4 ve 5) otomatik olarak serbest bırakılırlar. İLE mutex3 aynı zamanda yok edici olur scopedLock3 bir istisna oluştuğunda çağrılır. İlginç olan bu mutex3 hatırası mutex2 yeniden kullanıldı çünkü her ikisi de aynı adrese sahip.








Kapsamlı blok aşağıdaki avantajlara ve dezavantajlara sahiptir:

Avantajlar:

  • Kilit otomatik olarak alınıp serbest bırakıldığı için sağlamlık.
Dezavantajları:

  • özyinelemeli kıvrılma bir std::mutex tanımsız bir davranıştır ve genellikle kilitlenmeye yol açar.
  • longjmp C işlevi kullanılırken kilitler otomatik olarak açılmaz; longjpm, kapsamlı nesne C++ yıkıcılarını çağırmaz.
C++17, kilitleri dört türde destekler. C++ bir tane var std::lock_guard / std::scoped_lock basit ea için std::unique_lock / std::shared_lock muteksin açıkça kilitlenmesi veya kilidinin açılması gibi gelişmiş kullanım durumları için. “Kilitler” makalemde muteksler ve kilitler hakkında daha fazla bilgi edinebilirsiniz.

Stratejik blok, kapsamlı bloğu kullanmayı sever.

stratejik kapanış


Bir kitaplık gibi kodun aynı anda farklı alanlarda da kullanılacağını varsayalım. Güvenli tarafta olmak için kritik bölümleri bir asma kilitle emniyete alın. Kitaplık artık tek iş parçacıklı bir ortamda çalışıyorsa, gerekli olmayan pahalı bir eşitleme mekanizması kullanıldığından performans sorunu vardır. Strateji engellemenin devreye girdiği yer burasıdır: strateji modelini engellemeye uygulamak. Bu, engelleme stratejinizi bir nesneye sardığınız ve onu sisteminizin bir bileşeni yaptığınız anlamına gelir.

Stratejik kilitlemeyi uygulamaya yönelik iki tipik yöntem, çalışma zamanı polimorfizmi (nesne oryantasyonu) veya derleme zamanı polimorfizmidir (şablonlar). Her iki mod da kapatma stratejisinin özelleştirilmesini ve genişletilmesini geliştirir, sistem bakımını basitleştirir ve bileşenlerin yeniden kullanımını destekler. Çalıştırma zamanı veya derleme zamanı strateji bloğunun uygulanması birkaç açıdan farklılık gösterir.

Avantajlar:

Çalışma zamanı polimorfizmi

  • çalışma zamanı engelleme stratejisini yapılandırmanıza izin verir e
  • nesne yönelimli bir geçmişe sahip geliştiricilerin anlaması daha kolaydır.
Derleme zamanı polimorfizmi

  • soyutlama dezavantajı yoktur e
  • düz bir hiyerarşiye sahiptir.
Dezavantajları:

Çalışma zamanı polimorfizmi

  • ek dolaylı işaretçi e gerektirir
  • derin bir türetme hiyerarşisine sahip olabilir.
Derleme zamanı polimorfizmi

  • başarısızlık durumunda uzun hata mesajları üretebilir (bu, aşağıdaki gibi kavramlarla değişir) BasicLockable C++20’de).
Bu teorik tartışmadan sonra, her iki varyantta da Stratejik Kilitleme uygulayacağım. Benim örneğimde, stratejik blok hiçbiri, özel ve paylaşılan bloğu destekler. Basit olması için önceden var olan muteksleri kullandım.

Çalışma zamanı polimorfizmi


program strategizedLockingRuntime.cpp üç farklı kilitleme stratejisi kullanır.


// strategizedLockingRuntime.cpp

#include <iostream>
#include <mutex>
#include <shared_mutex>

class Lock { // (4)
public:
virtual void lock() const = 0;
virtual void unlock() const = 0;
};

class StrategizedLocking {
Lock& lock; // (1)
public:
StrategizedLocking(Lock& l): lock(l){ // (2)
lock.lock();
}
~StrategizedLocking(){ // (3)
lock.unlock();
}
};

struct NullObjectMutex{
void lock(){}
void unlock(){}
};

class NoLock : public Lock { // (5)
void lock() const override {
std::cout << "NoLock:🔒 " << 'n';
nullObjectMutex.lock();
}
void unlock() const override {
std::cout << "NoLock:🔓 " << 'n';
nullObjectMutex.unlock();
}
mutable NullObjectMutex nullObjectMutex; // (10)
};

class ExclusiveLock : public Lock { // (6)
void lock() const override {
std::cout << " ExclusiveLock:🔒 " << 'n';
mutex.lock();
}
void unlock() const override {
std::cout << " ExclusiveLock:🔓 " << 'n';
mutex.unlock();
}
mutable std::mutex mutex; // (11)
};

class SharedLock : public Lock { // (7)
void lock() const override {
std::cout << " SharedLock::lock_shared: " << 'n';
sharedMutex.lock_shared(); // (8)
}
void unlock() const override {
std::cout << " SharedLock::unlock_shared: " << 'n';
sharedMutex.unlock_shared(); // (9)
}
mutable std::shared_mutex sharedMutex; // (12)
};

int main() {

std::cout << 'n';

NoLock noLock;
StrategizedLocking stratLock1{noLock};

{
ExclusiveLock exLock;
StrategizedLocking stratLock2{exLock};
{
SharedLock sharLock;
StrategizedLocking startLock3{sharLock};
}
}

std::cout << 'n';

}


Sınıf StrategizedLocking bir asma kilide (1) sahiptir. StrategizedLocking kapsamlı kilitlemeyi modeller ve ardından yapıcıdaki (2) muteksi kilitler ve yıkıcıda (3) tekrar serbest bırakır. Lock (4) soyut bir sınıftır ve türetilmiş sınıfların arayüzünü tanımlar. Bunlar NoLock(5) sınıflarıdır, ExclusiveLock (6) ve SharedLock (7). SharedLock aramalar lock_shared (8) ve unlock_shared (9) onun üzerinde std::shared_mutex AÇIK. Bu blokların her biri mutekslerden birini içerir. NullObjectMutex (10), std::mutex (11) veya std::shared_mutex (satır 12). NullObjectMutex noop yer tutucudur. Mutex’ler şu şekilde saklanır: mutable beyan. Yani onlar gibi sabit üye fonksiyonlarındalar. lock VE unlock kullanılabilir

Derleme zamanı polimorfizmi


Şablon tabanlı uygulama, nesne yönelimli uygulamaya çok benzer. Soyut bir temel sınıf yerine Lock kavramı ben tanımlarım BasicLockable. Kavramlar hakkında daha fazla bilgiyi bir önceki makalemde bulabilirsiniz: Kavramlar.


template <typename T>
concept BasicLockable = requires(T lo) {
lo.lock();
lo.unlock();
};


BasicLockable type parametresi tarafından gerekli T, üye işlevlerine sahip olan lock VE unlock uygulandı. Sonuç olarak, sınıf modeli kabul eder StrategizedLocking yalnızca bu kısıtlamayı karşılayan parametreleri yazın.


template <BasicLockable Lock>
class StrategizedLocking {
...


Son olarak, model tabanlı uygulama izler.


// strategizedLockingCompileTime.cpp

#include <iostream>
#include <mutex>
#include <shared_mutex>

template <typename T>
concept BasicLockable = requires(T lo) {
lo.lock();
lo.unlock();
};

template <BasicLockable Lock>
class StrategizedLocking {
Lock& lock;
public:
StrategizedLocking(Lock& l): lock(l){
lock.lock();
}
~StrategizedLocking(){
lock.unlock();
}
};

struct NullObjectMutex {
void lock(){}
void unlock(){}
};

class NoLock{
public:
void lock() const {
std::cout << "NoLock:🔒 " << 'n';
nullObjectMutex.lock();
}
void unlock() const {
std::cout << "NoLock:🔓 " << 'n';
nullObjectMutex.lock();
}
mutable NullObjectMutex nullObjectMutex;
};

class ExclusiveLock {
public:
void lock() const {
std::cout << " ExclusiveLock:🔒 " << 'n';
mutex.lock();
}
void unlock() const {
std::cout << " ExclusiveLock:🔓 " << 'n';
mutex.unlock();
}
mutable std::mutex mutex;
};

class SharedLock {
public:
void lock() const {
std::cout << " SharedLock::lock_shared: " << 'n';
sharedMutex.lock_shared();
}
void unlock() const {
std::cout << " SharedLock::unlock_shared: " << 'n';
sharedMutex.unlock_shared();
}
mutable std::shared_mutex sharedMutex;
};

int main() {

std::cout << 'n';

NoLock noLock;
StrategizedLocking<NoLock> stratLock1{noLock};

{
ExclusiveLock exLock;
StrategizedLocking<ExclusiveLock> stratLock2{exLock};
{
SharedLock sharLock;
StrategizedLocking<SharedLock> startLock3{sharLock};
}
}

std::cout << 'n';

}


Programlar strategizedLockingRuntime.cpp VE strategizedLockingCompileTime.cpp aynı çıktıyı üret:








Sıradaki ne?


Guarded Suspension, değişimle başa çıkmak için farklı bir strateji kullanır. Değişiklik gerçekleştiğinde rapor verin. Bir sonraki makalemde denetimli uzaklaştırma hakkında daha fazla ayrıntıya gireceğim.


(rm)



Haberin Sonu
 
Üst