10

次の例を考えてみましょう。目標は、値を「計算」するスレッドと、計算された値を消費して使用するスレッドの 2 つのスレッドを使用することです (これを単純化しようとしました)。計算スレッドは、条件変数を使用して値が計算され、準備ができていることを他のスレッドに通知します。その後、待機中のスレッドが値を消費します。

// Hopefully this is free from errors, if not, please point them out so I can fix
// them and we can focus on the main question
#include <pthread.h>
#include <stdio.h>

// The data passed to each thread. These could just be global variables.
typedef struct ThreadData
{
  pthread_mutex_t mutex;
  pthread_cond_t cond;
  int spaceHit;
} ThreadData;

// The "computing" thread... just asks you to press space and checks if you did or not
void* getValue(void* td)
{
  ThreadData* data = td;

  pthread_mutex_lock(&data->mutex);

  printf("Please hit space and press enter\n");
  data->spaceHit = getchar() == ' ';
  pthread_cond_signal(&data->cond);

  pthread_mutex_unlock(&data->mutex);

  return NULL;
}

// The "consuming" thread... waits for the value to be set and then uses it
void* watchValue(void* td)
{
  ThreadData* data = td;

  pthread_mutex_lock(&data->mutex);
  if (!data->spaceHit)
      pthread_cond_wait(&data->cond, &data->mutex);
  pthread_mutex_unlock(&data->mutex);

  if (data->spaceHit)
      printf("You hit space!\n");
  else
    printf("You did NOT hit space!\n");

  return NULL;
}

int main()
{
  // Boring main function. Just initializes things and starts the two threads.
  pthread_t threads[2];
  pthread_attr_t attr;
  ThreadData data;
  data.spaceHit = 0;

  pthread_mutex_init(&data.mutex, NULL);
  pthread_cond_init(&data.cond, NULL);

  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
  pthread_create(&threads[0], &attr, watchValue, &data);
  pthread_create(&threads[1], &attr, getValue, &data);

  pthread_join(threads[0], NULL);
  pthread_join(threads[1], NULL);

  pthread_attr_destroy(&attr);
  pthread_mutex_destroy(&data.mutex);
  pthread_cond_destroy(&data.cond);

  return 0;
}

私の主な質問は、コンパイラによって行われる潜在的な最適化に関係しています。コンパイラは、トリッキーな最適化を実行して、次のようなプログラム フローを「最適化」することを許可されていますか?

void* watchValue(void* td)
{
  ThreadData* data = td;

  pthread_mutex_lock(&data->mutex);
  if (!data->spaceHit) // Here, it might remember the result of data->spaceHit
      pthread_cond_wait(&data->cond, &data->mutex);
  pthread_mutex_unlock(&data->mutex);

  if (remember the old result of data->spaceHit without re-getting it)
      printf("You hit space!\n");
  else
    printf("You did NOT hit space!\n");
  // The above if statement now may not execute correctly because it didn't
  // re-get the value of data->spaceHit, but "remembered" the old result
  // from the if statement a few lines above

  return NULL;
}

私は、コンパイラの静的分析がdata->spaceHit2 つのステートメント間で変化しないと判断し、新しい値を再取得する代わりにif古い値を使用することを正当化する可能性があることに少し偏執的です。data->spaceHitこのコードが安全かどうかを知るには、スレッド化とコンパイラの最適化について十分に知りません。それは...ですか?


注: これは C で記述し、C および C++ としてタグ付けしました。これを C++ ライブラリで使用していますが、C スレッド API (pthreads および Win32 スレッド) を使用しており、C++ ライブラリのこの部分に C を埋め込むオプションがあるため、これを C と C++ の両方としてタグ付けしました。 .

4

3 に答える 3

9

いいえ、コンパイラはまたはの呼び出し全体での値をキャッシュすることはできません。これらは両方とも、「他のスレッドに対してメモリを同期する関数」として具体的に呼び出され、コンパイラのバリアとして機能する必要があります。data->spaceHitpthread_cond_wait()pthread_mutex_unlock()

コンパイラが準拠する pthreads 実装の一部になるには、指定した場合にその最適化を実行してはなりません。

于 2013-05-29T03:39:02.610 に答える
-1

(後で編集:その後の回答がこのクエリに対するより良い回答を提供したようです。この回答は、質問にも回答しないアプローチの参照としてここに残します。別のアプローチを推奨する場合はアドバイスしてください。)

ThreadData自体は揮発性ではありません。

「データ」としてのインスタンス化main()は揮発性です。ポインター「data」getValue()および watchValueValue() も、「ThreadData」タイプの揮発性バージョンを指します。

最初の答えのタイトさが好きですが、書き直します

ThreadData data;  // main()
ThreadData* data; // getValue(), watchValueValue()

volatile ThreadData data;  // main()
volatile ThreadData* data; // getValue(), watchValueValue()
                           // Pointer `data` is not volatile, what it points to is volatile.

多分よくなる。ThreadData のメンバーへのアクセスは常に再読み込みされ、最適化されません。に追加のフィールドを追加する必要がThreadDataある場合は、同様に保護されています。

于 2013-05-29T02:14:42.707 に答える