Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Untouched shared resources in C++ threading

Imagine the following scenario:

#include <chrono>
#include <iostream>
#include <thread>
#include <vector>

void DoSomething(int* i)
{
    std::cout << *i << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    *i = 2;
    std::cout << *i << std::endl;
}

int main()
{
    std::vector<int> v = {0, 0, 0};
    v[0] = 1;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::thread t(&DoSomething, &v[0]);
    t.join();
    std::cout << v[0] << std::endl;
}

Is there any reason that there should be a mutex passed along with the vector element?

P.D. from 08/May/2015

I did not elaborate on the question more when posting, since I didn't want to influence the answer. What you've answered would have been pretty much my understanding till yesterday. However, it has been suggested to me that intuitive behaviour might not be serviceable in threading scenarios as much as one would hope. Particularly, in this case, it has been suggested that, for instance, the assumption that the writing v[0] = 1 happening on the main thread is something that, without a mutex, will be reflected when printing in DoSomething is not guaranteed. As a possible explanation I was offered that the value might go into thread accessible memory, but that it might be swapped out by the state of a different thread before it is written to cross-thread memory, and again, that the only way of guaranteeing the desired propagation would be using a mutex. What is your opinion on that argument?

like image 794
Some Dude Avatar asked Mar 17 '23 03:03

Some Dude


1 Answers

A mutex is used for synchronizing data access between multiple threads. If two or more threads access the same data and at least one of them is a writer, they need to synchronize.

In your case, DoSomething does write to the element you pass to it. However, in your current example, the thread that does the write is the only thread accessing the element at that time, since the main thread is immediately blocked in the following join.

If however the main thread would access v concurrently, you would need to protect that access:

// BROKEN CODE BELOW

int main()
{
    std::vector<int> v = {0, 0, 0};
    std::thread t(&DoSomething, &v[0]);
    // swapped the join and the cout; now cout accesses v[0] 
    // while DoSomething writes to it, which is bad;
    std::cout << v[0] << std::endl;
    t.join();
}

As for your edit: You are basically worried that the initial assignment of v[0] might get reordered, so it will not be visible to the DoSomething thread. In principle, writes can get reordered in ways that concurrent threads will observe different values than one might expect intuitively. The only way to prevent this is to insert memory barriers which explicitly enforce a certain ordering. In C++ this usually happens via thread synchronization primitives like std::mutex or atomics.

Fortunately, the creation of a thread acts as such a memory barrier. Therefore in your example, where the initial write to v happens before the creation of the thread, everything is fine and you will not experience any harmful reordering.

like image 69
ComicSansMS Avatar answered Mar 29 '23 23:03

ComicSansMS