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

C++ 向量复制和多线程:即使偶尔发生写入,如何确保多读?,c++,multithreading,vector,mutex,boost-mutex,C++,Multithreading,Vector,Mutex,Boost Mutex,该程序是多线程的。虽然许多线程可能会进行搜索,但向量复制仅由单个线程执行,但有时会执行。问题是不管它是什么,向量搜索不应该失败。Vector_复制可以延迟,但Vector_搜索不应延迟。这是一种无延迟、无故障的模式。问题是共享变量vectora必须是持久的,这样vector_搜索就不会失败。实现这一目标的最佳方式是什么 编辑: 如果所有权可升级的线程尝试升级,而其他线程 线程具有共享所有权,尝试将失败,线程 将阻止,直到获得独家所有权。 诀窍是矢量复制是异步完成的,它将在获得独占所有权后发生。

该程序是多线程的。虽然许多线程可能会进行搜索,但向量复制仅由单个线程执行,但有时会执行。问题是不管它是什么,向量搜索不应该失败。Vector_复制可以延迟,但Vector_搜索不应延迟。这是一种无延迟、无故障的模式。问题是共享变量vectora必须是持久的,这样vector_搜索就不会失败。实现这一目标的最佳方式是什么

编辑:

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

诀窍是矢量复制是异步完成的,它将在获得独占所有权后发生。所以向量复制最终会发生,但会延迟,事实上向量复制也是一个无故障的操作,但会延迟,这对我来说是可以的。同步会有一个锁,我们至少会阻塞一毫秒。但是,使用这种互换的好处是可以缩短互换的时间,从而可以忽略不计

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 ;

}
其余部分留给读者作为练习

更新:总结下面的评论

首先,我应该明确最初的交换。。。上面的调用是作为伪代码使用的,不一定是字面意义上的。我不知道VectorType到底是什么,从技术上讲,我仍然不知道。我假设您打算尽可能高效地进行掉期交易。这毕竟是伪代码

无论如何,STD::SWAP是C++标准库中所有容器类型的专用化。所以如果VectorType实际上是std::vector,那么swaptmp,vectora;应该是最优的,并且在不需要修改的情况下使用Koenig查找来解析符号

其余部分留给读者作为练习

更新:总结下面的评论

首先,我应该明确最初的交换。。。上面的调用是作为伪代码使用的,不一定是字面意义上的。我不知道VectorType到底是什么,从技术上讲,我仍然不知道。我假设您打算尽可能高效地进行掉期交易。这毕竟是伪代码


无论如何,STD::SWAP是C++标准库中所有容器类型的专用化。所以如果VectorType实际上是std::vector,那么swaptmp,vectora;应该是最优的,并且不需要修改就可以使用Koenig查找来解析符号。

我认为mcmcc的想法并不遥远,但应该能够使用std::vector::swap来代替将执行向量复制的通用std::swap。FunctionalVector的swap函数与泛型函数的工作原理相同,但速度要快得多,因为它只交换几个指针/大小变量,实际上不进行元素复制。因此,您的锁将仅用于少数装配说明

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

第二种解决方案的缺点是,它肯定需要更多的内存,而且有点不可预测。我猜您可以将活动向量指针声明为volatile,这将强制CPU确保每次读取指针值时缓存都是同步的,否则,存在一些不确定的元素,其中多个CPU内核可能会查看主内存的不同快照。同样,如果没有任何类型的同步,如何确保其他线程使用即将被吹走的向量来完成?如果你做了任何类型的同步,你就回到了RW锁

虽然我认为你可以得到第二个选择工作和一些调整相对稳定。。。可能我的第一反应是使用RW lock和vector::swap

更新:事实上,我刚刚重读了规范,看起来STL确实专门为容器(包括std::vector)指定了全局std::swap。因此,即使在调用全局函数时,复杂性也是O1。mcmcc的答案是有效的

所以我们停止了这种猜测,我只写了一点代码,并使用调试器逐步完成了它。这是我的申请代码:

Function1_vector_copy () 
{
    VectorType tmp = vectorb;

    rwLock.acquireWrite();
    swap(tmp,vectora);
    rwLock.releaseWrite();
}
下面是对std::swap的全局通用版本的调用:

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 );
正如您所看到的,generic std::swap template函数在generic模式下执行复制构造函数,它是专门为
向量。在内部,它只是将所有工作委托给std::vector::swap,正如我们所建立的,它是一个更快的变体。

我认为mcmcc的想法并不遥远,但是作为将执行向量复制的通用std::swap,您应该能够使用std::vector::swap。FunctionalVector的swap函数与泛型函数的工作原理相同,但速度要快得多,因为它只交换几个指针/大小变量,实际上不进行元素复制。因此,您的锁将仅用于少数装配说明

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

第二种解决方案的缺点是,它肯定需要更多的内存,而且有点不可预测。我猜您可以将活动向量指针声明为volatile,这将强制CPU确保每次读取指针值时缓存都是同步的,否则,存在一些不确定的元素,其中多个CPU内核可能会查看主内存的不同快照。同样,如果没有任何类型的同步,如何确保其他线程使用即将被吹走的向量来完成?如果你做了任何类型的同步,你就回到了RW锁

虽然我认为你可以得到第二个选择工作和一些调整相对稳定。。。可能我的第一反应是使用RW lock和vector::swap

更新:事实上,我刚刚重读了规范,看起来STL确实专门为容器(包括std::vector)指定了全局std::swap。因此,即使在调用全局函数时,复杂性也是O1。mcmcc的答案是有效的

所以我们停止了这种猜测,我只写了一点代码,并使用调试器逐步完成了它。这是我的申请代码:

