Search code examples
multithreadingglib

Acquire a read/write lock if not already held


In GLib, is there an operation to tell it “acquire the lock if you don’t hold it already”? Can the same thread acquire a lock twice (making the second acquisition a no-op, or requiring it to be released twice) or test if it is already holding a particular lock?

Suppose I have the following functions in my code:

void func_a() {
    //g_rw_lock_writer_lock(&shared_data->rw_lock);
    mess_with_data(shared_data);
    func_b();
    //g_rw_lock_writer_unlock(&shared_data->rw_lock);
}

void func_b() {
    //g_rw_lock_writer_lock(&shared_data->rw_lock);
    mess_with_data_again(shared_data);
    //g_rw_lock_writer_unlock(&shared_data->rw_lock);
}

Assume that:

  • shared_data points to a shared data structure, and access needs to be synchronized between threads
  • shared_data->rw_lock is the read/write lock to synchronize access
  • Both func_a() and func_b() can be called from outside
  • mess_with_data() and mess_with_data_again() are not thread-safe, thus the caller needs to hold a write lock on the data before calling them
  • They are not just single function calls but rows of statements, so copying the body of func_b() into func_a() is not an option (code duplication, poor maintainability)
  • The callers of func_a() and func_b() have no direct access to the lock, therefore locking needs to happen under the hood
  • Extracting the function body of func_b() (sans the locking/unlocking) into a separate helper function called by both func_a() and func_() is not an option (they are spread out across multiple modules and there are layers of abstraction between the function calls—in fact, func_a() does not directly call func_b() by name but a pointer which happens to resolve to func_b()).

How would I solve this?


Solution

  • First things first: the word you're looking for is "recursive".

    While GMutex explicitly mentions that whether or not the mutex is recursive is undefined, AFAIK GRWLock just omits any mention for whether the writer lock is recursive (the reader side is recursive).

    If you dive into the implementation a bit, you'll see that on POSIX GRWLock is implemented using a pthread_rwlock_t, which needn't be recursive ("Results are undefined if the calling thread holds the read-write lock (whether a read or write lock) at the time the call is made."). So basically no, GRWLock isn't recursive for writer locks.

    As for how to solve your problem, my first suggestion would be to have mess_with_data and mess_with_data_again acquire and release the lock themselves. Remember, you should only hold locks for as long as necessary and no longer.

    If that's not an option for some reason (like maybe you don't have access to that code), you could use a lock that is recursive, or restrict writer operations to one thread and use a queue to communicate with it.

    It may also be possible to refactor mess_with_data and mess_with_data_again so they don't require locks, but that may or may not be possible and would likely be quite difficult.