Genel olarak yeniden giriş kilidi ve kavramı nedir?


94

Kafam hep karışır. Birisi Reentrant'ın farklı bağlamlarda ne anlama geldiğini açıklayabilir mi ? Ve neden evresel ve evresel olmayan kullanmak isteyesiniz?

Pthread (posix) kilitleme ilkelleri deyin, bunlar yeniden giren mi değil mi? Bunları kullanırken hangi tuzaklardan kaçınılmalıdır?

Mutex yeniden mi giriyor?

Yanıtlar:


161

Yeniden giriş kilitleme

Bir evresel kilit, bir sürecin kendi kendini engellemeden kilidi birden çok kez talep edebildiği bir kilittir. Zaten bir kilit kapmış olup olmadığınızı takip etmenin kolay olmadığı durumlarda kullanışlıdır. Bir kilit yeniden girilmezse, kilidi kapabilir, ardından tekrar kapmaya gittiğinizde bloklayarak kendi sürecinizi etkin bir şekilde kilitleyebilirsiniz.

Genel olarak yeniden giriş, kod yürütülürken çağrılırsa, bozulabilecek merkezi bir değişken duruma sahip olmadığı bir kod özelliğidir. Böyle bir çağrı başka bir evre tarafından yapılabilir veya kodun kendisinden kaynaklanan bir yürütme yolu tarafından özyinelemeli olarak yapılabilir.

Kod, yürütme işleminin ortasında güncellenebilecek paylaşılan duruma dayanıyorsa, en azından bu güncelleme onu bozabilirse, yeniden giriş değildir.

Yeniden giriş kilitleme için bir kullanım durumu

Yeniden giriş kilidi için bir başvurunun (biraz genel ve uydurma) bir örneği şöyle olabilir:

  • Bir grafiğin üzerinden geçen bir algoritmayı içeren bazı hesaplamalarınız var (belki de içinde döngülerle). Bir geçiş, döngüler nedeniyle veya aynı düğüme giden birden çok yol nedeniyle aynı düğümü birden fazla ziyaret edebilir.

  • Veri yapısı eşzamanlı erişime tabidir ve herhangi bir nedenle, belki başka bir iş parçacığı tarafından güncellenebilir. Yarış koşulları nedeniyle olası veri bozulmasıyla başa çıkmak için tek tek düğümleri kilitleyebilmeniz gerekir. Bazı nedenlerden dolayı (belki performans) tüm veri yapısını global olarak kilitlemek istemezsiniz.

  • Hesaplamanız, hangi düğümleri ziyaret ettiğiniz hakkında tam bilgi tutamaz veya 'daha önce burada bulundum mu' sorularının hızlı bir şekilde yanıtlanmasına izin vermeyen bir veri yapısı kullanıyorsunuz.

    Bu duruma bir örnek, Dijkstra algoritmasının bir ikili yığın olarak uygulanan bir öncelik kuyruğuna sahip basit bir uygulaması veya bir kuyruk olarak basit bir bağlantılı listeyi kullanan genişlikte bir arama olabilir. Bu durumlarda, kuyruğun mevcut eklemeler için taranması O (N) 'dir ve bunu her yinelemede yapmak istemeyebilirsiniz.

Bu durumda, halihazırda sahip olduğunuz kilitleri takip etmek pahalıdır. Düğüm seviyesinde kilitlemeyi yapmak istediğinizi varsayarsak, bir yeniden giriş kilitleme mekanizması daha önce bir düğümü ziyaret edip etmediğinizi söyleme ihtiyacını azaltır. Düğümü körü körüne kilitleyebilir, belki de kuyruktan çıkardıktan sonra kilidini açabilirsiniz.

Yeniden giren muteksler

Belirli bir zamanda kritik bölümde yalnızca bir iş parçacığı olabileceğinden, basit bir muteks yeniden giriş değildir. Muteksi yakalar ve sonra tekrar yakalamaya çalışırsanız, basit bir muteks, onu daha önce kimin tuttuğunu söyleyecek yeterli bilgiye sahip olmaz. Bunu yinelemeli olarak yapmak için, her bir iş parçacığının bir belirteci olduğu bir mekanizmaya ihtiyacınız vardır, böylece muteksi kimin kaptığını anlayabilirsiniz. Bu, muteks mekanizmasını biraz daha pahalı hale getirir, bu nedenle her durumda yapmak istemeyebilirsiniz.

