Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can mutexes ensure thread visibility of objects while not explicitly protecting them?

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?

like image 503
amir.tu Avatar asked Feb 07 '23 21:02

amir.tu


1 Answers

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.

like image 119
Richard Hodges Avatar answered Feb 10 '23 15:02

Richard Hodges