4

時々キャンセルされる複数のスレッドを実行するアプリケーションがあります。これらのスレッドは、リソース (ソケット) に内部的にアクセスする別のオブジェクトを呼び出す場合があります。リソースが同時にアクセスされるのを防ぐために、実行時に何らかの順序を取得するためのクリティカル セクションがあります。

ここで、スレッドをキャンセルすると、スレッドがクリティカル セクションによってブロックされているコード内にあることが (時々) 発生します。クリティカル セクションはオブジェクトを使用してロックされており、スレッドのキャンセル時にこのオブジェクトが破棄され、その結果ロックが解除されることを期待していました。ただし、これは当てはまらないようで、スレッドの破棄時にこのリソース オブジェクトは永久にロックされます。

リソース オブジェクトを変更することはおそらくオプションではありません (サード パーティが提供)。さらに、並列で使用できないリソースへの同時アクセスを防止することは理にかなっています。

セクションがロック/ロック解除されているときに pthread_setcancelstate を使用してスレッドがキャンセルされないように実験しましたが、これは少し汚れているように感じ、他の状況 (たとえば、買収されたミューテックスなど) の最終的な解決策にはなりません。

好ましい解決策は、pthread_cancel を使用せず、代わりにスレッドにフラグを設定することであり、準備ができたときに (クリーンな方法で) スレッド自体をキャンセルすることを私は知っています。しかし、私はスレッドをできるだけ早くキャンセルしたいので、それを行う他のオプションがあるかどうか疑問に思っていました.

4

4 に答える 4

2

アプリケーション (前述のフラグ) の助けを借りずにスレッドをキャンセルすることは、悪い考えです。ただグーグル

実際、キャンセルは非常に難しいため、最新の C++0x ドラフトでは省略されています。http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2497.htmlを検索できますが、キャンセルについての言及はまったく見つかりません。提案されたスレッド クラスの定義は次のとおりです (そこには cancel はありません)。

class thread
{
public:
    // types:
    class id;
    typedef implementation-defined native_handle_type; // See [thread.native]

    // construct/copy/destroy:
    thread();
    template <class F> explicit thread(F f);
    template <class F, class ...Args> thread(F&& f, Args&&... args);
    ~thread();
    thread(const thread&) = delete;
    thread(thread&&);
    thread& operator=(const thread&) = delete;
    thread& operator=(thread&&);

    // members:
    void swap(thread&&);
    bool joinable() const;
    void join();
    void detach();
    id get_id() const;
    native_handle_type native_handle(); // See [thread.native]

    // static members:
    static unsigned hardware_concurrency();
};
于 2009-04-30T00:11:12.933 に答える
2

pthread_cleanup_push() を使用して、キャンセル クリーンアップ ハンドラをスレッド キャンセル クリーンアップ スタックにプッシュできます。このハンドラーは、クリティカル セクションのロックを解除する役割を果たします。

クリティカル セクションを離れたら、 pthread_cleanup_pop(0) を呼び出して削除する必要があります。

すなわち

CRIITICAL_SECTION g_section;

void clean_crit_sec( void * )
{
    LeaveCriticalSection( &g_section )
}

void *thrfunc( void * )
{
    EnterCriticalSection( &g_section );
    pthread_cleanup_push( clean_crit_sec, NULL );

    // Do something that may be cancellable

    LeaveCriticalSection( &g_section );
    pthread_cleanup_pop( 0 );
}

これにより、クリティカル セクションのロックが解除された小さな競合状態が残りますが、Leave.. と cleanup_pop の間でスレッドがキャンセルされた場合、クリーンアップ ハンドラーは引き続き実行される可能性があります。

pthread_cleanup_pop を 1 で呼び出すと、クリーンアップ コードが実行され、クリティカル セクションを自分で解放することはありません。すなわち

CRIITICAL_SECTION g_section;

void clean_crit_sec( void * )
{
    LeaveCriticalSection( &g_section )
}

void *thrfunc( void * )
{
    EnterCriticalSection( &g_section );
    pthread_cleanup_push( clean_crit_sec, NULL );

    // Do something that may be cancellable

    pthread_cleanup_pop( 1 );  // this will pop the handler and execute it.
}
于 2010-02-23T11:11:08.243 に答える
1

明確に定義された制御メソッド(つまり、フラグ)を使用せずにスレッドを中止するという考えは非常に邪悪なので、単にそれを行うべきではありません。

これを行う以外に選択肢がないサードパーティのコードがある場合は、プロセス内の恐ろしいコードを抽象化し、代わりにプロセスと対話して、そのような各コンポーネントを適切に分離することをお勧めします。

Windowsは複数のプロセスを実行するのが苦手なので、このような設計はWindowsではさらに悪くなりますが、Linuxではそれほど悪い考えではありません。

もちろん、スレッド化されたモジュールの適切な設計を行うことはさらに良いでしょう...

(個人的には、スレッドをまったく使用せず、常にプロセスまたは非ブロッキング設計を使用することを好みます)

于 2009-05-07T11:43:51.227 に答える
0

クリティカルセクションを制御するロックが直接あなたにさらされていない場合、あなたができることはあまりありません。スレッドをキャンセルすると、スレッドのすべてのクリーンアップハンドラーが通常の逆の順序で実行されますが、もちろん、これらのハンドラーは、アクセスできるミューテックスのみを解放できます。したがって、サードパーティコンポーネントへのアクセス中にキャンセルを無効にする以上のことはできません。

最善の解決策は、フラグpthread_cancel機能の両方を使用することだと思います。サードパーティコンポーネントを入力するときは、キャンセル処理(PTHREAD_CANCEL_DISABLE)を無効にします。終了したら、再度有効にします。再度有効にした後、フラグを確認します。

/* In thread which you want to be able to be canceled: */
int oldstate;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
... call 3rd party component ...
pthread setcancelstate(oldstate, NULL);
if (cancelled_flag) pthread_exit(PTHREAD_CANCELED);

/* In the thread canceling the other one. Note the order of operations
   to avoid race condition: */
cancelled_flag = true;
pthread_cancel(thread_id);
于 2009-03-20T18:01:41.967 に答える