C++ 这种线程间对象共享策略合理吗?

C++ 这种线程间对象共享策略合理吗?,c++,multithreading,thread-safety,shared-memory,C++,Multithreading,Thread Safety,Shared Memory,我试图想出一个快速解决以下问题的方法: 我有一个线程产生数据,还有几个线程使用数据。我不需要对生成的数据排队,因为数据生成的速度比消耗的速度慢得多(即使偶尔出现这种情况,如果偶尔跳过数据点也不会有问题)。因此,基本上,我有一个封装“最新状态”的对象,它只允许生产者线程更新 我的策略如下(如果我完全疯了,请告诉我): 我为这个例子创建了三个类:Thing(实际状态对象)、SharedObject(一个对象,可以是每个线程的本地对象,并允许该线程访问底层对象)和SharedObjectManager

我试图想出一个快速解决以下问题的方法:

我有一个线程产生数据,还有几个线程使用数据。我不需要对生成的数据排队,因为数据生成的速度比消耗的速度慢得多(即使偶尔出现这种情况,如果偶尔跳过数据点也不会有问题)。因此,基本上,我有一个封装“最新状态”的对象,它只允许生产者线程更新

我的策略如下(如果我完全疯了,请告诉我):

我为这个例子创建了三个类:
Thing
(实际状态对象)、
SharedObject
(一个对象,可以是每个线程的本地对象,并允许该线程访问底层对象)和
SharedObjectManager
,它封装了一个
共享的ptr
和一个
互斥对象

SharedObjectManager
(SOM)的实例是一个全局变量。 当producer启动时,它实例化一个对象,并将其告知全局SOM。然后,它制作一个副本,并对该副本执行所有更新工作。当它准备提交它对对象的更改时,它将新对象传递给全局SOM,全局SOM锁定它的互斥体,更新它保留的共享指针,然后释放锁

同时,使用者线程都是INTSANTATE
SharedObject
。这些对象各自保留一个指向全局SOM的指针,以及由SOM保留的
共享\u ptr
的缓存副本。。。它会一直将此缓存,直到显式调用
update()

我相信这越来越难理解,所以这里有一些代码:

#include <mutex>
#include <iostream>
#include <memory>

class Thing
{
    private:
        int _some_member = 10;
    public:
        int some_member() const { return _some_member; }
        void some_member(int val) {_some_member = val; }
};

// one global instance
template<typename T>
class SharedObjectManager
{
    private:
        std::shared_ptr<T> objPtr;
        std::mutex objLock;

    public:
        std::shared_ptr<T> get_sptr()
        {
            std::lock_guard<std::mutex> lck(objLock);
            return objPtr;
        }

        void commit_new_object(std::shared_ptr<T> new_object)
        {
            std::lock_guard<std::mutex> lck (objLock);
            objPtr = new_object;
        }
};


// one instance per consumer thread.
template<typename T>
class SharedObject
{
    private:
        SharedObjectManager<T> * som;
        std::shared_ptr<T> cache;

    public:
        SharedObject(SharedObjectManager<T> * backend) : som(backend)
        {update();}

        void update()
        {
            cache = som->get_sptr();
        }

        T & operator *()
        {
            return *cache;
        }

        T * operator->()
        {
            return cache.get();
        }


};



// no actual threads in this test, just a quick sanity check.

SharedObjectManager<Thing> glbSOM;

int main(void)
{
    glbSOM.commit_new_object(std::make_shared<Thing>());

    SharedObject<Thing> myobj(&glbSOM);

    std::cout<<myobj->some_member()<<std::endl;
    // prints "10".
}
#包括
#包括
#包括
阶级事务
{
私人:
int(某些)成员=10 ;;
公众:
int some_member()常量{return_some_member;}
void some_member(int val){some_member=val;}
};
//一个全局实例
模板
类SharedObjectManager
{
私人:
std::共享对象;
std::互斥对象锁;
公众:
std::shared_ptr get_sptr()
{
标准:锁紧装置lck(objLock);
返回objPtr;
}
void commit_new_对象(std::shared_ptr new_对象)
{
标准:锁紧装置lck(objLock);
objPtr=新的_对象;
}
};
//每个使用者线程一个实例。
模板
类共享对象
{
私人:
SharedObjectManager*som;
std::共享的ptr缓存;
公众:
SharedObject(SharedObjectManager*后端):som(后端)
{update();}
无效更新()
{
cache=som->get_sptr();
}
T&运算符*()
{
返回*缓存;
}
T*运算符->()
{
返回cache.get();
}
};
//在这个测试中没有实际的线程,只是一个快速的健全性检查。
共享项目经理glbSOM;
内部主(空)
{
提交新对象(std::make_shared());
共享对象myobj(&glbSOM);

std::cout这是过度工程化的。相反,我建议做以下几点:

  • 创建一个指向
    Thing*theThing
    的指针以及保护互斥锁。可以是全局指针,也可以通过其他方式共享。将其初始化为null ptr
  • 在你的制作人中:使用两个
    Thing
    类型的本地对象-
    thingOne
    thingTwo
    (记住,
    thingOne
    并不比
    thingTwo
    好,但是其中一个被称为
    thingOne
    是有原因的,但这是一个东西。小心猫。)。首先填充
    thingOne
    。完成后,锁定互斥锁,将
    thingOne
    地址复制到
    thingOne
    ,解锁互斥锁。开始填充
    thingTwo
    。完成后,请参见上文。重复操作,直到被杀死
  • 在每个侦听器中:(确保指针不是null ptr)。锁定互斥体。复制由
    theThing
    指向的两个对象。解锁互斥体。处理您的副本。读取后刻录。重复此操作,直到终止

std::shared\u ptr objPtr;
让我担心,在某个地方,有另一方在没有适当保护的情况下使用
*objPtr
。我不得不说不。听起来很复杂。相反,在生产者中使用一个大小有限的队列,只持有有效(已提交)对象并允许删除元素。这可能更简单(摆脱无用的管理器)。一定要小心猫。他想要你所有的基础。@user4581301,我们中的一个没有得到这个笑话。会是谁?如果复制对象是出于某种原因的问题,你可以使用受互斥锁保护的
共享\u ptr
。或者如果只有一个线程需要使用该对象,你可以使用你移入/移出的
唯一\u ptr
保护互斥。不确定@SergeyA。可能是两条鱼中的一条。我猜是红色的。@DavidSchwartz,应该复制或移动对象,否则,设计会崩溃。
// initialization - on startup
auto firstStateObj = std::make_shared<Thing>();
glbSOM.commit_new_object(firstStateObj);

// main loop
while (1)
{
    // invoke copy constructor to copy the current live Thing object
    auto nextState = std::make_shared<Thing>(*(glbSOM.get_sptr()));

    // do stuff to nextState, gradually filling out it's new value 
    //    based on incoming data from other sources, etc.
    ...

    // commit the changes to the shared memory location
    glbSOM.commit_new_object(nextState);
}
SharedObject<Thing> thing(&glbSOM);
while(1)
{
    // think about the data contained in thing, and act accordingly...
    doStuffWith(thing->some_member());        

    // re-cache the thing
    thing.update();
}