Geçenlerde, "malloc iş parçacığı güvenli mi?" Başlıklı bir soru sordum. ve bunun içinde "malloc yeniden mi giriyor?" diye sordum.
Tüm yeniden girenlerin iş parçacığı için güvenli olduğu izlenimindeydim.
Bu varsayım yanlış mı?
Geçenlerde, "malloc iş parçacığı güvenli mi?" Başlıklı bir soru sordum. ve bunun içinde "malloc yeniden mi giriyor?" diye sordum.
Tüm yeniden girenlerin iş parçacığı için güvenli olduğu izlenimindeydim.
Bu varsayım yanlış mı?
Yanıtlar:
Yeniden giriş fonksiyonları, C kütüphanesi başlıklarında gösterilen global değişkenlere dayanmaz .. strtok () - strtok_r (), örneğin C.
Bazı işlevler 'devam eden çalışmayı' depolamak için bir yere ihtiyaç duyar, yeniden giriş işlevleri bu işaretçiyi iş parçacığının kendi deposunda tanımlamanıza izin verir, genel olarak değil. Bu depolama, arama işlevine özel olduğundan, kesintiye uğrayabilir ve yeniden girilebilir (yeniden giriş) ve çoğu durumda işlevin bunun çalışması için uyguladığının ötesinde karşılıklı dışlama gerekmediğinden, bunlar genellikle güvenli iş parçacığı . Ancak bu, tanım gereği garanti edilmez.
Errno, POSIX sistemlerinde biraz farklı bir durumdur (ve tüm bunların nasıl çalıştığına dair herhangi bir açıklamada tuhaf olma eğilimindedir) :)
Kısacası, evresel, genellikle iş parçacığı güvenli anlamına gelir ("iş parçacığı kullanıyorsanız bu işlevin evresel sürümünü kullanın" gibi), ancak iş parçacığı güvenliği her zaman yeniden giren (veya tersi) anlamına gelmez. İş parçacığı güvenliğine bakarken, eşzamanlılık düşünmeniz gereken şeydir. Bir işlevi kullanmak için bir kilitleme ve karşılıklı dışlama aracı sağlamanız gerekiyorsa, işlev doğası gereği iş parçacığı açısından güvenli değildir.
Ancak, tüm işlevlerin her ikisi için de incelenmesi gerekmez. malloc()
evresel olmaya gerek yoktur, herhangi bir iş parçacığı için giriş noktasının kapsamı dışındaki herhangi bir şeye bağlı değildir (ve kendisi iş parçacığı güvenlidir).
Statik olarak ayrılmış değerleri döndüren işlevler , bir muteks, futex veya başka atomik kilitleme mekanizması kullanılmadan iş parçacığı açısından güvenli değildir . Yine de, kesintiye uğramayacaklarsa tekrar giriş yapmaları gerekmez.
yani:
static char *foo(unsigned int flags)
{
static char ret[2] = { 0 };
if (flags & FOO_BAR)
ret[0] = 'c';
else if (flags & BAR_FOO)
ret[0] = 'd';
else
ret[0] = 'e';
ret[1] = 'A';
return ret;
}
Gördüğünüz gibi, birden fazla iş parçacığına sahip olmak, bir çeşit kilitleme olmadan bunu kullanmak bir felaket olur .. ama yeniden girme amacı yoktur. Bazı gömülü platformlarda dinamik olarak ayrılmış bellek tabu olduğunda bununla karşılaşacaksınız.
Tamamen işlevsel Programlamada, genellikle için evresel gelmez parçacığı güvenli ima, bu fonksiyon giriş noktasına geçirilen tanımlanmış veya anonim işlevlerin, özyinelemenin vb davranışına bağlı olacağını
Eşzamanlı erişim için 'iş parçacığı güvenli' koymanın daha iyi bir yolu güvenlidir , bu da ihtiyacı daha iyi gösterir.
TL; DR: Bir işlev evresel, iş parçacığı güvenli olabilir veya ikisi birden olabilir.
İplik güvenliği ve yeniden giriş için Wikipedia makaleleri okumaya değer. İşte birkaç alıntı:
Bir işlev iş parçacığı açısından güvenlidir :
yalnızca paylaşılan veri yapılarını, aynı anda birden çok iş parçacığı tarafından güvenli yürütmeyi garanti edecek şekilde işler.
Bir işlev evreseldir :
yürütme sırasında herhangi bir noktada kesilebilir ve daha önceki çağrılarının yürütülmesi tamamlanmadan önce güvenli bir şekilde yeniden çağrılabilir ("yeniden girilmiş").
Olası yeniden giriş örnekleri olarak Wikipedia, sistem kesintileri tarafından çağrılmak üzere tasarlanmış bir işlev örneğini verir: başka bir kesinti olduğunda zaten çalışmakta olduğunu varsayalım. Ancak, sırf sistem kesintileriyle kod yazmadığınız için güvende olduğunuzu düşünmeyin: Geri çağırma veya özyinelemeli işlevler kullanırsanız, tek iş parçacıklı bir programda yeniden giriş sorunları yaşayabilirsiniz.
Karışıklıktan kaçınmanın anahtarı, evrenin yalnızca bir iş parçacığının yürütülmesini ifade etmesidir. Çok görevli işletim sistemlerinin bulunmadığı zamandan kalma bir kavramdır.
Örnekler
(Wikipedia makalelerinden biraz değiştirilmiştir)
Örnek 1: iş parçacığı güvenli değil, evresel değil
/* As this function uses a non-const global variable without
any precaution, it is neither reentrant nor thread-safe. */
int t;
void swap(int *x, int *y)
{
t = *x;
*x = *y;
*y = t;
}
Örnek 2: iş parçacığı güvenli, evresel değil
/* We use a thread local variable: the function is now
thread-safe but still not reentrant (within the
same thread). */
__thread int t;
void swap(int *x, int *y)
{
t = *x;
*x = *y;
*y = t;
}
Örnek 3: iş parçacığı güvenli değil, evresel
/* We save the global state in a local variable and we restore
it at the end of the function. The function is now reentrant
but it is not thread safe. */
int t;
void swap(int *x, int *y)
{
int s;
s = t;
t = *x;
*x = *y;
*y = t;
t = s;
}
Örnek 4: iş parçacığı güvenli, evresel
/* We use a local variable: the function is now
thread-safe and reentrant, we have ascended to
higher plane of existence. */
void swap(int *x, int *y)
{
int t;
t = *x;
*x = *y;
*y = t;
}
t = *x
, çağrılar swap()
, daha sonra t
beklenmedik sonuçlara yol açan, geçersiz kılınan olacaktır.
swap(5, 6)
bir swap(1, 2)
. Sonra t=*x
, s=t_original
ve t=5
. Şimdi, kesintiden sonra s=5
ve t=1
. Ancak, ikinci swap
dönüşlerden önce, içeriği geri yükleyecek t=s=5
. Şimdi, ilk geri dönmek swap
ile t=5 and s=t_original
ve sonra da devam t=*x
. Dolayısıyla, işlev yeniden giren gibi görünüyor. Her aramanın kendi s
yığınında ayrılmış bir kopyasını aldığını unutmayın .
Tanıma bağlıdır. Örneğin Qt aşağıdakileri kullanır :
İş parçacığı güvenli * bir işlev, çağrılar paylaşılan verileri kullandığında bile birden çok iş parçacığından aynı anda çağrılabilir çünkü paylaşılan verilere yönelik tüm referanslar serileştirilir.
Bir evresel işlev birden çok iş parçacığından aynı anda çağrılabilir, ancak yalnızca her çağrı kendi verilerini kullanıyorsa.
Bu nedenle, iş parçacığı güvenli bir işlev her zaman evreseldir, ancak bir evresel işlev her zaman iş parçacığı için güvenli değildir.
Uzantı olarak, bir sınıfın, her evre farklı bir sınıf örneği kullandığı sürece, üye işlevleri birden çok evreden güvenli bir şekilde çağrılabiliyorsa , sınıfın evresel olduğu söylenir . Tüm evreler aynı sınıf örneğini kullansa bile, üye işlevleri birden çok evreden güvenle çağrılabiliyorsa , sınıf iş parçacığı açısından güvenlidir .
ama aynı zamanda uyarıyorlar:
Not: Çoklu okuma alanındaki terminoloji tamamen standartlaştırılmamıştır. POSIX, C API'leri için biraz farklı olan evresel ve iş parçacığı güvenli tanımlarını kullanır. Qt ile diğer nesne yönelimli C ++ sınıf kitaplıklarını kullanırken, tanımların anlaşıldığından emin olun.