オフセット値を使用します。
ポインターの代わりにsize_t
、共有メモリー領域の先頭からのオフセット (文字単位) を使用します。これらのリストにアクセスしたり操作したりするすべての場所で、これを行う必要があります。
追加するために編集:
このようにオフセットを使用すると、ほとんどのアーキテクチャで非常に効率的なコードにコンパイルされ、__sync..()
ビルトインを使用してアトミックにアクセスおよび変更できます。読み取りを含むすべてのアクセスにビルトインを使用することを忘れないでください。そうしないと、非アトミック読み取り中に値がアトミックに変更され (またはその逆)、データが破損する可能性があります。
共有メモリのサイズが 4GB を超えないことがわかっている場合は、uint32_t
代わりにオフセット タイプとして使用して、64 ビット アーキテクチャで「ポインタ」ごとに 4 バイトを節約できます。すべてのターゲットを 32 ビット境界に揃えると、それを 4 倍の 8GB にすることができます。
を使用することによる非常に優れた副作用は、すべての 64 ビットおよび一部の 32 ビット アーキテクチャで、ポインターのペアuint32_t
(2 つの連続するオフセット) をアトミックに操作できることです。共有メモリ内のすべても 32 ビット境界に整列していると仮定し、各 32 ビット ユニットへのオフセットを使用すると、次を使用してアトミックにポインター ペアを取得/設定できます。
static inline void get_pair(void *const base, const uint32_t offset, uint32_t *const pair)
{
uint64_t *const ptr = (uint64_t *)(offset + (uint32_t *)base);
uint64_t val;
val = __sync_or_and_fetch(ptr, (uint64_t)0);
memcpy(pair, &val, sizeof val);
}
static inline void switch_pair(void *const base, const uint32_t offset, const uint32_t *const new, uint32_t *const old)
{
uint64_t *const ptr = (uint64_t *)(offset + (uint32_t *)base);
uint64_t oldval, newval;
memcpy(newval, &new, sizeof newval);
do {
/* Note: this access does not need to be atomic, */
memcpy(oldval, ptr, sizeof oldval);
/* because the next one verifies it. */
} while (!__sync_bool_compare_and_swap(ptr, oldval, newval));
if (old)
memcpy(old, &oldval, sizeof oldval);
}
__sync...()
ビルトインは、少なくとも GCC と Intel CC で動作します。新しい C11 標準も C++11 スタイル__atomic..()
のビルトインを採用していますが、現在のコンパイラに機能が実装されるまでにはしばらく時間がかかります。
ライブラリ コード、または数年間保守する予定のコードを作成する場合は、両方の組み込み型を調べて、自分自身 (または保守するときにそれを保守する人) にコメントを追加することで、おそらく時間を節約できます。ビルトイン間の移行)、それらが既に利用可能な場合に使用するアトミックビルトインを説明します。
最後に、このように共有メモリを使用することは、メモリに同時にアクセスする複数のスレッドがある場合と同じ注意を払う必要があることを意味することを覚えておいてください。アトミック操作は役に立ちます。ポインターのペアをアトミックに操作できる場合は、リストを使ってできる非常に巧妙なトリックがいくつかあります。