2017-02-04 2 views
0

私のコードはできるだけシンプルでスレッドセーフであることが必要です。そのISO/IEC 9899/201XドラフトフェンスでSPSCスレッドを安全にする

XとYの "7.17.4フェンス" の部分についてはC11のアトミック

、両方のいくつかのアトミックオブジェクトM上で動作する、そのようなAは がXの前に配列され、XがMより前に配列され、YがBより前に配列され、Yが のXで書かれた値または の操作でリリースされた場合、Yが 仮想放出配列X 。

このコードはスレッドセーフです(「オブジェクトM」の「w_i」)。
"w_i"と "r_i"の両方を_Atomicとして宣言する必要がありますか?
w_iのみが_Atomicの場合、メインスレッドはr_iの古い値をキャッシュに保持し、キューがいっぱいではないとみなし(フルの間)、データを書き込むことはできますか?
atomic_loadのないアトミックを読み込むとどうなりますか?

私はいくつかのテストを行いましたが、私の試みはすべて正しい結果を出すようです。 しかし、私はマルチスレッドに関して私のテストが本当に正しくないことを知っています。プログラムを何度か実行して結果を見ます。

どちらw_iは_atomic、私のプログラムの仕事としてR_iと宣言されていないが、唯一のフェンスは、右、C11の規格に関しては十分でない場合であっても?

typedef int rbuff_data_t; 

struct rbuf { 
    rbuff_data_t * buf; 
    unsigned int bufmask; 

    _Atomic unsigned int w_i; 
    _Atomic unsigned int r_i; 
}; 
typedef struct rbuf rbuf_t; 

static inline int 
thrd_tryenq(struct rbuf * queue, rbuff_data_t val) { 
    size_t next_w_i; 

    next_w_i = (queue->w_i + 1) & queue->bufmask; 

    /* if ring full */ 
    if (atomic_load(&queue->r_i) == next_w_i) { 
     return 1; 
    } 

    queue->buf[queue->w_i] = val; 
    atomic_thread_fence(memory_order_release); 
    atomic_store(&queue->w_i, next_w_i); 

    return 0; 
} 

static inline int 
thrd_trydeq(struct rbuf * queue, rbuff_data_t * val) { 
    size_t next_r_i; 

    /*if ring empty*/ 
    if (queue->r_i == atomic_load(&queue->w_i)) { 
     return 1; 
    } 
    next_r_i = (queue->r_i + 1) & queue->bufmask; 
    atomic_thread_fence(memory_order_acquire); 
    *val = queue->buf[queue->r_i]; 
    atomic_store(&queue->r_i, next_r_i); 
    return 0; 
} 

私は次のように論文の関数を呼び出す:
メインスレッドがいくつかのデータをエンキュー:デキューデータのスレッド

while (thrd_tryenq(thrd_get_queue(&tinfo[tnum]), i)) { 
    usleep(10); 
    continue; 
} 

その他:ASMフェンス

static void * 
thrd_work(void *arg) { 
    struct thrd_info *tinfo = arg; 
    int elt; 

    atomic_init(&tinfo->alive, true); 

    /* busy waiting when queue empty */ 
    while (atomic_load(&tinfo->alive)) { 
     if (thrd_trydeq(&tinfo->queue, &elt)) { 
      sched_yield(); 
      continue; 
     } 
     printf("Thread %zu deq %d\n", 
       tinfo->thrd_num, elt); 
    } 

    pthread_exit(NULL); 
} 

、LFENCEとSFENCEと特定のプラットフォームのx86に関しては、私はすべてのC11コードを削除し、ちょうど

asm volatile ("sfence" ::: "memory"); 

asm volatile ("lfence" ::: "memory"); 

でフェンスを交換した場合(これらのマクロの私の理解では、次のとおりです。メモリを防ぐために、コンパイラフェンス再利用/最適化+ハードウェアフェンスへのアクセス)

私の変数は、たとえばvolatileとして宣言する必要がありますか?

私はすでにこれらのみASMフェンス付き超えるが無い原子の種類と、このリングバッファコードを見ていると私は本当に驚いた、私はこのコードが正しかったかどうかを知りたいです。

答えて

1

私はちょうどC11アトミックに関して返信しますが、プラットフォームの仕様は複雑すぎて段階的に廃止する必要があります。

C11のスレッド間の同期は、一部のシステムコール(例えば、mtx_t)とアトミックによってのみ保証されます。それなしでやろうとしないでください。

アシックスに作用の可視性を介して、副作用の可視性が伝播することが保証されていると言われています。たとえば、最も単純な整合性モデルでは、スレッドT2が修正スレッドT1が原子変数Aに影響を与えたとみなすたびに、スレッドT1のその変更前のすべての副作用がT2に見える。

すべての共有変数がアトミックである必要はないため、状態がアトミックで正しく伝播されていることを確認する必要があります。その意味では、フェンスは、連続的なリリースまたは取得リリースの一貫性を使用するときには何も買わず、画像を複雑にするだけです。

いくつかのより一般的な注意事項:

  • あなたは デフォルトでシーケンシャル一貫性モデルを、使用しているように見えるので、アトミック操作の機能書き込み(例えば atomic_load)が不必要です。原子変数を評価するだけで、正確に同じ となります。
  • 私はあなたが開発の初期段階で最適化を試みていると感じています。 私はあなたが正しいことを証明することができる実装 を最初に行うべきだと思います。次に のパフォーマンスに問題がある場合にのみ、 の最適化について考える必要があります。このような原子データ構造 は、あなたのアプリケーションに本当のボトルネックとなることはほとんどありません。 多くのスレッドが同時にあなたの貧弱な人にハンマーで繋がなければなりません ここで測定可能なボトルネックを見るには、小さな原子変数が必要です。
+0

ありがとうございました!私がフェンスを避けるならば、私はatomic_store_explicitをメモリオーダーに使用する必要がありますか?または、私はちょうど書くだろう:キュー - > w_i = next_w_i(それはデフォルトで使用される緩やかな順序ですか?) 私はフェンスを使用する必要がありますか? – treywelsh

+0

いいえ、デフォルトの順序は、順次整合性であり、可能な限り最強です。 –

関連する問題