0

If I set the value of a variable in one thread and read it in another, I protect it with a lock to ensure that the second thread reads the value most recently set by the first:

Thread 1:

lock();
x=3;
unlock();

Thread 2:

lock();
<use the value of x>
unlock();

So far, so good. However, suppose I have a c++ object that sets the value of x in an initializer:

theClass::theClass() : x(3) ...
theClass theInstance;

Then, I spawn a thread that uses theInstance. Is there any guarantee that the newly spawned thread will see the proper value of x? Or is it necessary to place a lock around the declaration of theInstance? I am interested primarily in c++ on Linux.

1

3 Answers 3

0

Prior to C++11, the C++ standard had nothing to say about multiple threads of execution and so made no guarantees of anything.

C++11 introduced a memory model that defines under what circumstances memory written on one thread is guaranteed to become visible to another thread.

Construction of an object is not inherently synchronized across threads. In your particular case though, you say you first construct the object and then 'spawn a thread'. If you 'spawn a thread' by constructing an std::thread object and you do it after constructing some object x on the same thread then you are guaranteed to see the proper value of x on the newly spawned thread. This is because the completion of the thread constructor synchronizes-with the beginning of your thread function.

The term synchronizes-with is a specific term used in defining the C++ memory model and it's worth understanding exactly what it means to understand more complex synchronization but for the case you outline things 'just work' without needing any additional synchronization.

This is all assuming you're using std::thread. If you're using platform threading APIs directly then the C++ standard has nothing to say about what happens but in practice you can assume it will work without needing a lock on any platform I know of.

2
  • You are confusing the OP unneccessary. There is no way for him to access the object before it is constructed, so 'memory models' (the term you are using incorrectly, the proper one is memory ordering, models are from around 1994 or so) are irrelevant.
    – SergeyA
    Sep 18, 2015 at 21:48
  • @SergeyA I am using the term memory model correctly. Sep 18, 2015 at 21:52
0

You seem to have a misconception on locks:

If I set the value of a variable in one thread and read it in another, I protect it with a lock to ensure that the second thread reads the value most recently set by the first.

This is incorrect. Locks are used to prevent data races. Locks do not schedule the instructions of Thread 1 to happen before the instructions of Thread 2. With your lock in place, Thread 2 can still run before Thread 1 and read the value of x before Thread 1 changes the value of x.

As for your question:

  1. If your initialization of theInstance happens-before the initialization/start of a certain thread A, then thread A is guaranteed to see the proper value of x.

Example

#include <thread>
#include <assert.h>
struct C
{
    C(int x) : x_{ x } {}
    int x_;
};

void f(C const& c)
{
    assert(c.x_ == 42);
}

int main()
{
    C c{ 42 };                       // A
    std::thread t{ f, std::ref(c) }; // B
    t.join();
}

In the same thread: A is sequenced-before B, therefore A happens-before B. The assert in thread t will thus never fire.

  1. If your initialization of 'theInstance' inter-thread happens-before its usage by a certain thread A, then thread A is guaranteed to see the proper value of x.

Example

#include <thread>
#include <atomic>
#include <assert.h>

struct C
{
    int x_;
};

std::atomic<bool> is_init;

void f0(C& c)
{
    c.x_ = 37;               // B
    is_init.store(true);     // C
}

void f1(C const& c)
{
    while (!is_init.load()); // D

    assert(c.x_ == 37);      // E
}

int main()
{
    is_init.store(false); // A
    C c;
    std::thread t0{ f0, std::ref(c) };
    std::thread t1{ f1, std::ref(c) };
    t0.join();
    t1.join();
}

The inter-thread happens-before relationship occurs between t0 and t1. As before, A happens-before the creation of threads t0 and t1.

The assignment c.x_ = 37 (B) happens-before the store to the is_init flag (C). The loop in f1 is the source of the inter-thread happens-before relationship: f1 only proceeds once is_init is set, therefore C happens before E. Since these relationships are transitive, B inter-thread happens-before D. Thus, the assert will never fire in f1.

-1

First of all, your example above doesn't warrant any locks. All you need to do is to declare your variable atomic. No locks, no worries.

Second, your question does not really make a lot of sence. Since you can not use your object (instance of the class) before it is constructed, and construction is happening within single thread, there is no need to lock anything which is done in class constructor. You simply can not access non-constructed class from multiple threads, it is impossible.

4
  • It is possible to observe a partially constructed object from another thread if you do not have correct synchronization. Sep 18, 2015 at 22:33
  • I do not see how it is possible. The object has to be constructed before it can be used, and only after that it can be used in another object. Obviously, if you are talking about some sort of 'sending' address of this object to another thread, and this 'sending' is not atomic on it's own, than the object state will be in disarray. But that means the messaging is broken. With sound messaging it is impossible.
    – SergeyA
    Sep 18, 2015 at 22:58
  • But that's exactly what the question is asking about (at least as I understand it) - is it safe to access an object through a 'published' address created on another thread without explicit synchronization. In general the answer is no, but in this case the creation of the thread itself serves as sufficient synchronization to ensure that the 'publish' is visible even if it is just a regular (non-atomic) pointer assignment. Sep 18, 2015 at 23:06
  • I did not read it this way, and the question (do I need to a lock around declaration of instance) did not indicate this to me. In no scenario the lock is needed around instance creation. Proper mechanism of thread communications are required all the time.
    – SergeyA
    Sep 18, 2015 at 23:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.