Yinelemeli Kilit (Mutex) ve Yinelemesiz Kilit (Mutex)


183

POSIX, mutekslerin özyinelemesine izin verir. Bu, aynı iş parçacığının aynı muteksi iki kez kilitleyebileceği ve kilitlenmeyeceği anlamına gelir. Tabii ki aynı zamanda iki kez kilidini açması gerekiyor, aksi takdirde başka hiçbir iplik muteksi elde edemez. Pthreads'i destekleyen tüm sistemler özyinelemeli muteksleri de desteklemez, ancak POSIX uyumlu olmak istiyorlarsa, yapmak zorundadırlar .

Diğer API'ler (daha yüksek seviye API'ler) genellikle genellikle Kilitler olarak adlandırılan muteksler sunar. Bazı sistemler / diller (örn. Cocoa Objective-C) hem yinelemeli hem de yinelemesiz muteksler sunar. Bazı diller de yalnızca birini veya diğerini sunar. Örneğin, Java muteksleri her zaman özyinelemelidir (aynı iş parçacığı aynı nesne üzerinde iki kez "eşitlenebilir"). Sundukları diğer iplik işlevselliğine bağlı olarak, kendiniz kolayca yazılabildikleri için özyinelemeli mutekslere sahip olmak sorun olmayabilir (daha basit muteks / koşul işlemleri temelinde kendimde yinelenen muteksler uyguladım).

Ne anlamıyorum: Özyinelemeli muteksler ne işe yarar? Aynı muteksi iki kez kilitlerse neden bir iplik kilitlenmesi olmasını isterim? Bundan kaçınabilecek üst düzey diller bile (örneğin, bunun kilitlenip kilitlenmeyeceğini test etmek ve eğer bir istisna atmak) genellikle bunu yapmaz. Bunun yerine ipliğin kilitlenmesine izin verecekler.

Bu sadece yanlışlıkla iki kez kilitlediğim ve sadece bir kez kilidini açtığım durumlar için mi ve özyinelemeli bir muteks durumunda, sorunu bulmak daha zor olurdu, bu yüzden bunun yerine yanlış kilidin nerede göründüğünü görmek için hemen kilitlenme var mı? Ama kilidini açarken kilit sayacı döndürmekle aynı şeyi yapamadım ve son kilidi açtığımdan ve sayacın sıfır olmadığından emin olduğum bir durumda, bir istisna atabilir veya sorunu kaydedebilir miyim? Ya da göremediğim özyineli olmayan mutekslerin başka, daha kullanışlı bir kullanım durumu var mı? Veya özyinelemesiz bir muteks özyinelemeli olandan biraz daha hızlı olabileceğinden, sadece performans mıdır? Ancak, bunu test ettim ve fark gerçekten büyük değil.

Yanıtlar:


154

Özyinelemeli ve özyinelemesiz muteks arasındaki fark sahiplikle ilgilidir. Özyinelemeli bir muteks durumunda, çekirdek, muteksi ilk kez elde eden iş parçacığını izlemelidir, böylece özyineleme ile bunun yerine engellenmesi gereken farklı bir iş parçacığı arasındaki farkı algılayabilir. Başka bir cevabın işaret ettiği gibi, hem bu bağlamı saklamak için bellek hem de onu korumak için gerekli döngüler açısından bunun ek yükü söz konusudur.

Ancak burada dikkat edilmesi gereken başka noktalar da var.

Özyinelemeli muteks bir sahiplenme duygusuna sahip olduğundan, muteksi yakalayan evre muteksi serbest bırakan evre ile aynı olmalıdır. Özyinelemesiz muteksler söz konusu olduğunda, sahiplenme duygusu yoktur ve hangi iplik orijinal olarak muteksi alırsa alınsın herhangi bir iplik genellikle muteksi serbest bırakabilir. Çoğu durumda, bu tür "muteks" gerçekten daha çok bir semafor eylemidir, burada muteksi mutlaka bir dışlama cihazı olarak kullanmazsınız, ancak iki veya daha fazla iş parçacığı arasında senkronizasyon veya sinyalleme cihazı olarak kullanırsınız.

