web-dev-qa-db-ja.com

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

最近、タイトルを "malloc thread safe?" として質問し、その中に "malloc re-entrant?"と尋ねました。

すべてのリエントラントはスレッドセーフであるという印象を受けました。

この仮定は間違っていますか?

82
Alphaneo

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

一部の関数には、「進行中の作業」を保存する場所が必要です。再入可能な関数を使用すると、グローバルではなく、スレッド自身のストレージ内でこのポインターを指定できます。このストレージは呼び出し関数専用であるため、中断してre-entered(リエントラント)することができます。これが機能するために、関数implementsは必要ありません。多くの場合、スレッドセーフと見なされます。ただし、これは定義によって保証されるものではありません。

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

要するに、リエントラントoftenはスレッドセーフを意味します(「スレッドを使用している場合はその関数のリエントラントバージョンを使用する」など)が、スレッドセーフは常に再入可能(またはその逆)を意味するわけではありません。スレッドセーフを見るとき、concurrencyを考える必要があります。関数を使用するためにロックおよび相互排除の手段を提供する必要がある場合、その関数は本質的にスレッドセーフではありません。

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

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

すなわち:

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

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

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

「スレッドセーフ」を設定するより良い方法は、並行アクセスに対して安全ですです。

40
Tim Post

TL; DR:関数は、再入可能、スレッドセーフ、両方、またはどちらでもない場合があります。

thread-safety および reentrancy のウィキペディアの記事は読む価値があります。以下にいくつかの引用を示します。

次の場合、関数はthread-safeです

同時に複数のスレッドによる安全な実行を保証する方法で共有データ構造を操作するだけです。

次の場合、関数はreentrantです。

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

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

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

(Wikipediaの記事から少し変更)

例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;
}
67
MiniQuark

定義に依存します。たとえば、 Qtは 次を使用します。

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

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

したがって、thread-safe関数は常にリエントラントですが、reentrant関数は常にスレッドセーフではありません。

拡張により、各スレッドがクラスの異なるインスタンスを使用する限り、そのメンバー関数が複数のスレッドから安全に呼び出される場合、クラスはreentrantと呼ばれます。すべてのスレッドがクラスの同じインスタンスを使用している場合でも、そのメンバー関数を複数のスレッドから安全に呼び出すことができる場合、クラスはthread-safeです。

しかし、彼らも注意します:

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

55
Georg Schölly