4

Considering the following code, is it possible that the threads may see the state of an object differently, despite they both refer by the same pointer?

using namespace std;

class ProducerAndConsumer{

  class DummyObject {
  public:
    DummyObject() {
      sprintf(a, "%d", rand());
    }
  private:
    char a[1000];
  };

  mutex queue_mutex_;
  queue<DummyObject *> queue_;
  thread *t1, *t2;

  void Produce() {
    while (true) {
      Sleep(1);
      // constructing object without any explicit synchronization
      DummyObject *dummy = new DummyObject();
      {
        lock_guard<mutex> guard(queue_mutex_);
        if (queue_.size() > 1000) {
          delete dummy;
          continue;
        }
        queue_.push(dummy);
      }
    }
  }

  void Consume() {
    while (true) {
      Sleep(1);
      DummyObject *dummy;
      {
        lock_guard<mutex> guard(queue_mutex_);
        if (queue_.empty())
          continue;
        dummy = queue_.front();
        queue_.pop();
      }
      // Do we have dummy object's visibility issues here?
      delete dummy;
    }
  }

 public:

  ProducerAndConsumer() {
    t1 = new thread(bind(&ProducerAndConsumer::Consume, this));
    t2 = new thread(bind(&ProducerAndConsumer::Produce, this));
  }

};

Could you say that this example is thread safe? Do mutexes enforce cache trashing? Do mutexes provide more functionality than memory barriers together with atomics?

amir.tu
  • 83
  • 5
  • 1
    Irrelevant to the question but if the queue saturates (holds 1001 objects) you're leaking the dummy object. The recommended approach in modern C++ is `std::unique_ptr<>` but a simple `delete` before `continue` would do. – Persixty Feb 01 '16 at 11:11
  • 1
    @dan-allen, my bad, thanks for noting this) – amir.tu Feb 01 '16 at 11:29

1 Answers1

8

Considering the following code, is it possible that the threads may see the state of an object differently, despite they both refer by the same pointer?

Answer: No.

Explanation: Acquiring a mutex is an acquire operation and releasing it is a release operation.

When dummy is pushed onto the queue, the construction must take place prior to the push in order to maintain correct sequencing from the point of view of the pushing thread. The subsequent release of the mutex will ensure that a fence is issued to make the contents of the queue (and all other data you altered up until that point) visible to the other threads.

Similarly in the consumer thread, the sequencing of the assignment to dummy from the queue will be correctly ordered from the point of view of this thread. The acquisition of the mutex will ensure that memory in the DummyObject is valid.

supporting quote from §1.10.7:

A synchronization operation without an associated memory location is a fence and can be either an acquire fence, a release fence, or both an acquire and release fence.

...

For example, a call that acquires a mutex will perform an acquire operation on the locations comprising the mutex. Correspondingly, a call that releases the same mutex will perform a release operation on those same locations.

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • 1
    Which part of the question is "no" in response to? (Upvoted, as I agree with the answer, but the "no" isn't clear and could confuse the OP, if read as response to the wrong question). – Jonathan Wakely Feb 01 '16 at 11:03
  • @JonathanWakely thank you. updated the answer to make it more clear. – Richard Hodges Feb 01 '16 at 11:14