2

我正在尝试实现一个多线程框架,其中输出对象在我的网络线程运行的每一帧的末尾创建,以便另一个线程可以在其帧的开头获得最新的“完成输出”指针并且知道它对存储在输出对象中的任何数据具有安全且完整的只读访问权限。

我的(非常早期的)方法主要涉及以下代码块:

网络处理器 -

void NetworkHandler::Tick()
    {
        // Tick on our io_service
        m_ios.poll();

        // Assemble new output data
        this->AssembleOutput();
    }

ClientNetworkHandler -

void ClientNetworkHandler::AssembleOutput()
    {
        std::tr1::shared_ptr<crusade::task::TaskOutput> newOutput(new crusade::task::TaskOutput());
        newOutput->m_outputElements["connected"] = std::tr1::shared_ptr<crusade::task::TaskOutputElement>(new NetworkConnectedTaskOutputElement(this->m_isConnected));
        this->m_latestOutput.swap(newOutput);
    }

PyCruHandler -

void PyCruHandler::Tick()
    {
        printf("PyCruHandler\n");
        // Get any necessary inputs from other threads
        m_latestNetworkOutput.swap(crusade::task::THManager::GetInstance()->GetTaskHandler(crusade::task::THManager::TH_NETWORK)->GetLatestOutput());
        // Other unrelated processing to go here
    }

基本上,ClientNetworkHandler 和 PyCruHandler 在不同的线程上独立运行。PyCruHandler 从来没有真正用它的 m_latestNetworkOutput 副本做任何事情;我已经注释掉了以任何方式访问它的所有其他实例,但我仍然遇到以下问题:

如果我允许两个线程调用 swap(或 operator= 等效项),那么最终(通常在运行后 2 秒内,但有时需要几分钟)我将在 operator new 或分配器删除一些时收到以下错误种类:

“堆:释放堆块 2bab3b0 在释放后在 2bab3dc 处修改 Windows 已触发断点。

这可能是由于堆损坏,这表明存在错误……等等。”

我只是一个新手,但对我来说,这似乎表明 shared_ptr 对象之间的线程安全和时间敏感访问问题存在某种问题。但是,我一直对 shared_ptr 线程安全细微差别的解释(这里和其他地方)感到尴尬 - 一篇阅读表明引用计数是线程安全的,因此复制 shared_ptr 周围是安全的(但它们的内部对象不会是线程安全的),其他阅读表明在 shared_ptr 中实际上没有任何有用的线程安全性。我已经阅读了有关 shared_ptrs 线程安全的 boost 文档,但是我仍然没有弄清楚这是否应该是我的代码中的一个问题。

我的问题是,这里的任何人都可以发现我正在做的任何明显缺陷吗?我的目标是我可以访问仍由拥有线程存储的最新输出对象,然后该对象不会被删除(即使在拥有线程移至以后的输出之后),直到输出的每个用户都完成了它也是。在我看来,共享指针似乎是完美的……但是在我用头撞了 3 个小时之后,我开始怀疑……

非常感谢您,如果我以某种方式错误地发布了此内容,我深表歉意;这是我第一次来这里,就协议而言,FAQ 似乎很悠闲!

4

2 回答 2

4

可能这里最好的资源是文档

线程安全

shared_ptr 对象提供与内置类型相同级别的线程安全。多个线程可以同时“读取”(仅使用 const 操作访问)shared_ptr 实例。不同的 shared_ptr 实例可以被多个线程同时“写入”(使用诸如 operator= 或 reset 之类的可变操作访问)(即使这些实例是副本,并且在下面共享相同的引用计数。)

例子:

shared_ptr<int> p(new int(42));

//--- Example 1 ---

// thread A
shared_ptr<int> p2(p); // reads p

// thread B
shared_ptr<int> p3(p); // OK, multiple reads are safe

//--- Example 2 ---

// thread A
p.reset(new int(1912)); // writes p

// thread B
p2.reset(); // OK, writes p2

//--- Example 3 ---

// thread A
p = p3; // reads p3, writes p

// thread B
p3.reset(); // writes p3; undefined, simultaneous read/write

//--- Example 4 ---

// thread A
p3 = p2; // reads p2, writes p3

// thread B
// p2 goes out of scope: undefined, the destructor is considered a "write access"

//--- Example 5 ---

// thread A
p3.reset(new int(1));

// thread B
p3.reset(new int(2)); // undefined, multiple writes

我将假设m_latestOutput在您的代码中是shared_ptr- 在这种情况下,您最接近的示例类似于数字 5(多次写入)。

于 2009-07-19T23:28:02.343 回答
1

根据文档,您必须同步读取和写入。交换既是:)。考虑改用 operator=() ,那只是写。

此外,您的客户端线程必须制作 m_latestNetworkOutput 的副本(将被读取),如果他们想要让对象保持活动直到它们完成(我认为这就是您在 PyCruHandler::Tick() 中所做的)。

无论如何,您必须同步写入:

this->m_latestOutput.swap(newOutput);

并阅读:

m_latestNetworkOutput.swap(crusade::task::THManager::GetInstance()->GetTaskHandler(crusade::task::THManager::TH_NETWORK)->GetLatestOutput());

并将交换更改为分配 - 交换后您不需要旧指针,是吗?

于 2009-07-19T23:41:10.100 回答