スレッドセーフvsリエントラント


回答:


42

再入可能関数は、Cライブラリヘッダーで公開されているグローバル変数に依存しません。たとえば、Cではstrtok()とstrtok_r()を使用します。

一部の関数には「進行中の作業」を格納する場所が必要です。再入可能関数を使用すると、グローバルではなく、スレッド自体のストレージ内でこのポインターを指定できます。このストレージは呼び出し元の関数専用であるため、中断して再入力(再入可能)することができ、ほとんどの場合、関数が実装するものを超える相互排除はこれが機能するために必要ではないため、次のように見なされます。スレッドセーフ。ただし、これは定義上保証されていません。

ただし、errnoはPOSIXシステムではわずかに異なるケースです(そして、これがどのように機能するかを説明する上で奇妙な傾向があります):)

要するに、リエントラントはしばしばスレッドセーフを意味しますが(「スレッドを使用している場合はその関数のリエントラントバージョンを使用する」のように)、スレッドセーフは必ずしもリエントラント(またはその逆)を意味するわけではありません。スレッドセーフを検討している場合、並行性について考える必要があります。関数を使用するためにロックと相互排除の手段を提供する必要がある場合、その関数は本質的にスレッドセーフではありません。

ただし、すべての機能を調べる必要はありません。malloc()再入可能である必要はありません。特定のスレッドのエントリポイントの範囲外のものに依存しません(そしてそれ自体がスレッドセーフです)。

静的に割り当てられた値を返す関数は、ミューテックス、futex、またはその他のアトミックロックメカニズムを使用しないとスレッドセーフではありません。それでも、中断されないのであれば、再入可能である必要はありません。

すなわち:

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;
}

したがって、ご覧のとおり、複数のスレッドでそれを使用することは、なんらかのロックなしで災害になります。しかし、再入可能にする目的はありません。一部の組み込みプラットフォームで動的に割り当てられたメモリがタブーである場合、これに遭遇します。

純粋な関数型プログラミングでは、リエントラントはスレッドセーフを意味しないことが多く、関数のエントリポイントに渡される定義済み関数または無名関数の動作、再帰などに依存します。

「スレッドセーフ」を配置するためのより良い方法は、同時アクセスに対して安全であり、これは必要性をよりよく示しています。


2
再入可能は、スレッドセーフを意味するものではありません。純粋関数はスレッドセーフを意味します。
Julio Guerra 2013年

素晴らしい答えティム。明確にするために、あなたの「しばしば」からの私の理解は、スレッドセーフは再入可能を意味しないが、再入可能はスレッドセーフを意味しないということです。スレッドセーフではないリエントラント関数の例を見つけることができますか?
リカルド2014年

@ Tim Post「要するに、リエントラントはスレッドセーフを意味することがよくあります(「スレッドを使用している場合はその関数のリエントラントバージョンを使用する」のように)が、スレッドセーフは必ずしもリエントラントを意味するわけではありません。qt反対のことを言っています。「したがって、スレッドセーフ関数は常に再入可能ですが、再入可能関数は常にスレッドセーフであるとは限りません。」
4pie0 2015

ウィキペディアさらに別のことを述べています。「この再入可能性の定義は、マルチスレッド環境でのスレッドセーフの定義とは異なります。再入可能サブルーチンはスレッドセーフを実現できますが[1]、再入可能であるだけではスレッドセーフになるのに十分ではない可能性があります。すべての状況で。逆に、スレッドセーフコードは必ずしも再入可能である必要はありません(...) "
4pie0 2015

@Riccardo:揮発性変数を介して同期されたが、シグナル/割り込みハンドラーで使用するための完全なメモリバリアではない関数は、通常、再入可能ですが、スレッドセーフです。
doynax 2015年

77

TL; DR:関数は、再入可能、スレッドセーフ、またはその両方にすることができます。

スレッドセーフ再入可能に関するウィキペディアの記事は読む価値があります。ここにいくつかの引用があります:

次の場合、関数はスレッドセーフです。

複数のスレッドが同時に安全に実行できることを保証する方法でのみ、共有データ構造を操作します。

次の場合、関数は再入可能です。

実行中の任意の時点で中断され、前の呼び出しが実行を完了する前に、安全に再度呼び出されます(「再入力」)。

