在我的代码中,我将任务分配给我的线程池,其构造或多或少看起来像这样:
working_data get_data(my_thread_pool & thread_pool) {
size_t thread_pool_size = thread_pool.size();
std::vector<working_data> data(thread_pool_size);
std::vector<std::promise<void>> promises(thread_pool_size);
std::mutex data_0_mutex;
for(size_t i = 0; i < thread_pool_size; i++) {
thread_pool.post_task([&, i] {
std::unique_lock<std::mutex> lock(data_0_mutex, std::defer_lock);
if(i == 0)
lock.lock();
data[i].add_data(process_data());
if(i != 0) {
lock.lock();
data[0].merge_from(data[i]);
}
promises[i].set_value();
});
}
for(size_t i = 0; i < thread_pool_size; i++) {
promises[i].get_future().wait();
}
return std::move(data[0]);
}
虽然每次执行此代码时都不会发生这种情况,但执行此代码很多次,此代码会导致访问冲突。
相反,以下代码永远不会导致访问冲突,但我宁愿不使用它,因为我不喜欢最后使用轮询循环:
working_data get_data(my_thread_pool & thread_pool) {
size_t thread_pool_size = thread_pool.size();
std::vector<working_data> data(thread_pool_size);
std::vector<std::atomic_bool> flags(thread_pool_size);
std::mutex data_0_mutex;
for(size_t i = 0; i < thread_pool_size; i++) {
thread_pool.post_task([&, i] {
std::unique_lock<std::mutex> lock(data_0_mutex, std::defer_lock);
if(i == 0)
lock.lock();
data[i].add_data(process_data());
if(i != 0) {
lock.lock();
data[0].merge_from(data[i]);
}
flags[i].store(true, std::relaxed_memory_order);
});
}
for(size_t i = 0; i < thread_pool_size; i++) {
while(!flags[i].load(std::relaxed_memory_order))
std::this_thread::yield();
}
return std::move(data[0]);
}
请注意,主要区别在于我使用std::atomic_bool
而不是std::promise
和std::future
。
错误很可能是在我的[2000+行]代码中的其他地方引起的,但我想知道我在这里提出的代码中是否存在明显的错误。
我观察到的另一件事:如果我关闭优化,我无法重新创建错误。只有在启用优化时才会出现此错误。
发生访问冲突的 的调用堆栈是不可读的:
答案 0 :(得分:1)
问题在于时机问题。
当最后一个线程设置其promise的值时,这允许主线程中的循环完成,这反过来导致data_0_mutex
对象被销毁。最后一个线程仍然可以运行,并且当调用lock
的析构函数时,它引用的互斥锁已被(或正在被)销毁。
这在你的“轮询”版本中不太可能是一个问题,因为主线程必须等待操作系统恢复它,但它仍然可能发生。
解决方案是在设置承诺之前释放锁定,方法是调用
lock.unlock();
在promise[i].set_value();
之前。