繁体   English   中英

在线程之间复制std :: vector而不锁定

[英]Copying std::vector between threads without locking

我有一个在一个线程中修改的向量,我需要在另一个线程中使用它的内容。 由于性能要求,在这些线程之间锁定是不可接受的。 由于在向量迭代时迭代将导致崩溃,我想复制向量然后迭代复制。 我的问题是,这种方式也会崩溃吗?

struct Data
{
    int A;
    double B;
    bool C;
};

std::vector<Data> DataVec;

void ModifyThreadFunc()
{
    // Here the vector is changed, which includes adding and erasing elements
    ...
}

void ReadThreadFunc()
{
    auto temp = DataVec;    // Will this crash?
    for (auto& data : temp)
    {
        // Do stuff with the data
        ...
    }

    // This definitely can crash
    /*for (auto& data : DataVec)
    {
        // Do stuff with the data
        ...
    }*/
}

vector::operator=的基本线程安全保证是:

“如果抛出异常,则容器处于有效状态。”

这里有哪些类型的例外?

编辑:

我使用双缓冲解决了这个问题,并在下面发布了我的答案。

我的问题是,这种方式也会崩溃吗?

是的,你仍然有数据竞争。 如果线程A在线程B创建副本时修改了向量,则向量的所有迭代器都将失效。

这里有哪些类型的例外?

std::vector::operator=(const vector&)将抛出内存分配失败,或者如果包含的元素抛出副本。 同样的事情适用于复制构造,这是代码中标记为“ 这会崩溃吗? ”的行实际上正在做什么。


这里的根本问题是std::vector不是线程安全的。 必须使用锁/互斥锁保护它,或者用线程安全容器(例如Boost.Lockfreelibcds中 的无锁容器)替换它。

正如其他答案所指出的那样,你要求的是不可行的。 如果您有并发访问权限,则需要同步,故事结束。

话虽如此,像你这样的要求同步不是一种选择并不罕见。 在这种情况下,您仍然可以做的是摆脱并发访问 例如,您提到在执行游戏循环中每帧访问一次数据。 是否严格要求从当前帧获取数据,还是从最后一帧获取数据?

在这种情况下,您可以使用两个向量,一个由生产者线程写入,另一个由所有使用者线程读取。 在框架的最后,您只需交换两个向量。 现在您不再需要*( 1)数据访问的细粒度同步,因为不再有并发数据访问。

这只是一个如何做到这一点的例子。 如果您需要摆脱锁定,请开始考虑如何组织数据访问,以避免首先进入需要同步的情况。

*( 1) :严格来说,您仍然需要一个同步点,以确保在执行交换时,所有编写器和读取器线程都已完成工作。 但这要容易得多(通常在每个帧的末尾都有这样的同步点)并且对性能的影响远小于对每个对向量的访问进行同步。

我有一个在一个线程中修改的向量,我需要在另一个线程中使用它的内容。 由于性能要求,在这些线程之间锁定是不可接受的。

这是不可能满足的要求。

无论如何,两个线程之间的任何数据共享都需要一种锁定,无论是显式还是实现(最终是硬件)。 您必须再次检查您的实际要求:暂停一个线程直到另一个线程结束是不可接受的,但您可以锁定短序列指令。 和/或可能使用不同的架构。 例如,删除向量中的项目是一项代价高昂的操作(线性时间因为您必须将所有数据移动到已删除项目之上),而将其标记为无效则要快得多(恒定时间因为它是一次写入)。 如果你真的必须在向量的中间擦除,也许列表会更合适。

但是如果你可以在ReadThreadFunc中的向量副本周围放置一个锁定排除,并且在ReadThreadFunc任何向量修改ModifyThreadFunc ,它就足够了。 为了优先考虑修改线程,您可以尝试锁定另一个线程,如果不能,则立即放弃。

也许你应该重新考虑你的设计!

每个线程都应该有自己的向量(列表,队列,适合您的需要)来处理。 所以线程A可以做一些工作并将结果传递给thrad B.当从线程A int线程B的队列中写入数据时,你只需要锁定。

没有某种锁定就不可能。

所以我使用双缓冲来解决这个问题,它保证不会崩溃,并且读取线程将始终具有可用数据,即使它可能不正确:

struct Data
{
    int A;
    double B;
    bool C;
};

const int MAXSIZE = 100;
Data Buffer[MAXSIZE];
std::vector<Data> DataVec;

void ModifyThreadFunc()
{
    // Here the vector is changed, which includes adding and erasing elements
    ...

    // Copy from the vector to the buffer
    size_t numElements = DataVec.size();
    memcpy(Buffer, DataVec.data(), sizeof(Data) * numElements);
    memset(&Buffer[numElements],  0, sizeof(Data) * (MAXSIZE - numElements));
}

void ReadThreadFunc()
{
    Data* p = Buffer;
    for (int i = 0; i < MAXSIZE; ++i)
    {
        // Use the data
        ...
        ++p;
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM