矢量复制和多线程 :即使偶尔发生写入,如何确保多读

vector copying and multi-threading : how to ensure multi-read even if occasional writes may happen?

本文关键字:何确保 确保 多线程 复制 偶尔      更新时间:2023-10-16
Pseudocode:
Function1_vector_copy () {
vectora = vectorb;
}
Function2_vector_search() {
find k in vectora;
}

该程序是多线程的。虽然许多线程可以搜索,但矢量复制仅由单个线程完成,但偶尔也会完成。问题是不管它是什么,vector_search不应该失败。Vector_copy可以推迟,但vector_search不应该推迟。它没有延迟,没有故障模式。问题是共享变量 vectora 必须是持久的,这样vector_search根本不会失败。实现这一目标的最佳方法是什么?

编辑:

使用另一个问题的答案

boost::shared_mutex _access;
Function1_vector_copy() {
  // get upgradable access
  boost::upgrade_lock lock(_access);
  // get exclusive access
  boost::upgrade_to_unique_lock uniqueLock(lock);
  // now we have exclusive access
  vectora.swap(vectorb);
}
Function2_vector_search() {
  // get shared access
  boost::shared_lock lock(_access);
  // now we have shared access
  find k in vectora ;
}

如果具有可升级所有权的线程尝试升级,而其他线程 线程具有共享所有权,尝试将失败,线程 将阻止,直到可以获得独家所有权。 --提升文档

诀窍是矢量复制是异步完成的,它将在获得独占所有权后发生。所以矢量复制最终确实会发生,但延迟,实际上矢量复制也是一个非失败的操作,但延迟了,这对我来说没关系。会以某种方式锁定同步,我们将至少阻止一毫秒。但是使用这种交换的优势将导致更短的时间,使其可以忽略不计。

Function1_vector_copy () 
{
    VectorType tmp = vectorb;
    rwLock.acquireWrite();
    swap(tmp,vectora);
    rwLock.releaseWrite();
}

其余的留给读者作为练习。

更新:总结下面的评论...

首先,我应该明确指出,上面的原始swap(...)调用旨在作为伪代码,不一定从字面上理解。 我不知道VectorType到底是什么(从技术上讲仍然不知道(。 我假设你会打算尽可能高效地进行 swap((。 毕竟是伪代码...

在任何情况下,std::swap()都专门用于C++标准库中的所有容器类型。 因此,如果VectorType实际上是std::vector<UDT>,那么swap(tmp,vectora);应该是最优的,并且无需修改即可工作(依靠 Koenig 查找来解析符号(。

我认为 mccc 的想法并不太遥远,而是通用的 std::swap(( 将执行矢量复制,您应该能够使用 std::vector::swap((。 功能上矢量的交换函数与通用函数的工作方式相同,但它要快得多,因为它只是交换很少的指针/大小变量,并且实际上并不执行元素复制。 因此,您的锁只会在少数组装说明中出现。

如果这仍然太长(此时,您可能需要查看您的设计(,另一种选择是拥有一个永远不会相互替换的向量循环列表。而是使用另一个引用"活动"向量的指针。 当您需要修改该集合时,初始化圆圈中的下一个向量,初始化完成后,更新指针。使用此解决方案,根本没有锁。 前一刻你的读者正在使用一个向量,下一刻他们正在使用不同的向量。

第二种解决方案的缺点是它肯定需要更多的内存,而且有点不可预测。 我想你可以声明你的"活动"矢量指针易失性,这将强制 CPU 确保每次读取指针值时缓存都同步,否则,有一些不确定的元素,其中多个 CPU 内核可能正在查看主内存的不同快照。 同样,在没有任何同步的情况下,您如何确保其他线程与即将被吹走的矢量一起完成? 如果您执行任何类型的同步,则会返回 RW 锁定。

虽然我认为你可以得到第二种工作选择,并进行一些相对稳定的调整......可能。我的第一直觉是使用 RW 锁定和 vector::swap((。

更新:实际上,我只是重新阅读了规范,看起来STL确实专门针对容器(包括std::vector(专门针对全局std::swap。 因此,即使调用全局函数,复杂度也是 O(1(。 MCMCC 的答案有效。

所以我们把这个猜测放下了,我只是写了一点代码,然后用调试器逐步完成它。这是我的应用程序代码:

std::vector< int >      a, b;
for( int i = 0; i < 10; i++ )
{
    a.push_back( i );
    b.push_back( i * 10 );
}
std::swap( a, b );

以下是对 std::swap 的全局通用版本的调用内容:

template<class _Ty,
class _Alloc> inline
void swap(vector<_Ty, _Alloc>& _Left, vector<_Ty, _Alloc>& _Right)
{   // swap _Left and _Right vectors
_Left.swap(_Right);
}

如您所见,通用 std::swap 模板函数在通用模式下执行复制构造函数的事情,专门用于向量。 在内部,它只是将所有工作委托给 std::vector::swap((,正如我们所建立的那样,这是一个更快的变体。