可能な再入可能性の例として、ウィキペディアはシステム割り込みによって呼び出されるように設計された関数の例を示しています。別の割り込みが発生したときにすでに実行されていると仮定します。ただし、システム割り込みを使用してコーディングしないという理由だけで安全だとは思わないでください。コールバックまたは再帰関数を使用すると、シングルスレッドプログラムで再入可能問題が発生する可能性があります。

混乱を避けるための鍵は、再入可能が実行中の1つのスレッドのみを参照することです。これは、マルチタスクオペレーティングシステムが存在しなかった時代からの概念です。

(ウィキペディアの記事から少し変更)

例1:スレッドセーフではなく、再入可能ではない

/* 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;
}

例2:スレッドセーフ、再入可能ではない

/* 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;
}

例3:スレッドセーフではない、再入可能

/* 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;
}

例4:スレッドセーフ、再入可能

/* 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;
}

10
感謝の気持ちを表すためだけにコメントする必要はないことはわかっていますが、これは、再入可能機能とスレッドセーフ機能の違いを説明する最高の図の1つです。特に、非常に簡潔で明確な用語を使用し、4つのカテゴリを区別するための優れたサンプル関数を選択しました。ほんとありがと!
ryyker 2017

11
例3は再入可能ではないように思われます。シグナルハンドラーが、の後t = *xに割り込み、を呼び出すとswap()tオーバーライドされ、予期しない結果が発生します。
rom1v

1
@ SandBag_1996、swap(5, 6)によって中断された呼び出しについて考えてみましょうswap(1, 2)。の後t=*xs=t_originalおよびt=5。さて、中断後、s=5そしてt=1。ただし、2番目swapが戻る前に、コンテキストが復元され、が作成されt=s=5ます。今、私たちは、最初に戻っswapt=5 and s=t_original以降継続しますt=*x。したがって、関数は再入可能であるように見えます。すべての呼び出しは、sスタックに割り当てられた独自のコピーを取得することに注意してください。
urnonav

4
@ SandBag_1996関数が(任意の時点で)中断された場合、それは再度呼び出されるだけであり、元の呼び出しを続行する前に、関数が完了するまで待機することを前提としています。他に何かが起こった場合、それは基本的にマルチスレッドであり、この関数はスレッドセーフではありません。関数がABCDを実行するとすると、AB_ABCD_CD、A_ABCD_BCD、さらにはA__AB_ABCD_CD__BCDなどのみを受け入れます。確認できるように、例3はこれらの仮定の下で正常に機能するため、再入可能です。お役に立てれば。
MiniQuark

1
@ SandBag_1996、ミューテックスは実際には再入可能ではありません。最初の呼び出しはミューテックスをロックします。2番目の呼び出しであるデッドロックが発生します。
urnonav

56

定義によって異なります。たとえば、Qtは以下を使用します。

  • 共有データへのすべての参照がシリアル化されるため、呼び出しで共有データが使用されている場合でも、スレッドセーフ*関数を複数のスレッドから同時に呼び出すことができます。

  • 再入可能関数は、複数のスレッドから同時に呼び出すことができますが、それぞれの呼び出しは、独自のデータを使用する場合にのみ。

したがって、スレッドセーフ関数は常に再入可能ですが、再入可能関数は常にスレッドセーフであるとは限りません。

ひいては、各スレッドがクラスの異なるインスタンスを使用している限り、そのメンバー関数を複数のスレッドから安全に呼び出すことができる場合、クラスは再入可能であると言われます。すべてのスレッドがクラスの同じインスタンスを使用している場合でも、そのメンバー関数を複数のスレッドから安全に呼び出すことができれば、クラスはスレッドセーフです。

しかし、彼らはまた警告します:

注:マルチスレッドドメインの用語は完全に標準化されているわけではありません。POSIXは、CAPIでは多少異なるリエントラントとスレッドセーフの定義を使用します。Qtで他のオブジェクト指向C ++クラスライブラリを使用する場合は、定義が理解されていることを確認してください。


2
このリエントラントの定義は強すぎます。
qweruiop 2014年

関数は、グローバル/静的変数を使用しない場合、再入可能であり、スレッドセーフです。スレッドセーフ:多くのスレッドが同時に関数を実行する場合、競合はありますか?グローバル変数を使用する場合は、ロックを使用して保護します。したがって、スレッドセーフです。リエントラント:関数の実行中にシグナルが発生し、シグナルで関数を再度呼び出す場合、安全ですか?このような場合、複数のスレッドはありません。それはあなたがそれを再入にするために任意の静的/グローバルVARを使用しないことをお勧めします、または実施例3のように
kenieeバン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.