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

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

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

関数はスレッドセーフです もし:

関数は再入可能です もし:

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

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

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

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

ただし、次の点にも注意してください:


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

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

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

要するに、再入可能頻繁 はスレッド セーフを意味します (「スレッドを使用している場合は、その関数の再入可能バージョンを使用する」など) が、スレッド セーフは必ずしも再入可能 (またはその逆) を意味するわけではありません。スレッド セーフ、同時実行性を見ている場合 考えなければならないことです。関数を使用するためにロックと相互排除の手段を提供する必要がある場合、その関数は本質的にスレッドセーフではありません。

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

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

例:

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

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

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

「スレッドセーフ」をより適切に表現するには、同時アクセスに対して安全です。 、これは必要性をよりよく示しています。