Search code examples
cmultithreadingpthreads

reader/writer lock in pthread


I'm learning pthread and came across reader writer lock. The scenario is very simple; a global variable being shared by all the threads, reader keeps printing the current value of that same global variable, while writer will update the same variable. I can achieve this synchronization by using two mutexes (pthread_mutex_t), but I want to use "one" reader-writer lock for achieving this same result. However, with one reader-writer lock, as can be seen here(output of the program, below), reader only sees the first value of x and doesn't sees any updates to the global variable. Please throw some light here.

Code:

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <pthread.h>
#include <poll.h>

#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))

int x = 0;

pthread_rwlock_t lock_rw = PTHREAD_RWLOCK_INITIALIZER;

void *reader_thread(void *arg)
{
    int i;
    int newx, oldx;
    newx = oldx = -1;

    pthread_rwlock_t *p = (pthread_rwlock_t *)arg;

    if (pthread_rwlock_rdlock(p) != 0) {
        perror("reader_thread: pthread_rwlock_rdlock error");
        exit(__LINE__);
    }

    for (i = 0; i < 100; i++) {
        newx = ACCESS_ONCE(x);
        if (newx != oldx) {
            printf("reader_lock: x: %d\n",x);
        }
        oldx = newx;
        poll(NULL, 0, 1);
    }

    if (pthread_rwlock_unlock(p) != 0) {
        perror("reader thread: pthred_rwlock_unlock error");
        exit(__LINE__);
    }

    return NULL;
}

void *writer_thread(void *arg)
{
    int i;
    pthread_rwlock_t *p = (pthread_rwlock_t *)arg;

    if (pthread_rwlock_wrlock(p) != 0) {
        perror("writer thread: pthread_rwlock_wrlock error");
        exit(__LINE__);
    }

    for (i = 0; i < 3; i++) {
        ACCESS_ONCE(x)++;
        poll(NULL, 0, 5);
    }

    if (pthread_rwlock_unlock(p) != 0) {
        perror("writer thread: pthread_rwlock_unlock error");
        exit(__LINE__);
    }

    return NULL;
}

int main(void)
{
    pthread_t tid1, tid2;
    void *vp;
    if (pthread_create(&tid1, NULL, reader_thread, &lock_rw) != 0) {
        perror("pthread_create error");
        exit (__LINE__);
    }

    if (pthread_create(&tid2, NULL, writer_thread, &lock_rw) != 0) {
        perror("pthread_create error");
        exit (__LINE__);
    }

    //wait for the thread to complete
    if (pthread_join(tid1, &vp) != 0) {
        perror("pthread_join error");
        exit (__LINE__);
    }

    if (pthread_join(tid2, &vp) != 0) {
        perror("pthread_join error");
        exit (__LINE__);
    }

    printf("Parent process sees x: %d\n",x);
    return 0;
}

gcc pthread_rwlock.c -o rwlock -pthread -Wall -Werror

./rwlock

reader_lock: x: 0

Parent process sees x: 3


Solution

  • When a thread acquires a lock, other threads trying to acquire the same lock will be suspended until the first thread releases the lock.

    What is happening here is that your reader thread starts, acquires the lock, and enters the for/poll loop.

    The writer thread starts, tries to acquire the lock which was already taken by the reader thread, and remains blocked on pthread_rwlock_wrlock.

    What you actually want to do is to put your lock/unlock right before and after the code where you access your shared variable.

    thread_rwlock_rdlock(p);
    newx = ACCESS_ONCE(x);
    thread_rwlock_unlock(p);
    ...
    thread_rwlock_wrlock(p);
    ACCESS_ONCE(x)++;
    thread_rwlock_unlock(p);