マルチスレッドとC++の例外安全性の間の緊張関係は何ですか?従うべき良いガイドラインはありますか?キャッチされない例外が原因でスレッドが終了しますか?
9 に答える
C++0x には、スレッド間で例外を転送するための言語サポートが用意されているため、ワーカー スレッドが例外をスローしたときに、生成スレッドが例外をキャッチまたは再スローできます。
提案から:
namespace std {
typedef unspecified exception_ptr;
exception_ptr current_exception();
void rethrow_exception( exception_ptr p );
template< class E > exception_ptr copy_exception( E e );
}
C++ 標準ではマルチスレッドについて言及されていないと思います。マルチスレッドはプラットフォーム固有の機能です。
キャッチされない例外全般について C++ 標準が何を言っているのか正確にはわかりませんが、このページによると、何が起こるかはプラットフォーム定義であり、コンパイラのドキュメントで確認する必要があります。
私が g++ 4.0.1 (具体的には i686-apple-darwin8-g++-4.0.1) で行った簡単なテストでは、その結果がterminate()
呼び出され、プログラム全体が強制終了されます。私が使用したコードは次のとおりです。
#include <stdio.h>
#include <pthread.h>
void *threadproc(void *x)
{
throw 0;
return NULL;
}
int main(int argc, char **argv)
{
pthread_t t;
pthread_create(&t, NULL, threadproc, NULL);
void *ret;
pthread_join(t, &ret);
printf("ret = 0x%08x\n", ret);
return 0;
}
でコンパイルg++ threadtest.cc -lpthread -o threadtest
。出力は次のとおりです。
terminate called after throwing an instance of 'int'
キャッチされない例外が呼び出さterminate()
れ、次にterminate_handler
(プログラムによって設定できる)が呼び出されます。デフォルトでは、terminate_handler
呼び出しabort()
。
デフォルトを上書きしてもterminate_handler
、標準では、提供するルーチンは「呼び出し元に戻らずにプログラムの実行を終了する」とされています(ISO 14882-200318.6.1.3)。
したがって、要約すると、キャッチされない例外は、スレッドだけでなくプログラムを終了します。
Adam Rosenfieldが言うように、スレッドセーフに関する限り、これはプラットフォーム固有のものであり、標準では対応されていません。
他の人が議論したように、並行性 (特にスレッド セーフ) はアーキテクチャ上の問題であり、システムとアプリケーションの設計方法に影響します。
しかし、例外安全とスレッド安全の間の緊張関係について、あなたの質問に答えたいと思います。
クラス レベルでは、スレッド セーフにはインターフェイスの変更が必要です。例外安全性と同じように。たとえば、クラスが内部変数への参照を返すのは通例です。たとえば、次のようになります。
class Foo {
public:
void set_value(std::string const & s);
std::string const & value() const;
};
Foo が複数のスレッドで共有されている場合、問題が発生します。もちろん、Foo にアクセスするためにミューテックスやその他のロックを設定することもできます。しかしすぐに、すべての C++ プログラマーが Foo を "ThreadSafeFoo" にラップしたくなるでしょう。私の主張は、Foo のインターフェースを次のように変更する必要があるということです。
class Foo {
public:
void set_value(std::string const & s);
std::string value() const;
};
はい、より高価ですが、Foo 内のロックを使用してスレッドセーフにすることができます。IMnsHO これにより、スレッドセーフと例外セーフの間にある程度の緊張が生じます。または、少なくとも、共有リソースとして使用される各クラスを両方のライトの下で調べる必要があるため、さらに分析を実行する必要があります。
これが、Erlang が存在する最大の理由の 1 つです。
慣習が何であるかはわかりませんが、できるだけErlangのようにしてください。ヒープ オブジェクトを不変にし、ある種のメッセージ パッシング プロトコルを設定して、スレッド間で通信します。ロックは避けてください。メッセージの受け渡しが例外セーフであることを確認してください。ステートフルなものをできるだけ多くスタックに保持します。
1 つの典型的な例 (最初に見た場所を思い出せません) は std ライブラリにあります。
キューから何かをポップする方法は次のとおりです。
T t;
t = q.front(); // may throw
q.pop();
このインターフェイスは、次のものに比べてやや鈍いです。
T t = q.pop();
しかし、T コピー代入がスローされる可能性があるため、実行されます。ポップが発生した後にコピーがスローされた場合、その要素はキューから失われ、回復することはできません。ただし、要素がポップされる前にコピーが行われるため、try/catch ブロック内の front() からのコピーの周りに任意の処理を配置できます。
欠点は、2 つの手順が必要なため、std::queue のインターフェイスでスレッド セーフなキューを実装できないことです。例外の安全性 (スローする可能性のあるステップを分離すること) には適していますが、マルチスレッドには適していません。
例外の安全性における主な救世主は、ポインター操作が非スローであることです。同様に、ポインター操作はほとんどのプラットフォームでアトミックにすることができるため、マルチスレッド コードの救世主となることがよくあります。ケーキを持って食べることもできますが、それは本当に難しいです。
私が気づいた2つの問題があります:
Linux 上の g++ では、スレッドの強制終了 (pthread_cancel) は、「不明な」例外をスローすることによって行われます。一方では、これにより、スレッドが強制終了されたときに適切にクリーンアップできます。一方、その例外をキャッチして再スローしない場合、コードは abort() で終了します。したがって、あなたまたはあなたが使用するライブラリのいずれかが kill スレッドを使用している場合、
キャッチ(...)
それなし
throw;
スレッド化されたコードで。Web でのこの動作への参照は次のとおりです。
- スレッド間で例外を転送する必要がある場合があります。これは簡単なことではありません - 適切な解決策がプロセス間で使用するマーシャリング/デマーシャリングのようなものである場合、何らかのハックを行うことになりました。
最も重要なことは、他のスレッドからのキャッチされていない例外がユーザーに表示されたり、メインスレッドでスローされたりしないことを覚えておくことです。したがって、try/catch ブロックを使用して、メイン スレッドとは異なるスレッドで実行する必要があるすべてのコードをワープする必要があります。
例外がキャッチされないままにしておくことはお勧めしません。トップレベルのスレッド関数を、プログラムをより適切に (または少なくとも詳細に) シャットダウンできる catch-all ハンドラーでラップします。