views:

6527

answers:

9

I'm looking for a good reader/writer lock in C++. We have a use case of a single infrequent writer and many frequent readers and would like to optimize for this. Preferable I would like a cross-platform solution, however a Windows only one would be acceptable.

A: 

You could copy Sun's excellent ReentrantReadWriteLock. It includes features such as optional fairness, lock downgrading, and of course reentrancy.

Yes it's in Java, but you can easily read and transpose it to C++, even if you don't know any Java. The documentation I linked to contains all the behavioral properties of this implementation so you can make sure it does what you want.

If nothing else, it's a guide.

Jason Cohen
Java and C++ are soo different in style thats probably not a good idea. Java threading builtin to the language C++ does not. The code (directly translated) is not exception safe.
Martin York
That's completely untrue. In fact, Java's intstruction reordering rules are *more* permissive than most C++ compilers, which means it's *safer*. Your statement that "threading is in the language" is moot because the thread behavior is clearly defined as a standard "mutex" and "lock" system which all C++ threading systems that I've ever used also have, so the translation would indeed be simple.
Jason Cohen
@Jason - I think you're missing the point of exception-safety. Java doesn't use RAII idiomatically, and C++ doesn't have `finally`, so a direct translation from Java to C++ is a bad idea in the presence of excepions.
Tom
+10  A: 

Newer versions of boost::thread Have read/write locks (1.35.0 and later, apparently the previous versions did not work correctly).

They have the names shared_lock, unique_lock, and upgrade_lock and operate on a shared_mutex.

Greg Rogers
We're on 1.34.1, that might be a reason to move up :)
Matt Price
The thread library underwent a massive overhaul from 1.34 -> 1.35 so be aware of this when upgrading. Some breaking API changes were made, but they wont affect most people very much. The new version is more in line with the proposed C++0x library interface, which is a good thing in the long run.
Greg Rogers
We don't use most of the threading library to heavily, but we do use it for our locks. I did bust into the details to specialize lock_ops in one place so I could use scoped_lock with my type. I agree that in the long run we want the more standard implementation.
Matt Price
+8  A: 

Using standard pre-tested, pre-built stuff is always good (for example, Boost as another answer suggested), but this is something that's not too hard to build yourself. Here's a dumb little implementation pulled out from a project of mine:

#include <pthread.h>

struct rwlock {
    pthread_mutex_t lock;
    pthread_cond_t read, write;
    unsigned readers, writers, read_waiters, write_waiters;
};

void reader_lock(struct rwlock *self) {
    pthread_mutex_lock(&self->lock);
    if (self->writers || self->write_waiters) {
        self->read_waiters++;
        do pthread_cond_wait(&self->read, &self->lock);
        while (self->writers || self->write_waiters);
        self->read_waiters--;
    }
    self->readers++;
    pthread_mutex_unlock(&self->lock);
}

void reader_unlock(struct rwlock *self) {
    pthread_mutex_lock(&self->lock);
    self->readers--;
    if (self->write_waiters)
        pthread_cond_signal(&self->write);
    pthread_mutex_unlock(&self->lock);
}

void writer_lock(struct rwlock *self) {
    pthread_mutex_lock(&self->lock);
    if (self->readers || self->writers) {
        self->write_waiters++;
        do pthread_cond_wait(&self->write, &self->lock);
        while (self->readers || self->writers);
        self->write_waiters--;
    }
    self->writers = 1;
    pthread_mutex_unlock(&self->lock);
}

void writer_unlock(struct rwlock *self) {
    pthread_mutex_lock(&self->lock);
    self->writers = 0;
    if (self->write_waiters)
        pthread_cond_signal(&self->write);
    else if (self->read_waiters)
        pthread_cond_broadcast(&self->read);
    pthread_mutex_unlock(&self->lock);
}

void rwlock_init(struct rwlock *self) {
    self->readers = self->writers = self->read_waiters = self->write_waiters = 0;
    pthread_mutex_init(&self->lock, NULL);
    pthread_cond_init(&self->read, NULL);
    pthread_cond_init(&self->write, NULL);
}

pthreads not really being Windows-native, but the general idea is here. This implementation is slightly biased towards writers (a horde of writers can starve readers indefinitely); just modify writer_unlock if you'd rather the balance be the other way around.

