2

I have always found the Java synchronised statements to be a clean way of doing mutex like lock and unlocks:

public void addName(String name) {
    synchronized(this) {
        lastName = name;
        nameCount++;
    }
    nameList.add(name);
}

Though the fundamental concept of monitors used in Java and pthread mutexes are different, pthread mutexes at their most basic are often used as:

void addName(char* name) {

    int status = -1;

    status = pthread_mutex_lock(this->lock);
    if (status == 0) {
        this->lastName = name;
        this->nameCount++;
        pthread_mutex_unlock(this->lock);
    }

    nameList->add(name);
}

I understand that the above code doesn't really exploit the capabilities of pthread mutexes. Nor does it handle all the error scenarios. However, this is probably the most common way of using pthread mutexes. Saying that, I think it would be nice to have a cleaner idiom for such synchronisations:

public void addName(char* name) {
    synchronized(this->lock) {
        this->lastName = name;
        this->nameCount++;
    }
    nameList.add(name);
}

So is it possible to do this in C, C++?

tinkerbeast
  • 1,707
  • 1
  • 20
  • 42

4 Answers4

1

The classical approach is a lock object. A small object whose constructor acquires a mutex, and whose destructor releases it. Instantiating the lock object inside a scope has the same effect as a synchronized scope in Java.

C++11 already has all that's needed to do this, in the form of std::mutex or a std::recursive_mutex and std::unique_lock.

The following approach will end up having pretty much the same result as a synchronized method in java:

1) Declare a std::recursive_mutex class member

2) Acquire the mutex in the method.

Example:

class whatever {

private:

    std::recursive_mutex s_mutex;

// ... The rest of the class definition.

public:

// ...
    void addName();

// ...
};

void whatever::addName()
{
    std::unique_lock<std::recursive_mutex> s_lock(s_mutex);

// ... the rest of the method
}

This doesn't have to be done on the entire method scope:

void whatever::addName(char* name)
{
    {
        std::unique_lock<std::recursive_mutex> s_lock(s_mutex);

        this->lastName = name;
        this->nameCount++;
    }
    nameList.add(name);
}

To summarize:

A) Update to C++11, which offers the C++ versions of POSIX mutexes and locks.

B) If you can't update to C++11, write your own mutex and lock methods. You want to avoid doing explicit pthread_mutex_lock and unlock each and every time, yourself. It's easy to forget to unlock the mutex, explicitly, on some exit path, and end up with a mess. C++ will do it for you, if you properly wrap them up into class methods.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • +1 and i wish i could uptick this again for noting that Java `synchronized` object methods are reentrant from the same thread. A standard `std::mutex` would hang on a reentrance from the same thread, whereas a `std::recursive_mutex` is more inline with how Java synchronization works. [Reference to Java synchronization](http://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html) (see the bottom section on reentrancy). – WhozCraig Oct 03 '14 at 12:26
0

We can have something like:

#define SYNC_R rwlock_rd
#define SYNC_W rwlock_wr
#define SYNC_S spin_
#define SYNC_M mutex_

#define pthread_rwlock_wrunlock pthread_rwlock_unlock
#define pthread_rwlock_rdunlock pthread_rwlock_unlock

#define __SYNC_X__(lockObj, lockType) for(int __sync_status__=!pthread_##lockType##lock(lockObj); __sync_status__ ; pthread_##lockType##unlock(lockObj), __sync_status__=0)

#define __SYNC_M__(lockObj) for(int __sync_status__=!pthread_mutex_lock(lockObj); __sync_status__ ; pthread_mutex_unlock(lockObj), __sync_status__=0)

#define __SYNCALIAS__(_1, _2, NAME, ...) NAME

#define __SYNC__(...) __SYNCALIAS__(__VA_ARGS__, __SYNC_X__, __SYNC_M__)(__VA_ARGS__)

#define synchronized(...) __SYNC__(__VA_ARGS__)

This allows us to have two flavours of synchronization blocks - The first one is just for mutexes:

synchronized(this->lock)
// ==> __SYNC__(this->lock)
// ==> __SYNCALIAS__(this->lock, __SYNC_X__, __SYNC_M__)(this->lock)
// ==> __SYNC_M__(this->lock)
// ==> for(int __sync_status__=!pthread_mutex_lock(lockObj); __sync_status__ ; pthread_mutex_unlock(lockObj))
{
    this->lastName = name;
    this->nameCount++;
}

Alternatively we can use rwlocks, spinlocks or even mutexes with an additional parameter: -

synchronized(this->rwlock, SYNC_W)
// ==> __SYNC__(this->rwlock, rwlock_wr)
// ==> __SYNCALIAS__(this->rwlock, rwlock_wr, __SYNC_X__, __SYNC_M__)(mm, rwlock_rd)
// ==> __SYNC_X__(this->rwlock, rwlock_wr)
// ==> for(int __sync_status__=!pthread_rwlock_wrlock(lockObj); __sync_status__ ; pthread_rwlock_wrunlock(lockObj))
// ==> for(int __sync_status__=!pthread_rwlock_wrlock(lockObj); __sync_status__ ; pthread_rwlock_unlock(lockObj))
{
    this->lastName = name;
    this->nameCount++;
}

The problem with this is that it doesn't handle the error scenarios cleanly. So maybe there's a better way to do it. Though this one works out for both C and C++, C++ will probably have it's own brand of solutions. GCC extensions was another avenue I didn't explore.

Also made a reference implementation at sync.h, sync.c.

tinkerbeast
  • 1,707
  • 1
  • 20
  • 42
0

I find std::lock_guard() can be used similarly to Java's synchronized function:

class MyType
{
    std::mutex mtx;

public:

    void synced_functionA()
    {
        std::lock_guard<std::mutex> lock(mtx);

        // synchronized code
    }

    void synced_functionB()
    {
        std::lock_guard<std::mutex> lock(mtx);

        // synchronized code
    }
};
Galik
  • 47,303
  • 4
  • 80
  • 117
0

In C++, RAII gives something almost as neat as a synchronized block, and since C++11 there's a standard thread library using that:

{
    std::lock_guard<std::mutex> lock(mutex);
    lastName = name;
    nameCount++;
} // lock released here

nameList.add(name);

In C, you'll have to manually acquire and release the lock as you describe; it's a much simpler language that doesn't support automatic resource management.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644