237

ほとんど 場合 リエントラントの定義はウィキペディアから引用されています。

コンピュータプログラムまたはルーチンは 、前の呼び出しが完了する前に安全に再度呼び出すことができる場合(つまり、同時に安全に実行できる場合)、再入可能であると説明されます。再入可能であるためには、コンピュータプログラムまたはルーチン:

  1. 静的(またはグローバル)非定数データを保持してはなりません。
  2. アドレスを静的(またはグローバル)非定数データに返さないでください。
  3. 呼び出し元から提供されたデータに対してのみ機能する必要があります。
  4. シングルトンリソースへのロックに依存してはなりません。
  5. 独自のコードを変更してはなりません(独自のスレッドストレージで実行する場合を除く)
  6. 再入可能でないコンピュータプログラムまたはルーチンを呼び出さないでください。

安全に定義する方法は?

プログラムを同時に安全に実行できる場合、それは常に再入可能であることを意味しますか?

コードの再入可能機能をチェックする際に留意する必要がある、言及された6つのポイント間の共通のスレッドは正確には何ですか?

また、

  1. すべての再帰関数はリエントラントですか?
  2. すべてのスレッドセーフ機能は再入可能ですか?
  3. すべての再帰的でスレッドセーフな関数は再入可能ですか?

この質問を書いているときに、1つのことが頭に浮かびます。リエントラントスレッドセーフなどの用語は絶対的なものですか。つまり、具体的な定義が固定されているのでしょうか。そうでない場合、この質問はあまり意味がありません。

4

8 に答える 8

224
于 2010-05-09T21:37:03.467 に答える
23

「安全に」とは、常識が示すとおりに定義されます。つまり、「他のことに干渉することなく、そのことを正しく行う」ことを意味します。あなたが引用する6つのポイントは、それを達成するための要件を非常に明確に表しています。

あなたの3つの質問に対する答えは3×「いいえ」です。


すべての再帰関数はリエントラントですか?

いいえ!

たとえば、再帰関数の2つの同時呼び出しは、同じグローバル/静的データにアクセスする場合、互いに簡単に失敗する可能性があります。


すべてのスレッドセーフ機能は再入可能ですか?

いいえ!

関数は、同時に呼び出されても誤動作しない場合、スレッドセーフです。ただし、これは、たとえばミューテックスを使用して、最初の呼び出しが終了するまで2番目の呼び出しの実行をブロックすることで実現できるため、一度に1つの呼び出しのみが機能します。再入可能とは、他の呼び出しに干渉することなく同時に実行することを意味します。


すべての再帰的でスレッドセーフな関数は再入可能ですか?

いいえ!

上記を参照。

于 2010-05-09T20:21:19.837 に答える
14

共通のスレッド:

ルーチンが中断されている間に呼び出された場合の動作は明確に定義されていますか?

このような関数がある場合:

int add( int a , int b ) {
  return a + b;
}

その場合、外部状態に依存しません。動作は明確に定義されています。

このような関数がある場合:

int add_to_global( int a ) {
  return gValue += a;
}

結果は、複数のスレッドで明確に定義されていません。タイミングを間違えると情報が失われる可能性があります。

リエントラント関数の最も単純な形式は、渡された引数と定数値のみを操作するものです。それ以外のものは特別な処理が必要であるか、多くの場合、再入可能ではありません。そしてもちろん、引数は可変グローバルを参照してはなりません。

于 2010-05-09T20:23:28.367 に答える
7

ここで、前のコメントについて詳しく説明する必要があります。@paercebalの答えは正しくありません。サンプルコードでは、パラメータであるはずのミューテックスが実際に渡されていないことに誰も気づいていませんでしたか?

私は結論に異議を唱えます、私は主張します:並行性の存在下で関数が安全であるためには、それは再入可能でなければなりません。したがって、並行セーフ(通常はスレッドセーフと記述されている)は、再入可能を意味します。

スレッドセーフでもリエントラントでも、引数については何も言えません。関数の同時実行について話しているのですが、不適切なパラメーターが使用されていると、それでも安全ではない可能性があります。

