使用 std::promise 和 std::future 时发生访问冲突
Access Violation occurs when using std::promise and std::future
在我的代码中,我使用大致如下所示的结构将任务分派到我的线程池:
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 多行] 代码中的其他地方引起的,但我想知道我在这里展示的代码中是否存在明显的错误。
编辑:
我观察到的另一件事:如果关闭优化,我将无法重现错误。此错误仅在打开优化时发生。
双重编辑组合:
发生访问冲突的调用堆栈不可读:
时间问题之一。
当最后一个线程设置它的 promise 值时,这允许主线程中的循环完成,这反过来导致 data_0_mutex
对象被销毁。最后一个线程仍然可以是 运行,当 lock
的析构函数被调用时,它引用的互斥锁已经(或正在)被销毁。
这在您的 "polling" 版本中不太可能是问题,因为主线程必须等待 OS 恢复它,但它仍然可能发生。
解决方案是在设置 promise 之前释放锁,方法是调用
lock.unlock();
在 promise[i].set_value();
.
之前
在我的代码中,我使用大致如下所示的结构将任务分派到我的线程池:
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 多行] 代码中的其他地方引起的,但我想知道我在这里展示的代码中是否存在明显的错误。
编辑:
我观察到的另一件事:如果关闭优化,我将无法重现错误。此错误仅在打开优化时发生。
双重编辑组合:
发生访问冲突的调用堆栈不可读:
时间问题之一。
当最后一个线程设置它的 promise 值时,这允许主线程中的循环完成,这反过来导致 data_0_mutex
对象被销毁。最后一个线程仍然可以是 运行,当 lock
的析构函数被调用时,它引用的互斥锁已经(或正在)被销毁。
这在您的 "polling" 版本中不太可能是问题,因为主线程必须等待 OS 恢复它,但它仍然可能发生。
解决方案是在设置 promise 之前释放锁,方法是调用
lock.unlock();
在 promise[i].set_value();
.