std :: shared_ptr向量指向的数据之间的线程同步

时间:2017-06-27 14:11:20

标签: c++ multithreading c++11 shared-ptr

我对并发编程很陌生,我有一个特定的问题,我无法通过浏览互联网找到解决方案..

基本上我有这种情况(原理图伪代码):

void fun1(std::vector<std::shared_ptr<SmallObj>>& v) {
  for(int i=0; i<v.size(); i++)
    .. read and write on *v[i] ..
}

void fun2(std::vector<std::shared_ptr<SmallObj>>& w) {
  for(int i=0; i<w.size(); i++)
    .. just read on *w[i] ..
}


int main() {

 std::vector<std::shared_ptr<SmallObj>> tot;

 for(int iter=0; iter<iterMax; iter++) {
   for(int nObj=0; nObj<nObjMax; nObj++)
      .. create a SmallObj in the heap and store a shared_ptr in tot ..

   std::vector<std::shared_ptr<SmallObj>> v, w;
   .. copy elements of "tot" in v and w ..

   fun1(v);
   fun2(w);
 }

 return 0;
}

我想要做的是同时运行两个线程来执行fun1和fun2,但我需要使用一些锁定机制来规范对SmallObjs的访问。我该怎么做?在文献中,我只能找到使用互斥锁来锁定对特定对象或部分代码的访问的示例,而不是使用不同对象(在本例中为v和w)对相同的指向变量进行访问。

非常感谢,对此我对此事的无知感到抱歉。

2 个答案:

答案 0 :(得分:0)

  

我需要使用一些锁定机制来规范对SmallObjs的访问。我该怎么办?

为数据成员使用getter和setter。使用std::mutex(或std::recursive_mutex取决于是否需要递归锁定)数据成员来保护访问,然后始终使用锁定保护锁定。

示例(另请参阅代码中的注释):

class SmallObject{

    int getID() const{
        std::lock_guard<std::mutex> lck(m_mutex);
        return ....;
    }


    void setID(int id){
        std::lock_guard<std::mutex> lck(m_mutex);
        ....;
    }

    MyType calculate() const{
        std::lock_guard<std::mutex> lck(m_mutex);

        //HERE is a GOTCHA if `m_mutex` is a `std::mutex`
        int k = this->getID();               //Ooopsie... Deadlock

        //To do the above, change the decaration of `m_mutex` from
        //std::mutex, to std::recursive_mutex
}

private:
    ..some data
    mutable std::mutex m_mutex;
};

答案 1 :(得分:0)

最简单的解决方案是为整个向量保持std::mutex

#include <mutex>
#include <thread>
#include <vector>

void fun1(std::vector<std::shared_ptr<SmallObj>>& v,std::mutex &mtx) {
  for(int i=0; i<v.size(); i++)
    //Anything you can do before read/write of *v[i]...
   {
       std::lock_guard<std::mutex> guard(mtx);
        //read-write *v[i]
   }
   //Anything you can do after read/write of *v[i]...
}

void fun2(std::vector<std::shared_ptr<SmallObj>>& w,std::mutex &mtx) {
  for(int i=0; i<w.size(); i++) {

   //Anything that can happen before reading *w[i] 
   {
       std::lock_guard<std::mutex> guard(mtx);
        //read *w[i]
    }
    //Anything that can happen after reading *w[i]
}


int main() {

 std::mutex mtx;
 std::vector<std::shared_ptr<SmallObj>> tot;

 for(int iter=0; iter<iterMax; iter++) {
   for(int nObj=0; nObj<nObjMax; nObj++)
      .. create a SmallObj in the heap and store a shared_ptr in tot ..

   std::vector<std::shared_ptr<SmallObj>> v, w;
   .. copy elements of "tot" in v and w ..

   std::thread t1([&v,&mtx] { fun1(v,mtx); });
   std::thread t2([&w,&mtx] { fun2(w,mtx); });

   t1.join();
   t2.join();
 }

 return 0;
}

但是,您只能在fun1()fun2()的循环中实际获得前/后块中完成的位的任何并行性。

您可以通过引入更多锁来进一步提高并行度。 例如,你可以只使用2个控制奇数和偶数元素的互斥量来逃避:

void fun1(std::vector<int>&v,std::mutex& mtx0,std::mutex& mtx1 ){
    for(size_t i{0};i<v.size();++i){
        {
        std::lock_guard<std::mutex> guard(i%2==0?mtx0:mtx1);
            //read-write *v[i]
        }
    } 
}

使用fun2()的类似格式。

您可以通过从向量的两端开始工作或使用try_lock并移动到后续元素并且“回归”来减少争用。在可用时锁定元素。

如果一个函数的迭代的执行比另一个函数的迭代大得多,那么这可能是最重要的,并且从更快的&#39;中获得结果有一些优势。一个在另一个完成之前。

备选方案:

显然可以为每个对象添加std::mutex。 这是否有效取决于函数fun1fun2中实际执行的操作以及如何管理这些互斥锁。

如果在任何一个循环开始之前锁定是必要的,那么并行性实际上没有任何好处,因为fun1()fun2()中的一个基本上会等待另一个完成并且这两者实际上会串联起来。