たとえば、memcpy()はスレッドセーフであり、(通常は)再入可能です。明らかに、2つの異なるスレッドから同じターゲットへのポインターを使用して呼び出された場合、期待どおりに機能しません。これがSGI定義のポイントであり、同じデータ構造へのアクセスがクライアントによって同期されるようにクライアントに責任を負わせます。

一般に、スレッドセーフな操作にパラメータを含めることは意味がないことを理解することが重要です。データベースプログラミングを行ったことがある場合は、理解できます。「アトミック」であり、ミューテックスまたはその他の手法によって保護される可能性のある概念は、必然的にユーザーの概念です。データベースでトランザクションを処理するには、中断のない複数の変更が必要になる場合があります。同期を保つ必要があるのはクライアントプログラマーだけですが、誰が言うことができますか?

重要なのは、「破損」がシリアル化されていない書き込みでコンピュータのメモリを台無しにする必要がないということです。個々の操作がすべてシリアル化されている場合でも、破損が発生する可能性があります。したがって、関数がスレッドセーフであるか、再入可能であるかを尋ねる場合、質問はすべての適切に分離された引数を意味します。結合された引数を使用することは反例を構成しません。

そこには多くのプログラミングシステムがあります。Ocamlはその1つであり、Pythonも同様に、再入可能でないコードがたくさん含まれていますが、グローバルロックを使用してスレッドアクセスをインターリーブしていると思います。これらのシステムは再入可能ではなく、スレッドセーフでも並行セーフでもありません。グローバルな同時実行を防ぐという理由だけで安全に動作します。

良い例はmallocです。再入可能ではなく、スレッドセーフでもありません。これは、グローバルリソース(ヒープ)にアクセスする必要があるためです。ロックを使用しても安全にはなりません。再入可能ではありません。mallocへのインターフェースが適切に設計されていれば、再入可能でスレッドセーフにすることが可能です。

malloc(heap*, size_t);

これで、単一ヒープへの共有アクセスをシリアル化する責任がクライアントに移るので、安全になります。特に、個別のヒープオブジェクトがある場合は、作業は必要ありません。共通ヒープが使用されている場合、クライアントはアクセスをシリアル化する必要があります。関数内でロックを使用するだけでは不十分です。mallocがヒープをロックしていると考えてください*。その後、シグナルが届き、同じポインターでmallocを呼び出します。デッドロック:シグナルは続行できず、クライアントも続行できません。中断されます。

一般的に言って、ロックはスレッドセーフにはなりません..クライアントが所有するリソースを不適切に管理しようとすることで、実際には安全性を破壊します。ロックはオブジェクトの製造元が行う必要があります。これは、作成されるオブジェクトの数とその使用方法を知っている唯一のコードです。

于 2010-12-10T05:36:26.493 に答える
4

リストされたポイントの中での「共通のスレッド」(しゃれを意図した!?)は、関数が同じ関数への再帰的または同時呼び出しの動作に影響を与えるようなことをしてはならないということです。

たとえば、静的データはすべてのスレッドによって所有されているため、問題になります。1回の呼び出しで静的変数を変更すると、すべてのスレッドが変更されたデータを使用するため、動作に影響を及ぼします。複数のスレッドがありますが、コードのコピーは1つしかないため、コードの自己変更(めったに発生せず、場合によっては防止されます)が問題になります。コードも重要な静的データです。

基本的に再入可能であるためには、各スレッドが唯一のユーザーであるかのように関数を使用できる必要があります。これは、あるスレッドが別のスレッドの動作に非決定的な方法で影響を与える可能性がある場合には当てはまりません。主に、これには、関数が機能する個別のデータまたは定数データのいずれかを持つ各スレッドが含まれます。

とはいえ、ポイント(1)は必ずしも正しいとは限りません。たとえば、合法的に、設計上、静的変数を使用して再帰カウントを保持し、過度の再帰を防止したり、アルゴリズムのプロファイルを作成したりできます。