Yes, this is C and not C++. Translation is an exercise left to the reader.

Edit

Greg Rogers pointed out that the POSIX standard does specify pthread_rwlock_*. This doesn't help if you don't have pthreads, but it stirred my mind into remembering: Pthreads-w32 should work! Instead of porting this code to non-pthreads for your own use, just use Pthreads-w32 on Windows, and native pthreads everywhere else.

ephemient
I'm curious as to why you reimplemented pthread_rwlock_(rd/wr)(un)lock on top of pthread_mutex_t instead of just using the native pthread API.
Greg Rogers
Because I could / for fun. This project was a bit of a testbed for various locking facilities of mine. Also, it's useful as a demonstration now, right?
ephemient
Unfortunately NOT an implementation that is usable. Potential busy Spin wait here: while (self->writers || self->write_waiters); Plus you gave a C solution to a C++ question.
Martin York
Also not exception safe.
Martin York
It doesn't spin: the `while` modifies the `do pthread_cond_wait` on the line immediately above it. There are no exceptions in C. (True, I don't handle error conditions, but those aren't exceptions.)
ephemient
@ephemient: OK. See it now. Not very readable though.
Martin York
Anna
Like I mentioned, "This implementation is slightly biased towards writers (a horde of writers can starve readers indefinitely); just modify `writer_unlock` if you'd rather the balance be the other way around."
ephemient
I'm trying to make a different point here. If there are readers still reading, there is no point in signaling the writers, as the writers will not start working anyway, and just catch the lock and take useless time. This is a matter of minor inefficiency, not of priority.
Anna
What do mean there is no point? A new writer can happily take the lock even when there are readers waiting for the lock as long as nobody is actually holding the lock, and proceed to do work. If you have a continuous stream of readers and don't want occasional short writers to be blocked by them, this is the better balance.
ephemient
+2  A: 

I can recommend the ACE library, which provides a multitude of locking mechanisms and is ported to various platforms.

Depending on the boundary conditions of your problem, you may find the following classes useful:

  • ACE_RW_Process_Mutex
  • ACE_Write_Guard and ACE_Read_Guard
  • ACE_Condition
Dong Hoon
I wouldn't mind, but I don't think I can get the ACE libraries into our build right now. I've used them in the past and they are great.
Matt Price
+3  A: 

Boost.Thread has since release 1.35.0 already supports reader-writer locks. The good thing about this is that the implementation is greatly cross-platform, peer-reviewed, and is actually a reference implementation for the upcoming C++0x standard.

Dean Michael
Check the accepted answer, it has the same info ;)
Matt Price
Yeah, almost -- but Boost.Thread being the basis for C++0x threading wasn't part of the accepted answer above. Consider this additional information. :)
Dean Michael
+2  A: 

Whatever you decide to use, benchmark your work load against simple locks, as read/write locks tend to be 3-40x slower than simple mutex, when there is no contention.

Here is some reference

ididak
That's interesting, we definitely are benchmarking everything.
Matt Price
A: 

Intel Thread Building Blocks also provide a couple of rw_lock variants:

http://www.threadingbuildingblocks.org/

They have a spin_rw_mutex for very short periods of contention and a queueing_rw_mutex for longer periods of contention. The former can be used in particularly performance sensitive code. The latter is more comparable in performance to that provided by Boost.Thread or directly using pthreads. But profile to make sure which one is a win for your access patterns.

Edward Kmett
Er, not that I would necessarily recommend bringing in all of TBB just to get rw_locks.
Edward Kmett
+2  A: 

There is an article about reader-writer locks on MSDN that presents some implementations of them. It also introduces the Slim reader/writer lock, a kernel synchronisation primitive introduced with Vista. There's also a CodeProject article about comparing different implementations (including the MSDN article's ones).

vividos
My experience with the slim rw lock is that it's super fast compared to the ones based on mutexes and signals.
Laserallan
Thanks Laserallan, that's good to know!
vividos
The annoying thing is that to use it you have to write run-time OS detection and use function pointers or a virtual class to select a locking strategy. Not practical to ignore XP yet.
Zan Lynx
yes, you're right, Zan. Just wanted to mention it, in case someone finds the question and can use it.
vividos
A: 

Does any one know if the Boost implementation will allow starvation of the writer threads?

Demps