reader/writer lock in pthread

35,281

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);
Share:
35,281
Amit
Author by

Amit

Updated on October 21, 2020

Comments

  • Amit
    Amit over 3 years

    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

  • Amit
    Amit over 10 years
    Wouldn't that be same as using just a mutex(pthread_mutex_t)? On my setup, I tried using a mutex instead of reader-writer lock and replaced those calls from pthread_rwlock_{rd|wr}lock to pthread_mutex_lock() and placed them around those hotspots, and achieved what I wanted(reader could see all the intermediate values for x), but, this approach doesn't look correct to me. reader-writer locks should bring in some additional capabilities, isn't it ?
  • zch
    zch over 10 years
    @kumar, rwlock doesn't introduce any extra safety guarantees over standard mutex. However, it might reduce problem of high reader contention (but not necessarily, since it's more complicated and locking it is slower).
  • Amit
    Amit over 10 years
    From the man pages of pthread_rwlock_wrlock, "The calling thread acquires the write lock if no other thread (reader or writer) holds the read-write lock rwlock. Otherwise, the thread shall block until it can acquire the lock". So I was assuming that writer won't be blocked but this is not the case. I guess I need to explore more on reader-writer locks to fully understand the use cases. Thank you @zch. For now, I'll live with what Flavio suggested.
  • Flavio
    Flavio over 10 years
    As @zch said, read/write locks make a difference only if you have many reader threads access the same values. If you have only one reader, read/write locks behave the same way as standard mutexes.
  • caf
    caf over 10 years
    @kumar: The purpose of read-writer locks is to allow many readers to hold the read lock simultaneously, but still give exclusive access to writers. With a plain mutex, all threads will have exclusive access.