私は個人的な趣味時代のゲームエンジンで作業しており、マルチスレッドバッチエグゼキュータで作業しています。私はもともと、マスタとスレーブのスレッド間の通信を容易にするために、ロックレスキューとstd :: functionを並行して使用していましたが、メモリを厳密に制御できる軽量な方法割り当て:関数ポインタとメモリプール。マルチスレッドC++:キャッシュからバイパスキャッシュ
とにかく、私が問題に遭遇しました:他の人がNULLポインタを読み、これアサートに失敗しながら
関数ポインタは、関係なく、私がしようとするもの、1つのスレッドだけ正しく読み取れなかっなっています。
これはキャッシュに問題があると確信しています。私はすべてのスレッドが同じアドレスを持つことを確認しました。私はvolatile、intptr_t、std :: atomicと宣言しようとしましたが、あらゆる種類のcasting-fuを試しました。スレッドはすべて無視してキャッシュされたコピーを読み続けるようです。
私は、並行性が良好であることを確認するためにモデル検査では、マスタとスレーブをモデル化してきたが、何のライブロックやデッドロックが存在しない(共有変数すべてが正しく同期することを提供する)
void Executor::operator() (int me) {
while (true) {
printf("Slave %d waiting.\n", me);
{
std::unique_lock<std::mutex> lock(batch.ready_m);
while(!batch.running) batch.ready.wait(lock);
running_threads++;
}
printf("Slave %d running.\n", me);
BatchFunc func = batch.func;
assert(func != nullptr);
int index;
if (batch.store_values) {
while ((index = batch.item.fetch_add(1)) < batch.n_items) {
void* data = reinterpret_cast<void*>(batch.data_buffer + index * batch.item_size);
func(batch.share_data, data);
}
}
else {
while ((index = batch.item.fetch_add(1)) < batch.n_items) {
void** data = reinterpret_cast<void**>(batch.data_buffer + index * batch.item_size);
func(batch.share_data, *data);
}
}
// at least one thread finished, so make sure we won't loop back around
batch.running = false;
if (running_threads.fetch_sub(1) == 1) { // I am the last one
batch.done = true; // therefore all threads are done
batch.complete.notify_all();
}
}
}
void Executor::run_batch() {
assert(!batch.running);
if (batch.func == nullptr || batch.n_items == 0) return;
batch.item.store(0);
batch.running = true;
batch.done = false;
batch.ready.notify_all();
printf("Master waiting.\n");
{
std::unique_lock<std::mutex> lock(batch.complete_m);
while (!batch.done) batch.complete.wait(lock);
}
printf("Master ready.\n");
batch.func = nullptr;
batch.n_items = 0;
}
batch.func
ですそれは
typedef void(*BatchFunc)(const void*, void*);
struct JobBatch {
volatile BatchFunc func;
void* const share_data = operator new(SHARED_DATA_MAXSIZE);
intptr_t const data_buffer = reinterpret_cast<intptr_t>(operator new (EXEC_DATA_BUFFER_SIZE));
volatile size_t item_size;
std::atomic<int> item; // Index into the data array
volatile int n_items = 0;
std::condition_variable complete; // slave -> master signal
std::condition_variable ready; // master -> slave signal
std::mutex complete_m;
std::mutex ready_m;
bool store_values = false;
volatile bool running = false; // there is work to do in the batch
volatile bool done = false; // there is no work left to do
JobBatch();
} batch;
を扱っていますことを、別の関数で
template<typename SharedT, typename ItemT>
void set_batch_job(void(*func)(const SharedT*, ItemT*), const SharedT& share_data, bool byValue = true) {
static_assert(sizeof(SharedT) <= SHARED_DATA_MAXSIZE, "Shared data too large");
static_assert(std::is_pod<SharedT>::value, "Shared data type must be POD");
assert(std::is_pod<ItemT>::value || !byValue);
assert(!batch.running);
batch.func = reinterpret_cast<volatile BatchFunc>(func);
memcpy(batch.share_data, (void*) &share_data, sizeof(SharedT));
batch.store_values = byValue;
if (byValue) {
batch.item_size = sizeof(ItemT);
}
else { // store pointers instead of values
batch.item_size = sizeof(ItemT*);
}
batch.n_items = 0;
}
を設定し、ここでは構造体(とのtypedef)でされています
batch.func
へのすべての必要な読み取りと書き込みがスレッド間で正しく同期することを確認するにはどうすればよいですか?
私はVisual Studioを使用しており、x64 Debug Windows実行ファイルをコンパイルしています。 Intel i5、Windows 10、8GB RAM
なぜ構造物に揮発性のものが複数あるのですか? – tambre
[MVCE](http:// stackoverflow)を提供することをお勧めします。com/help/mcve)。 – tambre
volatileの過剰さは、関数ポインタ以外の変数が、私が期待したように動作しないものに対する過度の処理でキャッシュ競合を引き起こしていることを心配しています。 – Beefster