「コードの再入可能性」と「スレッドセーフ」の概念の違いは何ですか? 以下のリンクに従って、コードの一部は、それらのいずれか、両方、またはどちらでもない可能性があります。
説明がはっきりと理解できませんでした。助けていただければ幸いです。
「コードの再入可能性」と「スレッドセーフ」の概念の違いは何ですか? 以下のリンクに従って、コードの一部は、それらのいずれか、両方、またはどちらでもない可能性があります。
説明がはっきりと理解できませんでした。助けていただければ幸いです。
再入可能コードには、単一の点に状態がありません。コード内で何かが実行されている間にコードを呼び出すことができます。コードがグローバル状態を使用する場合、1 つの呼び出しでグローバル状態が上書きされ、別の呼び出しで計算が中断される可能性があります。
スレッド セーフ コードは、競合状態やその他の同時実行性の問題がないコードです。競合状態とは、2 つのスレッドが何かを実行する順序が計算に影響を与える場合です。一般的な並行性の問題は、共有データ構造への変更が部分的に完了し、一貫性のない状態のままになる可能性がある場合です。これを回避するには、ミューテックスのセマフォなどの同時実行制御メカニズムを使用して、操作が完了するまでデータ構造にアクセスできないようにする必要があります。
たとえば、ミューテックスによって外部的に保護されていても、呼び出しの全期間にわたって状態が一貫している必要があるグローバル データ構造を保持している場合、そのコードは再入可能ではなくスレッド セーフである可能性があります。この場合、外部の粗粒度ミューテックスによって保護されたまま、同じスレッドがプロシージャへのコールバックを開始する可能性があります。コールバックが非再入可能プロシージャ内から発生した場合、呼び出しによってデータ構造が状態のままになり、呼び出し元の観点から計算が中断される可能性があります。
更新の途中で中断され、データ構造が一貫性のない状態のままになる可能性がある共有 (および共有可能) データ構造に非アトミックな変更を加えることができる場合、コードは再入可能ですがスレッドセーフではありません。 . この場合、データ構造にアクセスする別のスレッドが、半分変更されたデータ構造の影響を受け、クラッシュするか、データを破損する操作を実行する可能性があります。
その記事には次のように書かれています。
「関数は、再入可能、スレッドセーフ、両方、またはどちらでもない可能性があります。」
また、次のようにも述べています。
「再入不可の関数はスレッドセーフではありません」。
これがどのように混乱を引き起こすかがわかります。それらは、再入可能である必要がないと文書化されている標準関数も、スレッドセーフである必要がないことを意味します。これは、POSIX ライブラリ iirc に当てはまります (そして、POSIX は、ANSI/ISO ライブラリにも当てはまると宣言しています。スレッドの概念がないため、スレッドセーフの概念がない)。言い換えれば、「関数が再入不可であると言う場合、それはスレッドセーフでもないと言っている」ということです。それは論理的な必要性ではなく、単なる慣例です。
スレッドセーフな疑似コードを次に示します (ロックの反転により、コールバックがデッドロックを作成する可能性は十分にありますが、ドキュメントにはユーザーがそれを回避するのに十分な情報が含まれていると仮定しましょう) が、再入可能ではありません。グローバル カウンターをインクリメントし、コールバックを実行することになっています。
take_global_lock();
int i = get_global_counter();
do_callback(i);
set_global_counter(i+1);
release_global_lock();
コールバックがこのルーチンを再度呼び出して別のコールバックが発生した場合、両方のレベルのコールバックが同じパラメーターを取得します (API によっては問題ない場合があります) が、カウンターは 1 回だけインクリメントされます (これはほとんど確実に必要なAPIなので、禁止する必要があります)。
もちろん、ロックが再帰的であると仮定しています。ロックが非再帰的である場合、2 回目にロックを取得しても機能しないため、もちろんコードは再入可能ではありません。
「弱く再入可能」であるがスレッドセーフではない疑似コードを次に示します。
int i = get_global_counter();
do_callback(i);
set_global_counter(get_global_counter()+1);
コールバックから関数を呼び出しても問題ありませんが、異なるスレッドから関数を同時に呼び出すのは安全ではありません。また、シグナル ハンドラーから呼び出すのも安全ではありません。これは、シグナルが適切なタイミングで発生した場合、シグナル ハンドラーからの再入可能性により、同様にカウントが中断される可能性があるためです。したがって、コードは適切な定義により再入可能ではありません。
これは間違いなく完全に再入可能であるコードです(ただし、標準では再入可能と「シグナルによる割り込み不可」が区別されていると思いますが、これがどこに該当するかはわかりません)が、それでもスレッドセーフではありません:
int i = get_global_counter();
do_callback(i);
disable_signals(); // and any other kind of interrupts on your system
set_global_counter(get_global_counter()+1);
restore_signal_state();
シングル スレッド アプリでは、OS が無効にする必要があるすべての無効化をサポートしていると仮定すると、これで問題ありません。臨界点でのリエントラントの発生を防ぎます。シグナルが無効になっている方法によっては、シグナル ハンドラーから呼び出しても安全な場合がありますが、この特定の例では、コールバックに渡されるパラメーターが個別の呼び出しで同じであるという問題がまだあります。ただし、マルチスレッドではまだうまくいかない可能性があります。
実際には、非スレッドセーフは再入不可を意味することがよくあります。これは、(非公式に) スレッドがスケジューラによって中断されたために問題が発生する可能性があり、別のスレッドから再度呼び出された関数も問題が発生する可能性があるためです。スレッドはシグナルによって中断され、関数はシグナル ハンドラから再度呼び出されます。ただし、シグナルを防止する (それらを無効にする) ための "修正" は、同時実行性 (通常はロック) を防止するための "修正" とは異なります。これはせいぜい経験則です。
ここではグローバルを暗示していますが、関数がパラメーターとしてカウンターとロックへのポインターを受け取る場合、まったく同じ考慮事項が適用されることに注意してください。まったく呼び出されたときではなく、同じパラメーターで呼び出されたときに、さまざまなケースがスレッドセーフまたは再入不可になるだけです。