Muteks içinde sahiplik duygusu ile gelen bir başka özellik de öncelikli mirasın desteklenmesidir. Çekirdek muteksin sahibi olan iş parçacığını ve tüm engelleyicilerin kimliğini izleyebildiğinden, öncelikli dişli sistemde, şu anda muteksin sahibi olan iş parçacığının önceliğini en yüksek öncelikli iş parçacığının önceliğine yükseltmek mümkün hale gelir şu anda muteksi engelliyor. Bu kalıtım, bu gibi durumlarda oluşabilecek öncelikli ters çevirme sorununu önler. (Tüm sistemlerin bu tür mutekslerde öncelikli kalıtımı desteklemediğini, ancak sahiplik kavramıyla mümkün olan başka bir özellik olduğunu unutmayın).

Klasik VxWorks RTOS çekirdeğine başvurursanız, üç mekanizma tanımlar:

  • mutex - özyinelemeyi ve isteğe bağlı olarak öncelikli devralmayı destekler. Bu mekanizma, verilerin kritik bölümlerini tutarlı bir şekilde korumak için yaygın olarak kullanılır.
  • ikili semafor - özyineleme yok, kalıtım yok, basit dışlama, alıcı ve verici aynı iş parçacığı olmak zorunda değildir, yayın sürümü mevcut. Bu mekanizma kritik bölümleri korumak için kullanılabilir, ancak dişler arasında uyumlu sinyalleme veya senkronizasyon için de özellikle yararlıdır.
  • semafor sayma - özyineleme veya kalıtım yok, istenen herhangi bir başlangıç ​​sayımından tutarlı bir kaynak sayacı görevi görür, iş parçacıkları yalnızca kaynağa karşı net sayımın sıfır olduğu yerlerde engeller.

Yine, bu, platforma göre biraz değişmektedir - özellikle de bunlara ne dediklerini, ancak bu, oyundaki kavramları ve çeşitli mekanizmaları temsil etmelidir.


9
özyinelemesiz muteks hakkındaki açıklamanız daha çok bir semafor gibiydi. Muteks (özyinelemeli veya özyinelemesiz) sahiplik kavramına sahiptir.
Jay D

@JayD İnsanlar böyle şeyler hakkında tartıştıklarında çok kafa karıştırıcı .. peki bu şeyleri tanımlayan varlık kim?
Pacerier

13
@Pacerier İlgili standart. Bu yanıt örneğin posix (pthreads) için yanlıştır, burada kilitlenen iş parçacığı dışındaki bir iş parçacığında normal bir muteksin kilidinin tanımlanmamış bir davranıştır, aynı şeyi bir hata denetimi veya özyinelemeli muteks ile yapmak öngörülebilir bir hata koduyla sonuçlanır. Diğer sistemler ve standartlar çok farklı olabilir.
nos

Belki de bu naif, ama bir muteksin merkezi fikri, kilitleme ipliğinin muteksin kilidini açtığı ve daha sonra diğer ipliklerin de aynısını yapabileceği izlenimi altındaydım. Gönderen computing.llnl.gov/tutorials/pthreads :
user657862

2
@curiousguy - bir yayın sürümü, semaforda engellenen tüm iş parçacıklarını açıkça vermeden (boş kalır) serbest bırakır, oysa normal bir ikili yayın yalnızca bekleme kuyruğunun başındaki iş parçacığını serbest bırakır (engellenmiş bir varsa).
Uzun Jeff

123

Cevap verimlilik değildir . Evresel olmayan muteksler daha iyi koda yol açar.

Örnek: A :: foo () kilidi alır. Daha sonra B :: bar () öğesini çağırır. Yazdığınızda bu işe yaradı. Ama bir süre sonra birisi A :: baz () 'ı çağırmak için B :: bar ()' ı değiştirir.

Eğer özyinelemeli muteksleriniz yoksa, bu çıkmazlar. Onlara sahipseniz, çalışır, ancak kırılabilir. A :: foo (), baz () işlevinin muteks'i de edinmesi nedeniyle çalıştırılamadığı varsayılarak, bar () çağrılmadan önce nesneyi tutarsız bir durumda bırakmış olabilir. Ama muhtemelen kaçmamalı! A :: foo () yazan kişi, aynı anda kimsenin A :: baz () diyemeyeceğini varsayar - bu her iki yöntemin de kilidi almasının nedeni budur.

