Steveng
Steveng

Reputation: 1521

Threading C++ lock

I need some synchronous mechanism for thread. I am wondering the implementation below which one is a better way?

classA{

public:
   int sharedResourceA;
   pthread_mutex_t mutex1;
   functionA();
   int nonSharedResources;

}

classA::functionA(){
  pthread_mutex_lock( &mutex1 );
  use sharedResourceA;
  pthread_mutex_unlock( &mutex1 );
}

classA objA;
pthread_mutex_lock(&objA.mutex1) //use lock because another thread can call obj.functionA
  use objA.sharedResources;
pthread_mutex_unlock(&objA.mutex1)

use objA.nonSharedResources = blah //without lock because is non shared

OR I shouldn't create a lock at classA, instead I create a lock at the application. Eg:

classA objA;
pthread_mutex_t mutex2;
pthread_mutex_lock(mutex2) //use lock because another thread can call obj.functionA
  use objA.sharedResources;
pthread_mutex_unlock(mutex2)

pthread_mutex_lock(mutex2) //use lock because another thread can call obj.functionA
  functionA();
pthread_mutex_unlock(mutex2)

use objA.nonSharedResources = blah //without lock because is non shared

Upvotes: 0

Views: 4865

Answers (3)

pm100
pm100

Reputation: 50110

First - the idiomatic way for doing locks in c++ is to create a lock class that uses RAII.

Then you can go

Lock l(mutex1);
// do stuff under mutex1 lock;
// Lock is freed at end of scope

(I bet boost has a lock, we made our own)

Second. (the scope question). If class A uses shared resources internally then it should lock them internally. Otherwise

  1. how does a caller know to do it
  2. how can you be sure they did it
  3. what if you change the implementation

The application level lock should be used when the caller is the one using the shared resources and is composing something larger that uses classA, funcX and file W. Note that classA may still have its own internal lock in this case

Upvotes: 4

Jarek
Jarek

Reputation: 11

If functionA uses some shared resources, it should ensure that it's accessing them in correct way - i.e. ensure thread safety. That is a vote for the first option you presented.

There are more efficient ways to use mutexes: see boost::recursive_mutex and boost::recursive_mutex::scoped_lock. Using this you can ensure that even if something in critical section throws, your mutex will be unlocked. For example:

using namespace boost;

struct C

{

function f ()
{
    //non critical section
    //...

    //critical section        
    {
        //acquire the mutex
        recursive_mutex::scoped_lock lock(mutex);
       //do whatever you want. Can throw if it needs to:)
    }//exiting the scope causes the mutex to be released     

   //non critical section again

}

private:

recursive_mutex mutex;

}

Upvotes: 1

Eric Fortin
Eric Fortin

Reputation: 7603

I would say the first one is better because if you need to instantiate ClassA more than once, you'll need do create as many global locks for the second solution.

It also respect object encapsulation if you do it inside the class and hides usage of the protected resource behind method. Also, if the shared resource ever becomes not shared, you have the class methods to change in the code instead of refactoring each and every usage of the resource if you use global locks.

Upvotes: 0

Related Questions