IIRC POSIX iş parçacıkları API'si, yeniden giren ve yeniden giren olmayan muteksler seçeneği sunar.


2
Bununla birlikte, bu tür durumlardan genellikle kaçınılması gerekir, çünkü kilitlenmeyi vb. Önlemeyi zorlaştırır. Zaten bir kilidiniz olup olmadığı konusunda şüphe duymadan diş açma yeterince zordur.
Jon Skeet

+1, ayrıca kilidin evresel OLMADIĞI durumu da göz önünde bulundurun, dikkatli değilseniz kendinizi bloke edebilirsiniz. Artı C'de, kilidin Alındığı kadar çok kez Serbest bırakılmasını sağlamak için diğer dillerin yaptığı aynı mekanizmalara sahip değilsiniz. Bu büyük sorunlara yol açabilir.
kullanıcı7116

1
Dün bana olan tam olarak buydu: Yeniden giriş konusunu dikkate
almadım

@Jon Skeet - Performans veya diğer hususlar nedeniyle kilitleri takip etmenin pratik olmadığı durumlar olabileceğini düşünüyorum (yukarıdaki biraz yapmacık örneğime bakın).
ConcernedOfTunbridgeWells

21

Yeniden giriş kilidi M, kaynağa bir kilit koyan Ave ardından Mözyinelemeli olarak veya halihazırda bir kilit tutan koddan çağıran bir yöntem yazmanıza olanak tanırA .

Yeniden girişli olmayan bir kilitle, Mbiri kilitli diğeri kilitlemeyen olmak üzere 2 sürümüne ve doğru olanı aramak için ek mantığa ihtiyacınız olacaktır.


Bu, aynı kilit objesini birden fazla kez alan özyinelemeli aramalarım varsa - örneğin xbelirli bir iş parçacığı tarafından defalarca, yinelemeli olarak elde edilen tüm kilitleri serbest bırakmadan yürütmeyi araya ekleyemeyeceğim anlamına mı geliyor (aynı kilit ancak xbirkaç kez)? Doğruysa, bu uygulamayı esasen sıralı hale getirir. Bir şey mi kaçırıyorum?
DevdattaK

Bu gerçek bir dünya problemi olmamalı. Daha çok parçalı kilitleme ve bir Dişin kendi kendine kilitlenmemesi ile ilgili.
Henk Holterman

17

Yeniden giriş kilidi bu eğitimde çok iyi açıklanmıştır .

Öğreticideki örnek, bir grafiğin üzerinden geçme hakkındaki cevaptan çok daha az yapmacıktır. Evresel kilit, çok basit durumlarda kullanışlıdır.


3

Yinelemeli muteksin ne ve neden , kabul edilen yanıtta açıklanan bu kadar karmaşık bir şey olmamalıdır.

İnternette biraz kazı yaptıktan sonra anlayışımı yazmak istiyorum.


İlk olarak, muteks hakkında konuşurken , çok iş parçacıklı kavramların da kesinlikle dahil olduğunu anlamalısınız . (muteks senkronizasyon için kullanılır. Programımda sadece 1 iş parçacığım varsa mutekse ihtiyacım yok)


İkinci olarak, normal bir muteks ve özyinelemeli bir muteks arasındaki farkı bilmelisiniz. .

APUE'den alıntı :

(Özyinelemeli bir muteks, a) Aynı iş parçacığının , önce kilidini açmadan onu birçok kez kilitlemesine izin veren bir muteks türüdür .

Temel fark, aynı iş parçacığı içinde özyinelemeli bir kilidin kilitlenmeye yol açmaması ve iş parçacığını engellememesidir.

Bu, tekrarlayıcı kilidin asla kilitlenmeye neden olmadığı anlamına mı geliyor?
Hayır, kilidi açmadan bir iş parçacığında kilitlediyseniz ve diğer iş parçacıklarında kilitlemeye çalışıyorsanız, normal muteks gibi kilitlenmeye neden olabilir.

