Why to pass mutex as a parameter to a function being called by a thread?

18,181

Solution 1

Mutex's are non-copyable objects, and while they can be members of a class, it would greatly complicate a parent class's copy-ability. Thus one preferred method, should a number of class instances need to share the same data, would be to create the mutex as a static data-member. Otherwise if the mutex only needed to be locked within an instance of the class itself, you could create a pointer to a mutex as a non-static data-member, and then each copy of the class would have it's own dynamically allocated mutex (and remain copyable if that is a requirement).

In the code example above, what's basically taking place is there is a global mutex being passed into the thread pool by reference. That enables all the threads sharing the same memory locations to create an exclusive lock on that memory using the exact same mutex, but without the overhead of having to manage the non-copyable aspect of the mutex itself. The mutex in this code example could have also been a static data-member of class myClass rather than a global mutex that is passed in by reference, the assumption being that each thread would need to lock some memory that is globally accessible from each thread.

The problem with a local mutex is that it's only a locally accessible version of the mutex ... therefore when a thread locks the mutex in order to share some globally accessible data, the data itself is not protected, since every other thread will have it's own local mutex that can be locked and unlocked. It defeats the whole point of mutual exclusion.

Solution 2

I believe you can have a mutex declared in the called function itself or can be declared a class member or global. Can anyone please explain?

creating a new mutex at the entry protects nothing.

if you were considering declaring a static (or global) mutex to protect non-static members, then you may as well write the program as a single threaded program (ok, there are some corner cases). a static lock would block all threads but one (assuming contest); it is equivalent to "a maximum of one thread may operate in this method's body at one time". declaring a static mutex to protect static data is fine. as David Rodriguez - dribeas worded it succinctly in another answer's comments: "The mutex should be at the level of the data that is being protected".

you can declare a member variable per instance, which would take the generalized form:

class t_object {
public:
    ...
    bool getData(t_data& outData) {
        t_lock_scope lock(this->d_lock);
        ...
        outData.set(someValue);
        return true;
    }

private:
    t_lock d_lock;
};

that approach is fine, and in some cases ideal. it makes sense in most cases when you are building out a system where instances intend to abstract locking mechanics and errors from their clients. one downside is that it can require more acquisitions, and it typically requires more complex locking mechanisms (e.g. reentrant). by more acquisitions: the client may know that an instance is used in only one thread: why lock at all in that case? as well, a bunch of small threadsafe methods will introduce a lot of overhead. with locking, you want to get in and out of the protected zones asap (without introducing many acquisitions), so the critical sections are often larger operations than is typical.

if the public interface requires this lock as an argument (as seen in your example), it's a signal that your design may be simplified by privatizing locking (making the object function in a thread safe manner, rather than passing the lock as an externally held resource).

using an external (or bound or associated) lock, you can potentially reduce acquisitions (or total time locked). this approach also allows you to add locking to an instance after the fact. it also allows the client to configure how the lock operates. the client can use fewer locks by sharing them (among a set of instances). even a simple example of composition can illustrate this (supporting both models):

class t_composition {
public:
    ...
private:
    t_lock d_lock; // << name and data can share this lock
    t_string d_name;
    t_data d_data;
};

considering the complexity of some multithreaded systems, pushing the responsibility of proper locking onto the client can be a very bad idea.

both models (bound and as member variable) can be used effectively. which is better in a given scenario varies by problem.

Share:
18,181

Related videos on Youtube

polapts
Author by

polapts

Updated on June 04, 2022

Comments

  • polapts
    polapts almost 2 years

    At some places I have seen people creating a thread pool and creating threads and executing a function with those threads. While calling that function boost::mutex is passed by reference. Why it is done so? I believe you can have a mutex declared in the called function itself or can be declared a class member or global. Can anyone please explain?

    e.g.

       myclass::processData()
       {
             boost::threadpool::pool pool(2);
             boost::mutex mutex;
    
             for (int i =0; data<maxData; ++data)
                 pool.schedule(boost::bind(&myClass::getData, boost_cref(*this), boost::ref(mutex)));
        }
    

    Then,

        myClass::getData(boost::mutex& mutex)
        {
             boost::scoped_lock(mutex)    // Why can't we have class member variable mutex or                                     
                                          //local mutex here
            //Do somethign Here
    }
    
    • David Rodríguez - dribeas
      David Rodríguez - dribeas over 12 years
      You should add a particular example where this is used. It is most probably due to the semantics that are implemented in the example, and cannot be answered without context at all (unless you want generic just because or might make sense answers)
  • David Rodríguez - dribeas
    David Rodríguez - dribeas over 12 years
    This makes no sense, there are many applications where having mutexes being non-static members make sense. Much more often than having the mutex as a static member, and there is no reason not to store a pointer or reference as a member. The mutex should be at the level of the data that is being protected (i.e. if it protects static data, it must be static, if it protects member attributes, then it probably should be a member, if it is shared by different instances or different types, then it must be even more general
  • Jason
    Jason over 12 years
    Sorry, I didn't mean to say you can't do it ... I was just stating that if you make it a non-static data-member, creating a properly copyable class is much harder (in the end it's not really going to be copyable, you would have to store a pointer to a mutex and then deallocate and reallocate the mutex in the copy, which is not a "true" copy of every member of the class). I'll revise my answer to make this clearer.
  • David Rodríguez - dribeas
    David Rodríguez - dribeas over 12 years
    ... it all depends on what the definition of your object is (semantically) In most cases the mutex does not form part of the state of the object, but is an utility to ensure that the real state of the class cannot be seen in an intermediate incorrect (invariants broken) state when used in a multithreaded environment.
  • Jason
    Jason over 12 years
    Yes, I agree, but in the code example given, while it was not explicitly expressed, it seems that because a pointer to a static myClass method was being passed to bind without a pointer to an instance of myClass, there is some data being shared between threads that would require a mutex accessible from more than a single instance of myClass.
  • kenny
    kenny over 12 years
    This rubs me wrong. If it's up to the calling class how to use the Mutex, it shouldn't not part of the interface definition?
  • polapts
    polapts over 12 years
    Thanks Justin. But, does it mean that if you want to protect static data then you need a static mutex and if you want to protect data member you need class level mutex as a general rule?
  • justin
    justin over 12 years
    as a general approach: yes, that is one of multiple usable approaches to ensuring your implementations are thread safe (another being the approach in the OP). granularity is also important if you want to minimize acquisitions and lock times. a class level lock may be too small in some cases, this will lead to many unnecessary acquisitions. a larger (global or document based) lock will not be ideal in many cases because the time locked per acquisition increases and there is a far higher likelihood that others will wait during that time.