Muteksleri kullanmak için doğru zihinsel model: Muteks bir değişmezi korur. Muteks tutulduğunda, değişmez değişebilir, ancak muteksi serbest bırakmadan önce değişmez yeniden kurulur. Ev kilitleri tehlikelidir, çünkü kilidi ikinci kez aldığınızda değişmezin artık doğru olduğundan emin olamazsınız.

Ev kilitlerinden memnunsanız, bunun nedeni daha önce böyle bir sorunu ayıklamak zorunda kalmamanızdır. Bu arada Java'nın java.util.concurrent.locks dosyasında bu gün evresel olmayan kilitleri var.


4
Kilidi ikinci kez yakaladığınızda değişmezin geçerli olmaması hakkında söylediklerinizi almam biraz zaman aldı. İyi bir nokta! Ya bir okuma-yazma kilidi olsaydı (Java'nın ReadWriteLock'u gibi) ve okuma kilidini edindikten sonra aynı kilide ikinci kez okuma kilidini yeniden aldınız. Bir okuma kilidi aldıktan sonra bir değişmezi geçersiz kılmazsınız değil mi? İkinci okuma kilidini aldığınızda, değişmez hala doğrudur.
dgrant

1
@Jonathan Java, java.util.concurrent.locks bu günlerde evresel olmayan kilitleri var mı ??
user454322

1
+1 Sanırım reentrant kilidi için en yaygın kullanım, hem korumalı hem de korumasız kod parçalarından bazı yöntemlerin çağrılabileceği tek bir sınıfın içinde. Bu aslında her zaman çarpanlarına ayrılabilir. @ user454322 Elbette Semaphore,.
maaartinus

1
Affedersiniz, ama bunun muteks ile nasıl alakalı olduğunu görmüyorum. Herhangi bir çoklu iş parçacığı ve kilitlemenin olmadığını, A::foo()çağrılmadan önce nesneyi tutarsız bir durumda bırakmış olabileceğini varsayın A::bar(). Muteks, özyinelemeli veya değil, bu durumla ne ilgisi var?
Siyuan Ren

1
@SiyuanRen: Sorun yerel olarak kod hakkında akıl yürütüyor. İnsanlar (en azından ben) kilitli bölgeleri değişmez bakım olarak tanımak üzere eğitilir, yani kilidi aldığınız sırada başka hiçbir iş parçacığı durumu değiştirmez, bu nedenle kritik bölgedeki değişmezler tutar. Bu zor bir kural değildir ve değişmezleri akılda tutmamakla kodlayabilirsiniz, ancak bu sadece kodunuzun akıl yürütmesini ve bakımını zorlaştırır. Aynı şey muteksler olmadan tek iş parçacıklı modda olur, ancak orada korunan bölge çevresinde yerel olarak akıl yürütmek için eğitilmedik.
David Rodríguez - dribeas

92

Dave Butenhof'un kendisi tarafından yazıldığı gibi :

"Özyinelemeli mutekslerle ilgili tüm büyük sorunların en büyüğü, sizi kilitleme şemanızın ve kapsamınızın izini tamamen kaybetmeye teşvik etmeleridir. Bu ölümcül. Kötü. Bu" iplik yiyen ". Kesinlikle mümkün olan en kısa süre için kilitleri tutuyorsunuz. Kilitli bir şeyi sadece tutulduğunu bilmediğiniz için ya da callee'nin mutekse ihtiyacı olup olmadığını bilmediğiniz için çağırıyorsanız, çok uzun süre tutuyorsunuz demektir. uygulamanıza bir av tüfeği hedefliyor ve tetiği çekiyorsunuz. Muhtemelen eşzamanlılık elde etmek için iplikleri kullanmaya başladınız;


9
Ayrıca Butenhof'un yanıtındaki son bölüme dikkat edin:...you're not DONE until they're [recursive mutex] all gone.. Or sit back and let someone else do the design.
user454322

2
Ayrıca, tek bir küresel özyinelemeli muteks kullanmanın (sadece bir taneye ihtiyacınız olduğu fikridir), çok iş parçacıklı kodda kullanmaya başladığınızda, harici bir kütüphanenin değişmezliklerini anlamanın zor işini bilinçli olarak ertelemek için bir koltuk değneği olarak iyi olduğunu söyler. Ancak koltuk değneklerini sonsuza kadar kullanmamalısınız, ancak sonunda kodun eşzamanlı değişmezlerini anlamak ve düzeltmek için zaman ayırın. Bu nedenle, özyinelemeli muteks kullanmanın teknik borç olduğunu söyleyebiliriz.
FooF

