1. Nasıl güvenli bir şekilde tanımlanır?
Semantik. Bu durumda, bu tanımlanmış bir terim değildir. Bu sadece "Risksiz bunu yapabilirsin" demek.
2. Bir program aynı anda güvenli bir şekilde yürütülebiliyorsa, bu her zaman yeniden girildiği anlamına mı gelir?
Hayır.
Örneğin, hem kilit hem de geri çağrıyı parametre olarak alan bir C ++ işlevine sahip olalım:
#include <mutex>
typedef void (*callback)();
std::mutex m;
void foo(callback f)
{
m.lock();
// use the resource protected by the mutex
if (f) {
f();
}
// use the resource protected by the mutex
m.unlock();
}
Başka bir fonksiyonun aynı muteksi kilitlemesi gerekebilir:
void bar()
{
foo(nullptr);
}
İlk bakışta her şey yolunda görünüyor ... Ama bekleyin:
int main()
{
foo(bar);
return 0;
}
Muteks üzerindeki kilit özyinelemeli değilse, ana iş parçacığında neler olacak:
main
arayacak foo
.
foo
kilidi alacak.
foo
arayacak bar
, arayacak foo
.
- 2
foo
, kilidi almak başarısız ve tahliye edilecek beklemek çalışacağız.
- Kilitlenme.
- Hata ...
Tamam, geri çağırmayı kullanarak hile yaptım. Ancak benzer bir etkiye sahip daha karmaşık kod parçalarını hayal etmek kolaydır.
3. Kodumu yeniden giriş yetenekleri için kontrol ederken akılda tutmam gereken altı nokta arasındaki ortak konu tam olarak nedir?
Sen edebilirsiniz kokusu sizin fonksiyon / değiştirilebilir bir kalıcı bir kaynağa erişim sağlar veya has / bir işleve erişim sağlar eğer bir sorun kokuyor .
( Tamam, kodumuzun% 99'u koklamalı, o zaman… Bunu yapmak için son bölüme bakın… )
Kodunuzu incelemek için, bu noktalardan biri sizi uyarmalıdır:
- Fonksiyonun bir durumu vardır (yani bir global değişkene, hatta bir sınıf üyesi değişkenine erişim)
- Bu işlev birden çok iş parçacığı tarafından çağrılabilir veya işlem yürütülürken yığında iki kez görünebilir (yani işlev kendisini doğrudan veya dolaylı olarak çağırabilir). Parametreler çok koktuğunda işlev geri çağrıları alır .
Tekrar girmeyenlerin viral olduğunu unutmayın: Olası yeniden girmeyen bir işlev çağırabilecek bir işlev, yeniden giriş olarak kabul edilemez.
Ayrıca, C ++ yöntemlerinin erişime sahip oldukları için koktuğunu unutmayın, bu this
nedenle komik bir etkileşime sahip olmadıklarından emin olmak için kodu incelemelisiniz.
4.1. Tüm özyinelemeli fonksiyonlar yeniden girer mi?
Hayır.
Çok iş parçacıklı durumlarda, aynı anda birden çok iş parçacığı tarafından paylaşılan bir kaynağa erişen özyinelemeli bir işlev çağrılabilir ve bu da verilerin bozuk / bozuk olmasına neden olabilir.
Tek kullanımlık durumlarda, özyinelemeli bir işlev, evresel olmayan bir işlev (rezil gibi strtok
) kullanabilir veya verilerin zaten kullanımda olduğu gerçeğini ele almadan küresel verileri kullanabilir. Bu nedenle işleviniz özyinelemeli çünkü doğrudan veya dolaylı olarak kendini çağırıyor, ancak yine de özyinelemeli-güvensiz olabilir .
4.2. Tüm iş parçacığı için güvenli işlevler yeniden girildi mi?
Yukarıdaki örnekte, görünüşe göre threadsafe fonksiyonunun nasıl yeniden girilmediğini gösterdim. Tamam, geri arama parametresi nedeniyle hile yaptım. Ancak, iki kez özyinelemeyen bir kilit elde etmesini sağlayarak bir iş parçacığını kilitlemenin birçok yolu vardır.
4.3. Tüm özyinelemeli ve iş parçacığı için güvenli işlevler yeniden gönderiliyor mu?
Eğer "özyinelemeli" ile "özyinelemeli-güvenli" demek istiyorsanız "evet" derdim.
Bir işlevin aynı anda birden çok iş parçacığı tarafından çağrılabileceğini ve kendisini doğrudan veya dolaylı olarak sorunsuz bir şekilde çağırabileceğini garanti ediyorsanız, o zaman yeniden girilir.
Sorun bu garantiyi değerlendiriyor… ^ _ ^
5. Tekrarlama ve iplik güvenliği gibi terimler mutlak midir, yani sabit somut tanımları var mı?
Yaptıklarına inanıyorum, ancak daha sonra bir işlevi değerlendirmek iş parçacığı açısından güvenli veya yeniden girme zor olabilir. Bu yüzden yukarıdaki koku terimini kullandım : Bir fonksiyonun reentrant olmadığını, ancak karmaşık bir kod parçasının reentrant olduğundan emin olmak zor olabilir
6. Bir örnek
Bir kaynağı kullanmanız gereken bir yöntemle bir nesneniz olduğunu varsayalım:
struct MyStruct
{
P * p;
void foo()
{
if (this->p == nullptr)
{
this->p = new P();
}
// lots of code, some using this->p
if (this->p != nullptr)
{
delete this->p;
this->p = nullptr;
}
}
};
İlk sorun, bu işlev bir şekilde özyinelemeli olarak adlandırılırsa (yani, bu işlev doğrudan veya dolaylı olarak çağrılırsa), kodun büyük olasılıkla çökmesine neden olur, çünkü this->p
son çağrının sonunda silinir ve yine de muhtemelen sonundan önce kullanılır İlk aramanın
Bu nedenle, bu kod özyinelemeli-güvenli değildir .
Bunu düzeltmek için bir referans sayacı kullanabiliriz:
struct MyStruct
{
size_t c;
P * p;
void foo()
{
if (c == 0)
{
this->p = new P();
}
++c;
// lots of code, some using this->p
--c;
if (c == 0)
{
delete this->p;
this->p = nullptr;
}
}
};
Bu şekilde, kod özyinelemeli güvenli olur ... Ama yine de, çünkü sorunlar çoklu kullanım evresel değildir: Biz olmalı emin modifikasyonları c
ve p
bir kullanarak, atomik olarak yapılacaktır özyinelemeli Muteksleri (tüm muteksler özyinelemelidir):
#include <mutex>
struct MyStruct
{
std::recursive_mutex m;
size_t c;
P * p;
void foo()
{
m.lock();
if (c == 0)
{
this->p = new P();
}
++c;
m.unlock();
// lots of code, some using this->p
m.lock();
--c;
if (c == 0)
{
delete this->p;
this->p = nullptr;
}
m.unlock();
}
};
Ve elbette, bu lots of code
, kullanımı da dahil olmak üzere kendisinin reentrant olduğunu varsayar p
.
Ve yukarıdaki kod uzaktan bile istisna-güvenli değil , ama bu başka bir hikaye… ^ _ ^
7. Hey kodumuzun% 99'u evresel değildir!
Spagetti kodu için oldukça doğrudur. Ancak kodunuzu doğru şekilde bölümlere ayırırsanız, yeniden giriş sorunlarından kaçınacaksınız.
7.1. Tüm işlevlerin HAYIR durumu olmadığından emin olun
Sadece parametreleri, kendi yerel değişkenlerini, durum bilgisi olmayan diğer işlevleri kullanmalı ve geri döndüklerinde verilerin kopyalarını döndürmelidirler.
7.2. Nesnenizin "özyinelemeli-güvenli" olduğundan emin olun
Bir nesne yönteminin erişimi vardır this
, bu nedenle bir nesneyi nesnenin aynı örneğinin tüm yöntemleriyle paylaşır.
Bu nedenle, nesnenin tüm nesneyi bozmadan yığındaki bir noktada (yani yöntem A'yı çağırmak) ve sonra başka bir noktada (yani yöntem B'yi çağırmak) kullanılabildiğinden emin olun. Nesnenizi bir yöntemden çıktıktan sonra nesnenin sabit ve doğru olduğundan emin olmak için tasarlayın (sarkan işaretçiler, çelişen üye değişkenler vb. Yok).
7.3. Tüm nesnelerinizin doğru şekilde kapsüllendiğinden emin olun
Başka hiç kimse dahili verilerine erişemez:
// bad
int & MyObject::getCounter()
{
return this->counter;
}
// good
int MyObject::getCounter()
{
return this->counter;
}
// good, too
void MyObject::getCounter(int & p_counter)
{
p_counter = this->counter;
}
Kodun başka bir kısmı, sabit referansı tutan kod söylenmeden değiştirilebileceğinden, kullanıcı verilerinin adresini alırsa bir const referansı döndürmek bile tehlikeli olabilir.
7.4. Kullanıcının nesnenizin iş parçacığı için güvenli olmadığını bildiğinden emin olun
Bu nedenle, kullanıcı iş parçacıkları arasında paylaşılan bir nesneyi kullanmak için muteksleri kullanmaktan sorumludur.
STL'den gelen nesneler, iş parçacığı açısından güvenli olmayacak şekilde tasarlanmıştır (performans sorunları nedeniyle) ve bu nedenle, kullanıcı std::string
iki iş parçacığı arasında bir paylaşım yapmak istiyorsa , kullanıcının erişimini eşzamanlılık ilkelleriyle koruması gerekir;
7.5. İş parçacığı güvenli kodunuzun yinelemeli olarak güvenli olduğundan emin olun
Bu, aynı kaynağın aynı iş parçacığı tarafından iki kez kullanılabileceğine inanıyorsanız, özyinelemeli mutekslerin kullanılması anlamına gelir.