nndhawan
nndhawan

Reputation: 617

Lists and multithreaded environments

I'm fairly new to C++ standard library and have been using standard library lists for specific multithreaded implementation. I noticed that there might be a trick with using lists that I have not seen on any tutorial/blog/forum posts and though seems obvious to me does not seem to be considered by anyone. So maybe I'm too new and possibly missing something so hopefully someone smarter than me can perhaps validate what I am trying to achieve or explain to me what I am doing wrong.

So we know that in general standard library containers are not thread safe - but this seems like a guiding statement more than a rule. With lists it seems that there is a level of tolerance for thread safety. Let me explain, we know that lists do not get invalidated if we add/delete from the list. The only iterator that gets invalidated is the deleted item - which you can fix with the following line of code:

it = myList.erase(it)

So now lets say we have two threads and call them thread 1 and thread 2.

Thread 1's responsibility is to add to the list. It treats it as a queue, so it uses the std::list::push_back() function call.

Thread 2's responsibility is to process the data stored in the list as a queue and then after processing it will remove elements from the list.

Its guaranteed that Thread 2 will not remove elements in the list that were just added during its processing and Thread 1 guarantees that it will queue up the necessary data well ahead for Thread 2's processing. However, keep in mind elements can be added during Thread 2's processing.

So it seems that this is a reasonable use of lists in this multithreaded environment without the use of a locks for data protection. The reason why I say its reasonable is because, essentially, Thread 2 will only process data up to now such that it can retreive the current end iterator shown by the following pseudocode:

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

Thread 2 uses a lock for a very short amount of time just to know where to stop processing, but for the most part Thread 1 and Thread 2 don't require any other locking. In addition, Thread 2 can possibly avoid locking too if its scope to know the current last element is flexible.

Does anyone see anything wrong with my suggestion?

Thanks!

Upvotes: 3

Views: 1546

Answers (1)

James McNellis
James McNellis

Reputation: 355167

Your program is racy. As an example of one obvious data race: std::list is more than just a collection of doubly-linked nodes. It also has, for example, a data member that stores the number of nodes in the list (it needs not be a single data member, but it has to store the count somewhere).

Both of your threads will modify this data member concurrently. Because there is no synchronization of those modifications, your program is racy.

Instances of the Standard Library containers cannot be mutated from multiple threads concurrently without external synchronization.

Upvotes: 5

Related Questions