Kanıt olarak biraz kod görelim.

  1. kilitlenme ile normal muteks
#include <pthread.h>
#include <stdio.h>

pthread_mutex_t lock;


void * func1(void *arg){
    printf("thread1\n");
    pthread_mutex_lock(&lock);
    printf("thread1 hey hey\n");

}


void * func2(void *arg){
    printf("thread2\n");
    pthread_mutex_lock(&lock);
    printf("thread2 hey hey\n");
}

int main(){
    pthread_mutexattr_t lock_attr;
    int error;
//    error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_RECURSIVE);
    error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_DEFAULT);
    if(error){
        perror(NULL);
    }

    pthread_mutex_init(&lock, &lock_attr);

    pthread_t t1, t2;

    pthread_create(&t1, NULL, func1, NULL);
    pthread_create(&t2, NULL, func2, NULL);

    pthread_join(t2, NULL);

}

çıktı:

thread1
thread1 hey hey
thread2

ortak kilitlenme örneği, sorun yok.

  1. kilitlenme ile özyinelemeli muteks

Sadece bu satırı uncomment
error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_RECURSIVE);
açıklamasını ve diğerini yorumlayın.

çıktı:

thread1
thread1 hey hey
thread2

Evet, yinelemeli muteks de kilitlenmeye neden olabilir.

  1. normal muteks, aynı iş parçacığı içinde yeniden kilitle
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

pthread_mutex_t lock;


void func3(){
    printf("func3\n");
    pthread_mutex_lock(&lock);
    printf("func3 hey hey\n");
}

void * func1(void *arg){
    printf("thread1\n");
    pthread_mutex_lock(&lock);
    func3();
    printf("thread1 hey hey\n");

}


void * func2(void *arg){
    printf("thread2\n");
    pthread_mutex_lock(&lock);
    printf("thread2 hey hey\n");
}

int main(){
    pthread_mutexattr_t lock_attr;
    int error;
//    error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_RECURSIVE);
    error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_DEFAULT);
    if(error){
        perror(NULL);
    }

    pthread_mutex_init(&lock, &lock_attr);

    pthread_t t1, t2;

    pthread_create(&t1, NULL, func1, NULL);
    sleep(2); 
    pthread_create(&t2, NULL, func2, NULL);

    pthread_join(t2, NULL);

}

çıktı:

thread1
func3
thread2

Kilitlenme thread t1, içeri func3.
( sleep(2)Kilitlenmenin öncelikle yeniden kilitlenmeden kaynaklandığını görmeyi kolaylaştırmak için kullanırım func3)

  1. özyinelemeli muteks, aynı iş parçacığı içinde yeniden kilitle

Yine, yinelemeli muteks satırının açıklamasını kaldırın ve diğer satırı yorumlayın.

çıktı:

thread1
func3
func3 hey hey
thread1 hey hey
thread2

İçinde Kilitlenme thread t2içinde, func2. Görmek? func3bitirir ve çıkar, yeniden kilitleme iş parçacığı engellemez veya kilitlenmeye yol açmaz.


Öyleyse, son soru, neden buna ihtiyacımız var?

Özyinelemeli işlev için (çok iş parçacıklı programlarda çağrılır ve bazı kaynakları / verileri korumak istiyorsunuz).

Örneğin, çok evreli bir programınız var ve A evresinde özyinelemeli bir işlev çağırıyorsunuz. Bu özyinelemeli işlevde korumak istediğiniz bazı verileriniz var, dolayısıyla muteks mekanizmasını kullanıyorsunuz. Bu işlevin yürütülmesi A evresinde sıralıdır, bu nedenle muteksi özyinelemede kesinlikle yeniden kilitlersiniz. Normal muteks kullanmak kilitlenmelere neden olur. Ve resursive mutex bunu çözmek için icat edildi.

Kabul edilen cevaptan bir örnek görün Özyinelemeli muteks ne zaman kullanılır? .

Wikipedia yinelemeli muteksi çok iyi açıklıyor. Kesinlikle okumaya değer. Wikipedia: Reentrant_mutex

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.