9

次のコードがあるとします。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

void guarantee(bool cond, const char *msg) {
    if (!cond) {
        fprintf(stderr, "%s", msg);
        exit(1);
    }
}

bool do_shutdown = false;   // Not volatile!
pthread_cond_t shutdown_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t shutdown_cond_mutex = PTHREAD_MUTEX_INITIALIZER;

/* Called in Thread 1. Intended behavior is to block until
trigger_shutdown() is called. */
void wait_for_shutdown_signal() {

    int res;

    res = pthread_mutex_lock(&shutdown_cond_mutex);
    guarantee(res == 0, "Could not lock shutdown cond mutex");

    while (!do_shutdown) {   // while loop guards against spurious wakeups
        res = pthread_cond_wait(&shutdown_cond, &shutdown_cond_mutex);
        guarantee(res == 0, "Could not wait for shutdown cond");
    }

    res = pthread_mutex_unlock(&shutdown_cond_mutex);
    guarantee(res == 0, "Could not unlock shutdown cond mutex");
}

/* Called in Thread 2. */
void trigger_shutdown() {

    int res;

    res = pthread_mutex_lock(&shutdown_cond_mutex);
    guarantee(res == 0, "Could not lock shutdown cond mutex");

    do_shutdown = true;

    res = pthread_cond_signal(&shutdown_cond);
    guarantee(res == 0, "Could not signal shutdown cond");

    res = pthread_mutex_unlock(&shutdown_cond_mutex);
    guarantee(res == 0, "Could not unlock shutdown cond mutex");
}

標準準拠の C/C++ コンパイラはdo_shutdown、 への呼び出し全体で の値をレジスタにキャッシュできpthread_cond_wait()ますか? そうでない場合、どの基準/条項がこれを保証していますか?

pthread_cond_wait()コンパイラは、が を変更しないことを仮説的に知ることができdo_shutdownます。これはかなりありそうにないように思えますが、それを防止する標準を私は知りません。

実際には、C/C++ コンパイラdo_shutdownは の呼び出し全体で の値をレジスタにキャッシュしますpthread_cond_wait()か?

do_shutdownコンパイラーがacrossの値をキャッシュしないことが保証されている関数呼び出しはどれですか? 関数が外部で宣言されていて、コンパイラがその定義にアクセスできない場合、コンパイラはその動作について何も仮定してはならないため、アクセスしないことを証明できないことは明らかですdo_shutdown。コンパイラが関数をインライン化し、アクセスしないことを証明できる場合、マルチスレッド設定でもdo_shutdownキャッシュできますか? do_shutdown同じコンパイル単位内のインライン化されていない関数はどうですか?

4

4 に答える 4

6

もちろん、現在の C および C++ 標準では、この件について何も述べていません。

私の知る限り、Posix はまだ並行性モデルを正式に定義することを避けています (ただし、私は時代遅れかもしれません。その場合、私の回答は以前の Posix バージョンにのみ適用されます)。したがって、それが言っていることは少し同情して読む必要があります - この領域の要件を正確に説明しているわけではありませんが、実装者は「それが何を意味するのかを知り」、スレッドを使用可能にするために何かをすることが期待されています.

標準でミューテックスが「メモリ アクセスを同期する」と述べている場合、実装では、これは、あるスレッドのロック下で行われた変更が他のスレッドのロック下で表示されることを意味すると想定する必要があります。つまり、同期操作に何らかのメモリ バリアを含める必要があり (十分ではありません)、メモリ バリアの必要な動作は、グローバルが変更できると想定する必要があるということです。

ライブラリとして実装できないスレッドは、pthreads を実際に使用できるようにするために必要ないくつかの特定の問題をカバーしていますが、執筆時点 (2004 年) の Posix 標準では明示的に述べられていません。プログラマーが「プログラムの正確さについて説得力のある推論」を行えるようにするという点で、コンパイラライター、または実装のメモリモデルを定義した人が、「使用可能」の意味に同意するかどうかが非常に重要になります。

Posix はコヒーレントなメモリ キャッシュを保証しないことに注意してください。そのため、実装がdo_somethingコード内のレジスタに逆にキャッシュしたい場合は、それを volatile とマークしたとしても、同期操作の間に CPU のローカル キャッシュをダーティにしないことを逆に選択する可能性があります。と読書do_something。そのため、ライター スレッドが独自のキャッシュを持つ別の CPU で実行されている場合でも、変更が見られない場合があります。