スレッドセーフ関数は再入可能である必要はありません。特にロックによるリエントラントを防止することでスレッドセーフを実現できる可能性があり、ポイント(6)はそのような機能はリエントラントではないと述べています。ポイント(6)に関しては、ロックするスレッドセーフ関数を呼び出す関数は、再帰での使用には安全ではないため(デッドロックになります)、したがって、同時実行には安全である可能性がありますが、リエントラントとは言えません。複数のスレッドがそのような関数で同時に(ロックされた領域ではなく)プログラムカウンターを持つことができるという意味で、それでも再入可能です。これは、スレッドセーフとリエンタレンシーを区別するのに役立つ可能性があります(または混乱を助長する可能性があります)。

于 2010-05-09T20:47:46.190 に答える
3

「また」の質問に対する答えは、「いいえ」、「いいえ」、「いいえ」です。関数が再帰的および/またはスレッドセーフであるという理由だけで、関数を再入可能にすることはありません。

これらのタイプの関数はそれぞれ、引用するすべての点で失敗する可能性があります。(私はポイント5を100%確信しているわけではありませんが)。

于 2010-05-09T20:19:40.947 に答える
1

「スレッドセーフ」および「再入可能」という用語は、それらの定義が言っていることだけを意味します。この文脈での「安全」とは、その下に引用する定義が言うことだけを意味します。

ここでの「安全」とは、特定のコンテキストで特定の関数を呼び出してもアプリケーションが完全に機能しなくなるという広い意味での安全を意味するものではありません。全体として、関数はマルチスレッドアプリケーションで確実に望ましい効果を生み出す可能性がありますが、定義によれば、再入可能またはスレッドセーフとしての資格はありません。反対に、マルチスレッドアプリケーションでさまざまな望ましくない、予期しない、および/または予測できない効果を生成する方法で、リエントラント関数を呼び出すことができます。

再帰関数は何でもかまいません。リエントラントはスレッドセーフよりも強力な定義を持っているため、番号付きの質問に対する答えはすべてノーです。

リエントラントの定義を読むと、それを、変更するために呼び出すもの以外は何も変更しない関数を意味するものとして要約することができます。しかし、要約だけに頼るべきではありません。

マルチスレッドプログラミングは、一般的なケースでは非常に困難です。コードの再入可能部分のどの部分を知ることは、この課題の一部にすぎません。スレッドセーフは相加的ではありません。再入可能関数をつなぎ合わせようとするのではなく、全体的なスレッドセーフな デザインパターンを使用し、このパターンを使用して、プログラム内のすべてのスレッドと共有リソースの使用をガイドすることをお勧めします。

于 2010-05-10T01:19:13.650 に答える
1
  • 非リエントラント関数は、関数によって維持される静的コンテキストが存在することを意味します。初めて入るとき、あなたのために新しいコンテキストを作成します。次に入力すると、トークン分析に便利なように、そのためのパラメータをそれ以上送信しません。例:cのstrtok。コンテキストをクリアしていない場合は、エラーが発生している可能性があります。
/* strtok example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] ="- This, a sample string.";
  char * pch;
  printf ("Splitting string \"%s\" into tokens:\n",str);
  pch = strtok (str," ,.-");
  while (pch != NULL)
  {
    printf ("%s\n",pch);
    pch = strtok (NULL, " ,.-");
  }
  return 0;
}
  • 非リエントラントとは対照的に、リエントラント関数は、いつでも関数を呼び出すと、副作用なしに同じ結果が得られることを意味します。コンテキストがないからです。
  • スレッドセーフの観点からは、現在のプロセスで、現在の時刻にパブリック変数の変更が1つしかないことを意味します。したがって、ロックガードを追加して、パブリックフィールドを一度に1回だけ変更できるようにする必要があります。
  • したがって、スレッドセーフとリエントラントは、異なるビューでは2つの異なるものです。リエントラント関数の安全性は、次回コンテキスト分析を行う前にコンテキストをクリアする必要があることを示しています。スレッドセーフは、パブリックフィールドオーダーを訪問し続ける必要があると言っています。
于 2021-02-08T02:46:18.830 に答える