再入可能ロックと一般的なコンセプトは何ですか?


91

私はいつも混乱します。誰かがさまざまな状況でリエントラントが何を意味するかを説明できますか?そして、なぜ再入可能ではなく再入可能を使用したいのですか?

pthread(posix)ロックプリミティブと言いますが、それらは再入可能ですか?それらを使用するときに回避すべき落とし穴は何ですか?

mutexは再入可能ですか?

回答:


157

再入可能ロック

再入可能ロックは、プロセスがそれ自体をブロックすることなく、ロックを複数回要求できるロックです。すでにロックを取得しているかどうかを追跡するのが容易でない状況で役立ちます。ロックが再入可能でない場合は、ロックを取得し、再度取得するときにブロックして、自分のプロセスを効果的にデッドロックすることができます。

一般に、再入可能性はコードのプロパティであり、コードの実行中にコードが呼び出された場合に破損する可能性のある変更可能な中心的な状態はありません。このような呼び出しは、別のスレッドによって行われるか、コード自体から発生する実行パスによって再帰的に行われる可能性があります。

コードが実行中に更新される可能性のある共有状態に依存している場合、コードは再入可能ではありません。少なくとも、その更新によってコードが壊れる可能性がある場合は例外です。

再入可能ロックの使用例

再入可能ロックのアプリケーションの(やや一般的で不自然な)例は次のとおりです。

  • グラフをトラバースするアルゴリズムを含む計算があります(おそらくその中にサイクルがあります)。トラバースは、サイクルまたは同じノードへの複数のパスが原因で、同じノードを複数回訪問する場合があります。

  • データ構造は同時アクセスの対象であり、おそらく別のスレッドによって何らかの理由で更新される可能性があります。競合状態による潜在的なデータ破損に対処するには、個々のノードをロックできる必要があります。何らかの理由(おそらくパフォーマンス)のために、データ構造全体をグローバルにロックしたくない場合。

  • 計算では、訪問したノードに関する完全な情報を保持できないか、または「以前にここに来たことがあるか」という質問にすばやく回答できないデータ構造を使用しています。

    この状況の例は、優先ヒープをバイナリヒープとして実装したダイクストラのアルゴリズムの単純な実装、または単純なリンクリストをキューとして使用する幅優先検索です。これらの場合、既存の挿入のキューをスキャンすることはO(N)であり、反復ごとに実行する必要はない場合があります。

この状況では、取得済みのロックを追跡するのはコストがかかります。ノードレベルでロックを行いたい場合、リエントラントロックメカニズムを使用すると、以前にノードにアクセスしたことがあるかどうかを確認する必要がなくなります。ノードを盲目的にロックすることができます。おそらく、キューからノードをポップした後でロックを解除できます。

再入可能なミューテックス

単純なミューテックスは、一度に1つのスレッドしかクリティカルセクションに入れることができないため、リエントラントではありません。mutexを取得してからもう一度取得しようとすると、単純なmutexには、以前にそれを保持していたユーザーを特定するための十分な情報がありません。これを再帰的に行うには、ミューテックスを取得したユーザーを確認できるように、各スレッドにトークンがあるメカニズムが必要です。これにより、mutexメカニズムがいくらか高価になるため、すべての状況で実行したくない場合があります。

IIRC POSIXスレッドAPIは、再入可能および非再入可能mutexのオプションを提供します。


2
ただし、デッドロックなどを回避することが困難になるため、通常、このような状況は回避する必要があります。とにかくスレッド化は、すでにロックを取得しているかどうかを疑うことなく十分に困難です。
Jon Skeet、

+1、ロックが再入可能でない場合も考慮してください。注意しなければ、自分でブロックできます。さらに、Cでは、ロックが獲得された回数だけロックが確実に解放されるようにするために、他の言語が行うのと同じメカニズムはありません。これは大きな問題につながる可能性があります。
user7116 2009

1
それは
まさに

@ジョン・スキート-おそらくパフォーマンスやその他の考慮事項のために、ロックを追跡することが実用的でない状況があると思います(少し工夫した上記の例を参照)。
ConcernedOfTunbridgeWells

