3

我对 C++ 标准库相当陌生,并且一直在使用标准库列表来实现特定的多线程实现。我注意到使用列表可能有一个技巧,我在任何教程/博客/论坛帖子上都没有见过,尽管对我来说似乎很明显,但似乎没有人考虑过。所以也许我太新了,可能错过了一些东西,所以希望比我更聪明的人可以验证我正在努力实现的目标或向我解释我做错了什么。

所以我们知道,一般标准库容器不是线程安全的——但这似乎更像是一个指导性声明而不是规则。对于列表,似乎对线程安全有一定程度的容忍度。让我解释一下,我们知道,如果我们从列表中添加/删除,列表不会失效。唯一无效的迭代器是已删除的项目 - 您可以使用以下代码行修复它:

it = myList.erase(it)

所以现在假设我们有两个线程,分别称为线程 1 和线程 2。

线程 1 的职责是添加到列表中。它将它视为一个队列,因此它使用 std::list::push_back() 函数调用。

线程 2 的职责是将存储在列表中的数据作为队列处理,然后在处理后从列表中删除元素。

它保证线程 2 不会删除列表中刚刚在其处理期间添加的元素,线程 1 保证它将提前为线程 2 的处理排队必要的数据。但是,请记住,可以在线程 2 的处理过程中添加元素。

所以看起来这是在这个多线程环境中合理使用列表而不使用锁来保护数据。我之所以说它是合理的,是因为从本质上讲,线程 2 只会处理到目前为止的数据,以便它可以检索以下伪代码所示的当前结束迭代器:

Thread 2 {
    iter = myList.begin();
    lock();
    iterEnd = myList.end(); // lock data temporarily in order to get the current 
                            // last element in the list
    unlock();
    // perform necessary processing
    while (iter != iterEnd) {
        // process data
        // ...
        // remove element
        iter = myList.erase(iter);
    }
}

线程 2 在很短的时间内使用锁只是为了知道在哪里停止处理,但在大多数情况下,线程 1 和线程 2 不需要任何其他锁定。此外,如果知道当前最后一个元素的范围是灵活的,线程 2 也可以避免锁定。

有人认为我的建议有什么问题吗?

谢谢!

4

1 回答 1

5

你的程序很活泼。作为一个明显的数据竞赛的一个例子: std::list不仅仅是一个双重链接节点的集合。例如,它还有一个数据成员,用于存储列表中的节点数(它不必是单个数据成员,但必须将计数存储在某处)。

您的两个线程将同时修改此数据成员。因为这些修改没有同步,所以你的程序很活泼。

标准库容器的实例不能在没有外部同步的情况下同时从多个线程中进行变异。

于 2012-06-09T20:53:07.937 回答