これが (1 つの理由) スレッドを単にライブラリとして実装できない理由です。ローカル CPU キャッシュからのみ揮発性グローバルをフェッチするというこの最適化は、シングルスレッド C 実装では有効ですが [*]、マルチスレッド コードを壊します。したがって、コンパイラはスレッドと、それらが他の言語機能にどのように影響するかを「知る」必要があります (pthreads 以外の例: キャッシュが常に一貫している Windows では、Microsoft はvolatile、マルチスレッド コードで許可する追加のセマンティクスを詳しく説明しています) 。 . 基本的に、実装が pthreads 関数を提供するのに苦労した場合、ロックが実際にメモリアクセスを同期する実行可能なメモリモデルを定義するのに手間がかかると想定する必要があります。

コンパイラが関数をインライン化し、それが do_shutdown にアクセスしないことを証明できる場合、マルチスレッド設定でも do_shutdown をキャッシュできますか? 同じコンパイル単位内のインライン化されていない関数はどうですか?

すべてはい - オブジェクトが不揮発性であり、コンパイラがこのスレッドがそれを変更していないことを証明できる場合 (名前またはエイリアス ポインターを介して)、メモリ バリアが発生しない場合は、再利用できます。過去価値。もちろん、他の実装固有の条件があり、それが停止することがあります。

[*] グローバルが何らかの「特別な」ハードウェア アドレスに配置されていないことを実装が認識している場合、そのアドレスに影響を与えるハードウェア操作の結果を確認するために、読み取りが常にキャッシュを介してメイン メモリに移動する必要があります。しかし、そのような場所にグローバルを配置したり、その場所を DMA などで特別にしたりするには、実装固有の魔法が必要です。そのような魔法がなければ、原則として実装はこれを知ることができます。

于 2010-12-18T03:47:22.550 に答える
2

手を振っている答えはすべて間違っています。辛口でごめんなさい。

道はない

コンパイラーは、pthread_cond_wait() が do_shutdown を変更しないことを仮定的に認識できます。

pthread_cond_wait違うと思うなら、証拠を示してください: MT 用に設計されていないコンパイラが を変更しないと推測できるような完全な C++ プログラムdo_shutdown

ばかげています。POSIXスレッドの知識が組み込まれpthread_ていない限り、コンパイラは関数が何をするのか理解できません。

于 2011-10-02T03:48:29.180 に答える
2

には外部リンケージがあるためdo_shutdown、コンパイラーが呼び出し全体で何が起こるかを知る方法はありません (呼び出される関数を完全に可視化できない限り)。そのため、呼び出し後に値をリロードする必要があります (揮発性かどうか - スレッド化はこれに関係ありません)。

私の知る限り、標準でこれについて直接述べられていることは何もありませんが、標準が式の動作を定義するために使用する (シングルスレッドの) 抽象マシンが、式で変数にアクセスするときに変数を読み取る必要があることを示していることを除きます。標準では、変数が再ロードされたかのように動作することが証明できる場合にのみ、変数の読み取りを最適化して取り除くことを許可しています。これは、値が関数呼び出しによって変更されていないことをコンパイラが認識できる場合にのみ発生します。

pthread_cond_wait()また、pthread ライブラリは、次 のようなさまざまな関数のメモリ バリアについて一定の保証を行っているわけではありません。

ここで、do_shutdown静的 (外部リンケージなし) で、同じモジュールで定義されたその静的変数を使用する複数のスレッドがある場合 (つまり、静的変数のアドレスが別のモジュールに渡されることはありません)、それは別の話。たとえば、そのような変数を使用する 1 つの関数があり、その関数に対して実行されている複数のスレッド インスタンスを開始したとします。その場合、標準に準拠したコンパイラの実装は、関数呼び出し間で値をキャッシュする可能性があります。これは、他に何も値を変更できないと想定できるためです (標準の抽象マシン モデルにはスレッドが含まれていません)。

したがって、その場合、メカニズムを使用して、呼び出し全体で値が再ロードされたことを確認する必要があります。ハードウェアが複雑volatileなため、正しいメモリ アクセス順序を保証するにはキーワードが適切でない場合があることに注意してください。これを保証するには、pthreads または OS によって提供される API に依存する必要があります。(ちなみに、Microsoft のコンパイラの最近のバージョンでは、volatile完全なメモリ バリアを強制するように文書化されていますが、これは標準では必要ないことを示す意見を読んだことがあります)。

于 2010-12-18T03:47:28.917 に答える
0

私自身の仕事から、そうです、コンパイラーはpthread_mutex_lock/pthread_mutex_unlock全体で値をキャッシュできると言えます。私は週末のほとんどを、一連のポインター割り当てがキャッシュされ、それらを必要とするスレッドで使用できないことによって引き起こされたコードのバグを追跡することに費やしました。簡単なテストとして、割り当てをミューテックスのロック/ロック解除でラップしましたが、スレッドはまだ適切なポインター値にアクセスできませんでした。ポインタの割り当てと関連するミューテックスロックを別の関数に移動すると、問題は修正されました。

于 2011-01-11T05:57:44.170 に答える