Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

STL and multithread

Tags:

c++

stl

I'm aware, that I need to use mutex, when I perform operations on single STL container inside multiple threads. However I want to know if there are any exceptions from this rule. Please consider simplified scenario I'm trying to implement.

I have multiple threads adding elements to container and operation is surrounded with mutex lock/unlock. Then threads notify somehow (e.g. using eventfd on linux) single thread dedicated to dispatch elements in this container. What I want to do is to access first element in container without using mutex. Sample code based on deque but note that I ca use any container with queue capability:

std::mutex     locker;
std:deque<int> int_queue;
int            fd; // eventfd
eventfd_t      buffer;
bool           some_condition;

Thread 1, 2, 3, etc.

locker.lock ();
int_queue.push_back (1);
locker.unlock ();

eventfd_write (fd, 1);

Thread dedicated to dispatch elements:

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] I will do some_operation() on signle element many times, that's why I want to avoid mutex locking here. It's to expensive.

I want to know if this code can lead to any synchronisation problems or something.

like image 825
Goofy Avatar asked Nov 22 '25 04:11

Goofy


1 Answers

What you need is reference stability. I.e. you can use containers this way if the reference to the first element is not invalidated when the container is push_back'd. And even then, you'll want to obtain the reference to the front element under the lock.

I'm more familiar with std::condition_variable for the event notification, so I'll use that:

#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();
    }
}

23.3.3.4 [deque.modifiers] says this about push_back:

An insertion at either end of the deque invalidates all the iterators to the deque, but has no effect on the validity of references to elements of the deque.

That is the key to allowing you to hang onto that reference outside of the lock. If thread_1_2_3 starts inserting or erasing in the middle, then you can no longer hang on to this reference.

You can't use a vector this way. But you could use a list this way. Check each container you want to use this way for reference stability.

like image 142
Howard Hinnant Avatar answered Nov 24 '25 20:11

Howard Hinnant



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!