About mutexes.

About mutexes.

Hi,
I have some questions about the use of mutexes.

1) Having this code, is it safe to concurrently invoke the method DoObject from the global resource in each child task to modify the state of the local objects? I have tried it several times and ended always getting the expected result. However, can the program in some run yield and incorrect result since the resource is not protected by a mutex? Isn't threre a data race that can corrupt the state of the objects?

class Resource {
public:
  Resource(){}
  void DoObject(MyObject* o) {
    o->AddX( o->GetY() );
    o->AddY( o->GetX() );
  }
}

class MyObject{
private:
  int x;
  int y;
public:
  MyObject( ... ):...{}
  // getters and setters here
}

class Child: public tbb::task {
private:
  Myobject* local_object;
  Resource* global_resource
public:
  Child(...):...{}
  tbb::task* execute() {
    global_resource->DoObject( local_object );
   return NULL;
}
}
class Root: public tbb::task {
  // spawn several child tasks, i.e. 2
}

int main() {
  Resource* res = new Resource;
  MyObject* o1 = new Object( ... );
  MyObject* o2 = new Object( ... ); 
  Root& r = *new(tbb::task::allocate_root()) Root( o1, o2, res );
  tbb::task::spawn_root_and_wait( r );
}

2) If a mutex is needed, where is the most appropiate place to put it.
Here?:

typedef tbb::spin_mutex MutexType;

class Resource {
  private:
   /// more things
   MutexType Mutex;
  public:
   /// more things
   void DoObject(MyObject* o) {
    MutexType::scoped_lock lock( Mutex );
    /// do the work
   }
}

or here?:

typedef tbb::spin_mutex MutexType;

class Resource {
  private:
   /// more things
   MutexType Mutex;
  public:
   /// more things
   void DoObject(MyObject* o){
    /// do the work as always
   }
   MutexType& GetMutex() const { return Mutex; }
};

class Child: public tbb::task {
 /// more things
 Resource* global_resource;
public:
  /// more things
  tbb::task* execute() {
   MutexType::scoped_lock lock( global_resource->GetMutex() );
   global_resource->DoObject( local_object );
   return NULL;
  }
};

Thanks in advance.

7 posts / 0 new
Last post
For more complete information about compiler optimizations, see our Optimization Notice.

In the example provided, DoObject only affects the specified object. By placing the mutex within the object, multiple different objects can be locked and worked on simultaneously. A global resource lock may also be required for other functions that manage multiple (all) objects in one session. You also might want to consider having DoObject not lock and providing DoObjectWithLock that does:

lock, DoObject(p1), unlock
or
global lock, DoObject(p1), DoObject(p2), DoObject(pn), global unlock

Jim Dempsey

www.quickthreadprogramming.com

Hi,
thanks for your answer Jim.

"By placing the mutex within the object"


Do you mean having a mutex as a private member of the class, like in Resource?

"multiple different objects can be locked and worked on simultaneously"


So multiple Resources could be working simultaneosly, is that?
But if there is something like this:

Resource* res = new Resource;
std::vector v;
// fill vector with pointers

for(unsigned int i(0); iDoObject( v.at(i) );
}

This should be ok with and without a mutex, as you stated in your answer.

Or do you mean something like:

class MyObject {
private:
  MutexType Mutex;
public:
  ///
};


"A global resource lock may also be required"

Like this?

typedef tbb::spin_mutex MutexType;
MutexType GlobalMutex;

class ... {
/// more things
};

class ... {
/// more things
};

Or declared in, for example, a header file that will be included by others?

Sorry about my questions but I find kinda difficult this topic.

Thanks.

When processing a single vector of object pointers as outlined in the first example you need not have locks within the objects as long as only one such object is processed by any one thread at any one time. Note, a parallel_for would work on different slices of the vector and concurrent work would be fine as long as noobjectis worked onconcurrently by two threads witout lock or other protection (e.g. atomic update). An example of this could be a vector of objects interacting via gravity or charge where the parallel_for of a subset of the vector upon a secondary reference to thevector would introduce concurrent updates to objects. In these cases either use object mutex or zone exclusions without mutex.

The purpose of the mutex is to avoid something like

A = A + X

Where the operation is concurrent with multiple threads .AND. not performed atomicly or within a protected critical section (locked by mutex).

If you can avoid mutex and be thread safe, please do so as it generaly offers better performance.

Jim Dempsey

www.quickthreadprogramming.com

Hi!
Thanks for your answers, Jim.

This is the last question!

While searching the forums I found this: http://software.intel.com/en-us/forums/showthread.php?t=64849&o=a&s=lr

In the code of Anton's answer(http://software.intel.com/en-us/forums/showpost.php?p=82360), the mutex is declared as public. Is there a special reason for that? If I declare the mutex as private and have a function like

CurrentMutexType& GetMutex(void) const { return currentMutex; }

wouldn't that work as well?

And, why the static keyword?

Thanks!!

If I may step in while Jim has nighttime in his timezone, GetMutex() would work as well but seems a bit pedantic, and that particular mutex is static because it is not about protecting instances of A but about concurrent access to std::cout across instances of A (in addition to the possibly concurrent use of the operator per instance).

Thank you Raf!!

Leave a Comment

Please sign in to add a comment. Not a member? Join today