Function1_vector_copy () 
{
    VectorType tmp = vectorb;

    rwLock.acquireWrite();
    swap(tmp,vectora);
    rwLock.releaseWrite();
}
下面是对std::swap的全局通用版本的调用:

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 );

正如您所看到的,generic std::swap template函数在generic模式下执行复制构造函数,它专门用于向量。在内部,它只是将所有工作委托给std::vector::swap,正如我们所确定的,它是一个更快的变体。

看起来您只需要一个写锁来保护复制。但是,无论如何,对于任何类型的读访问,您都需要写锁。这是否意味着搜索永远不会失败?当复制发生并且必须进行搜索时会发生什么。搜索不会失败吗?即使我是从锁开始的,但它恰好与我想实现的相反。标准的多读单写习惯用法:每个人都必须等待写锁。您只需设计它,使写入锁定不会保持很长时间,例如,首先制作整个副本,并且仅在锁定时交换缓冲区指针,等等。如果我的优先级是读和写,那么当它在开始时发生一次时,第二次写入(可能是1000次读取)可能会延迟或延迟。只是我想对读取进行优先级排序,并使其始终作为一个无故障操作。使用。看起来您只需要一个写锁来保护复制。但是,无论如何,对于任何类型的读访问,您都需要写锁。这是否意味着搜索永远不会失败?当复制发生并且必须进行搜索时会发生什么。搜索不会失败吗?即使我是从锁开始的,但它恰好与我想实现的相反。标准的多读单写习惯用法:每个人都必须等待写锁。您只需设计它,使写入锁定不会保持很长时间,例如,首先制作整个副本,并且仅在锁定时交换缓冲区指针,等等。如果我的优先级是读和写,那么当它在开始时发生一次时,第二次写入(可能是1000次读取)可能会延迟或延迟。这只是我想优先考虑阅读,并使其始终成为一个无故障操作。使用。这是正确的答案。交换实际上是瞬时的。@DavidSchwartz不会在线性时间内进行交换操作,从而与正常的复制构造没有区别?@King否。交换实际上是瞬时的,大致相当于将一个指针交换为另一个指针。如果您担心,您可以自己进行指针交换。NewSharedPtr=新。。。;锁oldSharedPtr=sharedPtr;sharedPtr=新闻共享PTR;解锁;删除oldSharedPtr@大卫施瓦茨:那么向量交换和STL交换是不同的?@King:我不确定我是否理解你的问题。但是一个没有有效交换方法的类不应该有一个。如果您使用的是ve

如果没有,就换一个新的。或者自己交换指针。这是正确的答案。交换实际上是瞬时的。@DavidSchwartz不会在线性时间内进行交换操作,从而与正常的复制构造没有区别?@King否。交换实际上是瞬时的,大致相当于将一个指针交换为另一个指针。如果您担心,您可以自己进行指针交换。NewSharedPtr=新。。。;锁oldSharedPtr=sharedPtr;sharedPtr=新闻共享PTR;解锁;删除oldSharedPtr@大卫施瓦茨:那么向量交换和STL交换是不同的?@King:我不确定我是否理解你的问题。但是一个没有有效交换方法的类不应该有一个。如果你使用的是一个向量类,那么就换一个新的。或者自己做指针交换。我已经尝试在循环列表上实现了这一点,但显然必须有一个更新,我们需要锁。因此,在某个时间点,它看起来像是一个以毫秒为单位的搜索阻塞的折衷方案+1为Swap辩护,因为我对std::Swap的印象是,它在线性时间内运行,对我没有任何影响。但看起来std::vector::swap可能更快。但它真的与普通交换不同吗?它的操作顺序是什么?@King:这里有一些关于vector::swap:的更多信息。基本上是O1操作,只需要复制9个指针值。普通的std::swap调用复制构造函数,所以在您的情况下,您肯定希望避免这种情况。我认为你需要给现代CPU一些荣誉。在0.9毫秒内,您可以从SQLite数据库中获得20行。你在这里说的是大约30条汇编指令,它们可能会内联。@King:我再次认为,如果没有锁定比使用50倍内存更重要,那么有足够大的循环列表,你就可以得到一个完整的无锁定解决方案。可能需要在这里和那里引入一些联锁计数器。如果你准备迎接挑战,技术上应该是可行的。。。。我不会这么做的:那就行了。向量交换是指针交换,与普通交换不同:很好。我本想实现指针交换,但很好,有一个真正有效的交换说generic做复制构造,而swap-in-vector是指针交换。我曾尝试在基于循环列表的基础上实现这一点,但显然必须有一个更新,我们需要锁。因此,在某个时间点,它看起来像是一个以毫秒为单位的搜索阻塞的折衷方案+1为Swap辩护,因为我对std::Swap的印象是,它在线性时间内运行,对我没有任何影响。但看起来std::vector::swap可能更快。但它真的与普通交换不同吗?它的操作顺序是什么?@King:这里有一些关于vector::swap:的更多信息。基本上是O1操作,只需要复制9个指针值。普通的std::swap调用复制构造函数,所以在您的情况下,您肯定希望避免这种情况。我认为你需要给现代CPU一些荣誉。在0.9毫秒内,您可以从SQLite数据库中获得20行。你在这里说的是大约30条汇编指令,它们可能会内联。@King:我再次认为,如果没有锁定比使用50倍内存更重要,那么有足够大的循环列表,你就可以得到一个完整的无锁定解决方案。可能需要在这里和那里引入一些联锁计数器。如果你准备迎接挑战,技术上应该是可行的。。。。我不会这么做的:那就行了。向量交换是指针交换,与普通交换不同:很好。我本想实现指针交换,但很好,有一个真正有效的交换表示generic执行复制构造,而swap-in向量是指针交换。