C++ STL与多线程

C++ STL与多线程,c++,stl,C++,Stl,我知道,当我在多个线程中对单个STL容器执行操作时,我需要使用互斥体。但是我想知道这条规则是否有例外。请考虑我正在尝试实现的简化场景。 我有多个线程将元素添加到容器中,操作被互斥锁/解锁包围。然后线程以某种方式通知(例如,在linux上使用eventfd)专用于调度此容器中元素的单个线程。我想做的是访问容器中的第一个元素,而不使用互斥体。基于deque的示例代码,但请注意,我可以使用任何具有队列功能的容器: std::mutex locker; std:deque<int>

我知道,当我在多个线程中对单个STL容器执行操作时,我需要使用互斥体。但是我想知道这条规则是否有例外。请考虑我正在尝试实现的简化场景。

我有多个线程将元素添加到容器中,操作被互斥锁/解锁包围。然后线程以某种方式通知(例如,在linux上使用eventfd)专用于调度此容器中元素的单个线程。我想做的是访问容器中的第一个元素,而不使用互斥体。基于deque的示例代码,但请注意,我可以使用任何具有队列功能的容器:

std::mutex     locker;
std:deque<int> int_queue;
int            fd; // eventfd
eventfd_t      buffer;
bool           some_condition;
专用于分派元素的线程:

while (true)
{
    bool some_condition (true);

    locker.lock ();
    if (int_quque.empty () == false)
    {
        locker.unlock ();
    }
    else
    {
        locker.unlock ();
        eventfd_read (fd, &buffer);
    }

    while (some_condition)
    {
        int& data (int_queue.front ());

        some_condition = some_operation (data); // [1]
    }

    locker.lock ();
    int_queue.pop ();
    locker.unlock ();
}
[1] 我将多次对Single元素执行一些_操作(),这就是为什么我希望在这里避免互斥锁。太贵了


我想知道这个代码是否会导致任何同步问题或什么。

< P>我不能真正地看到你的问题或代码,但是一般来说,标准C++库中的容器为你提供了一个松散的保证,即在不同元素上的并发访问是线程安全的。但是,一定要理解其含义和局限性:如果您有一个随机访问容器或元素的迭代器,并且您只使用这些容器或迭代器来读取或更改元素值,那么只要您在不同的元素上这样做,结果应该是定义良好的。不好的是更改容器本身,因此必须序列化任何擦除或插入操作(例如,通过锁定对整个容器的访问),并确保在执行此操作时了解容器的迭代器和引用无效规则

对于单个容器,您可能能够说得更多一些——例如,在基于树的容器中插入/擦除,并且在随机访问容器的中间插入/擦除几乎肯定需要全局锁。在vector/deque中,您需要重新获取迭代器。在列表中,您可以在不同的位置同时执行插入


任何全局操作,如
size()
empty()
也需要序列化。

对于此特定示例,这是不安全的

当引用第一个元素时,它可能会被另一个线程移动,该线程将元素添加到队列中,迫使它重新分配(DEQUE通常被实现为“环绕”数组)。如果复制值而不是引用,则根据实现的不同,您可能会侥幸逃脱。如果您想做到这一点,std::deque不会附带任何标准的“例外”。当然,在安全的情况下,可以编写类似于deque的数据结构,但不能保证deque的编写方式是那样的(也不一定是那样写的)。

您需要的是引用稳定性。也就是说,当容器被推回时,如果对第一个元素的引用没有失效,您可以这样使用容器。即使这样,您也需要获得锁下的front元素的引用

我更熟悉事件通知的
std::condition_variable
,因此我将使用它:

#include <mutex>
#include <condition_variable>
#include <deque>

std::mutex              locker;
std::deque<int>         int_queue;
std::condition_variable cv;

void thread_1_2_3()
{
    // use lock_guard instead of explicit lock/unlock
    //    for exception safety
    std::lock_guard<std::mutex> lk(locker);
    int_queue_.push_back(1);
    cv.notify_one();
}

void dispatch()
{
    while (true)
    {
        bool some_condition = true;
        std::unique_lock<std::mutex> lk(locker);
        while (int_queue.empty())
            cv.wait(lk);
        // get reference to front under lock
        int& data = int_queue.front();
        lk.unlock();
        // now use the reference without worry
        while (some_condition)
            some_condition = some_operation(data);
        lk.lock();
        int_queue.pop_front();
    }
}
#包括
#包括
#包括
互斥锁锁;
std::deque int_队列;
std::条件变量cv;
无效线程_1_2_3()
{
//使用锁定/解锁保护,而不是显式锁定/解锁
//例外安全
标准:锁紧装置lk(储物柜);
内部队列向后推(1);
cv.通知_one();
}
无效分派()
{
while(true)
{
bool some_condition=true;
标准:唯一锁lk(锁柜);
while(int_queue.empty())
cv.等待(lk);
//在锁定状态下获取前参考
int&data=int_queue.front();
lk.unlock();
//现在不用担心就使用引用
while(某些情况)
某些条件=某些操作(数据);
lk.lock();
int_queue.pop_front();
}
}
23.3.3.4[deque.modifiers]是关于
推回的:

deque两端的插入将使所有迭代器无效 但不影响引用的有效性 德克的元素

这是允许您在锁外保持引用的关键。如果<代码> thRead 1x2y3在中间开始插入或擦除,则不能再挂在该引用上。


不能以这种方式使用
向量。但是你可以这样使用
列表。检查每个容器的参考稳定性。为什么要这样做?为什么使用者线程不提取锁内的对象,然后在带外处理它

假设您想要避免的是必须将对象复制到容器之外,一种更简单、更易于维护的方法可以是动态分配对象,使用一个包含(智能)指针的容器并在锁中提取它(成本最低)。那么你就不再需要考虑线程安全问题了。
请注意,即使您可以在这个特定场景中实现这一点,也不能使用多个使用者线程。我建议您不要使用这种方法,而只需找到一种不同的方法,在这种方法中,您可以满足您的需求,而不必走到最前沿。多线程很难正确执行,而且很难调试甚至检测到存在问题。通过遵循常见模式,您的代码更易于推理和维护。

如果您确实想要无锁队列,我还建议您查看没有
stl::mutex
stl:deque
——您指的是什么编译器/库/标准?这里没有快捷方式,需要锁定。如果需要,请不要执行
(condition==false)
这只会影响可读性。我知道他引用了标准中的内容(不管怎样,是N3242草案),显然我弄错了。我正在研究
int& data (int_queue.front ());
#include <mutex>
#include <condition_variable>
#include <deque>

std::mutex              locker;
std::deque<int>         int_queue;
std::condition_variable cv;

void thread_1_2_3()
{
    // use lock_guard instead of explicit lock/unlock
    //    for exception safety
    std::lock_guard<std::mutex> lk(locker);
    int_queue_.push_back(1);
    cv.notify_one();
}

void dispatch()
{
    while (true)
    {
        bool some_condition = true;
        std::unique_lock<std::mutex> lk(locker);
        while (int_queue.empty())
            cv.wait(lk);
        // get reference to front under lock
        int& data = int_queue.front();
        lk.unlock();
        // now use the reference without worry
        while (some_condition)
            some_condition = some_operation(data);
        lk.lock();
        int_queue.pop_front();
    }
}