Question

I'm currently working on a correct implementation of the Reader-Writer problem (see here).

I found this solution in the Qt docks guaranteeing fair treatment of Reader and Writer threads by using a semaphore and mutex. The basic code is this:

sem_t semaphore_;
pthread_mutex_t lock_;

void PalindromeDatabase::initializeLocks()
{
    sem_init(&semaphore_, 0, NumberOfReaders_);
    pthread_mutex_init(&lock_, nullptr);
}

void PalindromeDatabase::lockReaders()
{
    sem_wait(&semaphore_);
}

void PalindromeDatabase::unlockReaders()
{
    sem_post(&semaphore_);
}

void PalindromeDatabase::lockWriters()
{
    pthread_mutex_lock(&lock_);
    {
        for (int i = 0; i < NumberOfReaders_; ++i)
            sem_wait(&semaphore_);
    }
    pthread_mutex_unlock(&lock_);
}

void PalindromeDatabase::unlockWriters()
{
    for (int i = 0; i < NumberOfReaders_; ++i)
        sem_post(&semaphore_);
}

This seems like a very elegant solution. It seems easier and a lot more efficient than the pthread_rwlock_*behavior detailed in this SO answer.

I was wondering if this code below is a correct adjustment of the Qt solution to prefer Reader threads.

int readersActive_;
sem_t semaphore_;
pthread_mutex_t lock_;
pthread_mutex_t readLock_;
pthread_cond_t wait_;

void PalindromeDatabase::initializeLocks()
{
    sem_init(&semaphore_, 0, numberOfReaders_);
    pthread_mutex_init(&lock_, nullptr);
    pthread_mutex_init(&readLock_, nullptr);
    pthread_cond_init(&wait_, nullptr);
}

void PalindromeDatabase::lockReaders()
{
    pthread_mutex_lock(&lock_);
    {
        pthread_mutex_lock(&readLock_);
        sem_wait(&semaphore_);
        pthread_mutex_unlock(&readLock_);

        ++readersActive_;
    }
    pthread_mutex_unlock(&lock_);
}

void PalindromeDatabase::lockReaders()
{
    pthread_mutex_lock(&lock_);
    {
        pthread_mutex_lock(&readLock_);
        sem_wait(&semaphore_);
        pthread_mutex_unlock(&readLock_);

        ++readersActive_;
    }
    pthread_mutex_unlock(&lock_);
}

void PalindromeDatabase::unlockReaders()
{
    sem_post(&semaphore_);

    pthread_mutex_lock(&lock_);
    {
        --readersActive_;

        if (readersActive_ == 0)
            pthread_cond_signal(&wait_);
    }
    pthread_mutex_unlock(&lock_);
}

void PalindromeDatabase::lockWriters()
{
    pthread_mutex_lock(&lock_);
    {
        if (readersActive_ != 0)
        {
            do
            {
                pthread_cond_wait(&wait_, &lock_);
            } while (readersActive_ != 0);
        }

        pthread_mutex_lock(&readLock_);
        for (int i = 0; i < numberOfReaders_; ++i)
            sem_wait(&semaphore_);
        pthread_mutex_unlock(&readLock_);
    }
    pthread_mutex_unlock(&lock_);
}

void PalindromeDatabase::unlockWriters()
{
    for (int i = 0; i < numberOfReaders_; ++i)
        sem_post(&semaphore_);
}
Was it helpful?

Solution

There are quite some issues with your code:

  1. The semaphore is only used by the writer and as such it is meaningless.
  2. While locking for writer, you use the mutex, while unlocking you don't.
  3. The readers signal a changed condition when #readers become zero, and the writer waits for the signal of the condition variable, but it doesn't check the condition.
  4. Upon locking for writer, if #readers is already zero, it will not actually lock.

Having thought about my remark that it is easy, locking is still tricky, I thought about it and I hope I cracked it with this pseudo code, focussing on correct order not the correct notation:

void lockReader()
{
  lock(rdmutex);  // make sure Reader and Writer can't interfere during locking
  lock(wrmutex);  // lock mutex so waitfor can unlock
  while (writer_)
    waitfor(wrcv, wrmutex);  // no active writers

  ++readers_; // at least 1 reader present
  unlock(wrmutex);
  unlock(rdmutex);
}

void unlockReader()
{
  lock(rdmutex);
  bool noReaders = (--readers_ == 0);
  unlock(rdmutex);
  if (noReaders) signal(rdcv); // signal when no more readers
}

void lockWriter()
{
  lock(WritersLock);  // only 1 writer allowed
  lock(rdmutex);  // lock mutex so waitfor can unlock and no interference by lockReader
  while (readers_ != 0)
    waitfor(rdcv, rdmutex);  // wait until no more readers
  lock(wrmutex);
  writer_ = true;  // a writer is busy
  unlock(wrmutex);
  unlock(rdmutex);
  // WritersLock is still locked
}

void unlockWriter()
{
  lock(wrmutex);
  writer_ = false;
  unlock(wrmutex);
  signal(wrcv);  // no more writer (until WritersLock is unlocked)

  unlock(WritersLock);
}

As it turns out, the Qt implementation is simpler, but my algorithm doesn't need to know the maximum number of readers in advance.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top