21

リエントラントロックを使用すると、Mリソースにロックをかけるメソッドを記述してから、再帰的に、またはすでにロックを保持しているコードからA呼び出すことができます。MA

非再入可能ロックではM、の2つのバージョン(ロックするものとロックしないもの)と、正しいものを呼び出す追加のロジックが必要になります。


これは、同じロックobjを2回以上取得する再帰呼び出しがある場合、つまりx特定のスレッドによって何度も実行される場合、すべての再帰的に取得されたロックを解放せずに実行をインターリーブできないことを意味しxますか?trueの場合、この実装は基本的に順次実装されます。私は何かを逃していますか?
DevdattaK

それは本当の世界的な問題ではないはずです。それはきめ細かいロックの詳細であり、スレッドはそれ自体をロックアウトしません。
ヘンクホルターマン

16

再入可能ロックは、このチュートリアルで非常によく説明されていますます。

チュートリアルの例は、グラフのトラバースについての回答よりもはるかに簡単です。再入可能ロックは、非常に単純な場合に役立ちます。


3

再帰的mutexの内容と理由、受け入れられた回答に記載されているような複雑なものであってはなりません。

ネットを掘り下げた後、私の理解を書き留めたいと思います。


まず、ミューテックスについて話すとき、マルチスレッドの概念も間違いなく関与します。(ミューテックスは同期に使用されます。プログラムにスレッドが1つしかない場合はミューテックスは必要ありません)


次に、通常のミューテックス再帰的ミューテックスの違いを知っておく必要があります。

APUEから引用:

(再帰的mutexはa)同じスレッドが最初にロックを解除することなく複数回ロックできるようにするmutexタイプです。

主な違いは、同じスレッド内で再帰的ロックを再ロックしてもデッドロックが発生せず、スレッドをブロックしないことです。

これは、再帰的なロックによってデッドロックが発生することはないという意味ですか?
いいえ、ロックを解除せずに1つのスレッドでロックし、他のスレッドでロックしようとすると、通常のミューテックスとしてデッドロックが発生する可能性があります。

証明としていくつかのコードを見てみましょう。

  1. デッドロックのある通常のミューテックス
#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);

}

出力:

thread1
thread1 hey hey
thread2

一般的なデッドロックの例、問題ありません。

  1. デッドロックのある再帰的ミューテックス

この行のコメントを外してください
error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_RECURSIVE);
コメントを外して、他のをコメント化してください。

出力:

thread1
thread1 hey hey
thread2

はい、再帰的なミューテックスもデッドロックを引き起こす可能性があります。

  1. 通常のミューテックス、同じスレッドで再ロック
#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);

}

出力:

thread1
func3
thread2

デッドロック中thread t1func3
(私sleep(2)はデッドロックが最初にで再ロックすることによって引き起こされていることを簡単に確認できるようにするために使用しますfunc3

  1. 再帰的ミューテックス、同じスレッドで再ロック

繰り返しますが、再帰的なmutex行のコメントを外して、他の行をコメント化します。

出力:

thread1
func3
func3 hey hey
thread1 hey hey
thread2

デッドロック中thread t2func2。見る?func3終了して終了します。再ロックしてもスレッドはブロックされず、デッドロックも発生しません。


それで、最後の質問、なぜそれが必要なのですか?

再帰関数の場合(マルチスレッドプログラムで呼び出され、一部のリソース/データを保護する場合)。

たとえば、マルチスレッドプログラムがあり、スレッドAで再帰関数を呼び出します。その再帰関数で保護したいデータがあるので、mutexメカニズムを使用します。その関数の実行はスレッドAではシーケンシャルなので、再帰的にmutexを確実に再ロックします。通常のミューテックスを使用すると、デッドロックが発生します。そして再帰的なミューテックスこれを解決するためにが発明されました。

受け入れられた回答の例を参照してください

ウィキペディアは再帰的ミューテックスを非常によく説明しています。間違いなく読む価値があります。ウィキペディア:Reentrant_mutex

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.