13

Muteksleri kullanmak için doğru zihinsel model: Muteks bir değişmezi korur.

Bunun muteksleri kullanmak için gerçekten doğru bir zihinsel model olduğundan neden emin misiniz? Bence doğru model verileri koruyor ama değişmezleri korumuyor.

Değişmezleri koruma sorunu, tek iş parçacıklı uygulamalarda bile ortaya çıkar ve çoklu iş parçacığı ve mutekslerde ortak bir şey yoktur.

Ayrıca, değişmezleri korumanız gerekirse, yinelemesiz olan ikili semaforu kullanabilirsiniz.


Doğru. Bir değişmezi korumak için daha iyi mekanizmalar vardır.
ActiveTrayPrntrTagDataStrDrvr

8
Bu, bu ifadeyi sunan cevaba bir yorum olmalıdır. Muteksler sadece verileri korumakla kalmaz, değişmezleri de korur. Muteksler yerine atomik (verilerin kendini koruduğu) açısından basit bir kap (en basit yığın) yazmayı deneyin ve ifadeyi anlayacaksınız.
David Rodríguez - dribeas

Muteksler verileri korumaz, bir değişmezi korurlar. Bu değişmez veriyi korumak için kullanılabilir.
Jon Hanna

4

Özyinelemeli mutekslerin yararlı olmasının temel nedenlerinden biri, yöntemlere aynı iş parçacığıyla birden çok kez erişilmesidir. Örneğin, muteks kilidi bir bankayı A / c'yi çekmek için koruyorsa, bu para çekme ile de ilgili bir ücret varsa, aynı muteksin kullanılması gerektiğini varsayalım.


4

Özyineleme muteksi için tek iyi kullanım durumu, bir nesnenin birden çok yöntem içermesidir. Yöntemlerden herhangi biri nesnenin içeriğini değiştirdiğinde ve bu nedenle durum tekrar tutarlı olmadan önce nesneyi kilitlemesi gerektiğinde.

Yöntemler başka yöntemler kullanıyorsa (örneğin: addNewArray (), addNewPoint () öğesini çağırır ve recheckBounds () ile sonlandırır, ancak bu işlevlerden herhangi birinin kendi başına muteksi kilitlemesi gerekir, sonra yinelemeli muteks bir kazan-kazan olur.

Başka herhangi bir durumda (sadece kötü kodlamayı çözmek, farklı nesnelerde bile kullanmak) açıkça yanlıştır!


1

Özyinelemesiz muteksler ne işe yarar?

Bir şey yapmadan önce muteksin kilidinin açıldığından emin olmanız gerektiğinde kesinlikle iyidirler . Bunun nedeni pthread_mutex_unlockmuteksin yalnızca özyinelemesiz olması durumunda kilidinin açılmasını garanti edebilmesidir.

pthread_mutex_t      g_mutex;

void foo()
{
    pthread_mutex_lock(&g_mutex);
    // Do something.
    pthread_mutex_unlock(&g_mutex);

    bar();
}

g_mutexÖzyinelemeli değilse , yukarıdaki kodun bar()muteks kilidi açıkken aranacağı garanti edilir .

Böylece bar(), başka bir iş parçacığının aynı muteksi elde etmeye çalışmasına neden olabilecek bir şey yapabilen bilinmeyen bir dış işlev olması durumunda bir kilitlenme olasılığını ortadan kaldırmak . Bu tür senaryolar, iş parçacığı havuzları üzerinde oluşturulan uygulamalarda ve bir süreçler arası çağrının istemci programcı bunu fark etmeden yeni bir iş parçacığı oluşturabileceği dağıtılmış uygulamalarda nadir değildir. Tüm bu senaryolarda, söz konusu harici fonksiyonları sadece kilit açıldıktan sonra çağırmak en iyisidir.

Eğer g_mutexözyinelemeli oldu sadece orada olacağını hiçbir şekilde arama yapmadan önce emin kilidi yapmak.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.