リエントラント

例

次の例の swap() 関数は、リエントラントではない（同時にスレッドセーフでもない）。したがってこれを割り込みサービスルーチン isr() で使用すべきでない。
int t;void swap(int *x, int *y){
  t = *x;
  *x = *y;
  // ここでハード割り込みが起きて isr() が呼び出される可能性がある。
  *y = t;}void isr(){
  int x = 1, y = 2;
  swap(&x, &y);}
swap() は t をスレッド局所記憶にすることでスレッドセーフにできる。しかしそのようにしてもリエントラントにはならず、swap() 実行中に同じスレッドのコンテキストで isr() が呼び出されれば問題を生じる可能性が残っている。
次の工夫を加えたswap関数では、実行完了時のグローバルなデータを注意深く一貫性を保つようにしており、完全にリエントラントである。ただし、実行途中のグローバルなデータの一貫性は保証されていないのでスレッドセーフではない。また、int型変数の読み出しおよび書き込みが不可分操作（アトミック）である（読み出し処理および書き込み処理はそれぞれ1命令のみで実行され、処理の最中に割り込みが発生しない）ことが前提である。
int t;void swap(int *x, int *y){
  int s;

  s = t; // グローバル変数をセーブ
  t = *x;
  *x = *y;
  // ここでハード割り込みが起きて isr() が呼び出される可能性がある。
  *y = t;
  t = s; // グローバル変数をリストア}void isr(){
  int x = 1, y = 2;
  swap(&x, &y);}
次のswap関数はリエントラントかつスレッドセーフである。
void swap(int *x, int *y){
  int t;
  t = *x;
  *x = *y;
  // ここでハード割り込みが起きて isr() が呼び出される可能性がある。
  *y = t;}void isr(){
  int x = 1, y = 2;
  swap(&x, &y);}

背景

リエントラント性と冪等性は同義ではない。冪等な関数は、何度呼び出したとしても1度だけ呼び出したかのように全く同じ出力を生成する。一般化すれば、共有データを使わず、入力データに基づいて出力データを生成する関数である（ただし、入力や出力はオプション）。共有データはいつでも誰でもアクセスできる。データを誰かが更新し、誰も更新を把握していない場合、そのデータが以前と比べて変化したのかどうかさえ誰にもわからない。冪等性はリエントラント性を包含するが、逆は必ずしも真ではない。
データにはスコープという属性がある。グローバルなデータはあらゆる関数のスコープの範囲外にあり、寿命は不定である。一方局所的なデータは関数が呼び出されるたびに生成され、関数から抜けるときに破棄される。
局所的データはルーチン間で共有されず、再入時にも共有されない。したがってリエントラント性を阻害しない。グローバルなデータは任意の関数間で共有でき、あるいは再入時にも共有される。したがってリエントラント性を阻害する。
リエントラント性はスレッドセーフの概念とは異なるが、密接に関連している。関数がスレッドセーフであっても、リエントラントでないことがある。例えば関数全体をミューテックスで囲むと、マルチスレッド環境では問題を防げるが、割り込みサービスルーチンで使用すると再入時にミューテックスの解放を待ち続けることになる。混乱を避ける鍵は、リエントラントが1つのスレッド実行でも問題になるという点である。リエントラント性はマルチタスクOSの存在する前からの概念である。

リエントラントな割り込みハンドラ

リエントラントな割り込みハンドラは、割り込み処理中に早期に割り込み可能にする割り込みハンドラである。それによって割り込みレイテンシが低減される。一般に割り込みサービスルーチンをプログラミングする際、割り込みハンドラ内でなるべく早期に割り込み可能な状態にすることが推奨される。それによって割り込みを拾い損なうのを防ぎやすくなる。

さらなる例

以下のコードにある関数 f も g もリエントラントではない。
int g_var = 1;int f(){
  g_var = g_var + 2;
  return g_var;}int g(){
  return f() + 2;}
上記のコードで、f はグローバル変数 g_var に依存している。したがって、2つのプロセス（スレッド）が f を実行すると、g_var に同時並行的にアクセスし、結果はタイミングに依存することになる。したがって、f はリエントラントではない。その f を呼び出している g もリエントラントではない。
これらを若干変更したリエントラントである版を以下に示す:
int f(int i){
  return i + 2;}int g(int i){
  return f(i) + 2;}
新しい版では、グローバル変数 g_var は使われていない。引数を渡して、それに基づいて処理を行って結果を返す。共有される可能性のあるオブジェクトにはアクセスしないようになっている。その代わり、呼出し側が前回の戻り値を引数として渡してやらなければならない。このように、リエントラントなサブルーチンでは、必要な静的データは呼出し側が管理しなければならない。
次のPthreadsを使ったC言語のコードでは、関数functionはスレッドセーフだが、リエントラント（非同期シグナル安全）ではない。Pthreadsのミューテックス関数がリエントラント（非同期シグナル安全）であることは保証されないからである。
void function(pthread_mutex_t mutex){
  pthread_mutex_lock(mutex);
  /* ... */
  /* 何らかの処理 */
  /* ... */
  pthread_mutex_unlock(mutex);}
この function は複数のスレッドから呼び出されても全く問題はない。しかし、pthread_mutex_init()によるミューテックスの初期化時にPTHREAD_MUTEX_NORMALを設定した属性を使用していて、リエントラントな割り込みハンドラがこの関数を呼び出す場合、2つ目の割り込みがこの関数実行中に発生すると、二度目の呼び出しはミューテックスを獲得できず、永久に停止（デッドロック）する。割り込みサービスでは他の割り込みをディセーブルするので、システム全体がハングアップすることになる。
デッドロックを回避するには、ミューテックス初期化時に、同一スレッドによる複数回のロックを許可するPTHREAD_MUTEX_RECURSIVEを設定した属性を使用する必要がある。ただし、PTHREAD_MUTEX_RECURSIVEを設定したからといって、pthread_mutex_lock()およびpthread_mutex_unlock()が非同期シグナル安全になるとは限らない。なお、ミューテックス初期化時にPTHREAD_MUTEX_INITIALIZERを使用すると、PTHREAD_MUTEX_DEFAULTを設定した既定の属性が使用されることになり、デッドロックに関しては未定義動作となる。
一方、Microsoft WindowsのEnterCriticalSection()関数は、同一スレッドからの複数回呼び出しはブロッキングなしで実行される。ただし非同期シグナル安全性や再入可能性に関する言及や保証はない。