-1

Considering this example code:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

void *func1(void *);
void *func2(void *);

static pthread_rwlock_t rwLock = PTHREAD_RWLOCK_INITIALIZER;

int main() {

    pthread_t thread1;
    pthread_t thread2;

    pthread_create(&thread1, NULL, func1, NULL);
    sleep(1);
    int i;
    for (i = 0; i < 3; i++) {
        pthread_create(&thread2, NULL, func2, (void *)(i + 1));
    }

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    return 0;
}

void *func1(void *arg) {

    int j;
    for(j = 0; j < 10; j++) {

        printf("func 1: trying lock\n");
        pthread_rwlock_wrlock(&rwLock);
        printf("func 1: lock aquired, sleep 1 sec...\n");

        sleep(1);

        pthread_rwlock_unlock(&rwLock);
    }
}

void *func2(void *arg) {

    int true = 1;
    while(true) {

        pthread_rwlock_rdlock(&rwLock);

        printf("func 2: thread %i: lock aquired, sleep 1 sec... \n", (int)arg);
        sleep(1);

        pthread_rwlock_unlock(&rwLock);
    }
}

I have one thread looping over in func1 in which write lock is asked for 1 second, and 3 others looping over in func 2 in which read lock is asked for 1 second.

On the pthread_rwlock_rdlock man page it says "The calling thread acquires the read lock if a writer does not hold the lock and there are no writers blocked on the lock. ". From my output paste on line 5 you can see on "func 1: trying lock" a writer is clearly on block there, so why do my readers will get the lock anyway? After line 5, 3 lines are printed each second. Reducing my reader threads increases writer's chance to get the lock.

func 1: trying lock
func 1: lock aquired, sleep 1 sec...
func 1: trying lock
func 1: lock aquired, sleep 1 sec...
func 1: trying lock
func 2: thread 1: lock aquired, sleep 1 sec... 
func 2: thread 3: lock aquired, sleep 1 sec... 
func 2: thread 2: lock aquired, sleep 1 sec... 
func 2: thread 2: lock aquired, sleep 1 sec... 
func 2: thread 3: lock aquired, sleep 1 sec... 
func 2: thread 1: lock aquired, sleep 1 sec... 
func 2: thread 2: lock aquired, sleep 1 sec... 
func 2: thread 3: lock aquired, sleep 1 sec... 
func 2: thread 1: lock aquired, sleep 1 sec... 
func 2: thread 3: lock aquired, sleep 1 sec... 
func 2: thread 1: lock aquired, sleep 1 sec... 
func 2: thread 2: lock aquired, sleep 1 sec... 
func 2: thread 3: lock aquired, sleep 1 sec... 
func 2: thread 2: lock aquired, sleep 1 sec... 
func 2: thread 1: lock aquired, sleep 1 sec... 
func 2: thread 3: lock aquired, sleep 1 sec... 
func 2: thread 1: lock aquired, sleep 1 sec... 
func 2: thread 2: lock aquired, sleep 1 sec... 
...

Added another example

#define _GNU_SOURCE

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#define SIZE 10000

void *writerFunc(void *);
void *readerFunc1(void *);
void *readerFunc2(void *);
int setSchedulePolicyTo2(void);

static pthread_rwlock_t rwLock = PTHREAD_RWLOCK_INITIALIZER;

int main() {

    pthread_t readerThread1;
    pthread_t readerThread2;
    pthread_t writerThread;

    pthread_create(&readerThread1, NULL, readerFunc1, NULL);
    sleep(1);
    pthread_create(&readerThread1, NULL, writerFunc, NULL);
    sleep(1);
    pthread_create(&readerThread2, NULL, readerFunc2, NULL);

    pthread_join(readerThread1, NULL);
    pthread_join(readerThread2, NULL);
    pthread_join(writerThread, NULL);

    return 0;
}

void *writerFunc(void *arg) {
    printf("                writer's scheduling policy: %d\n", setSchedulePolicyTo2());

    printf("writer 1: trying to acquire rw lock...(on hold)\n");
    pthread_rwlock_wrlock(&rwLock); // Note ..._wrlock
    printf("writer 1: rw lock acquired \n");
    pthread_rwlock_unlock(&rwLock);
}

void *readerFunc1(void *arg) {
    printf("                reader1's scheduling policy: %d\n", setSchedulePolicyTo2());

    printf("reader 1: trying to acquire rw lock...(on hold)\n");
    pthread_rwlock_rdlock(&rwLock);
    printf("reader 1: rw lock acquired \n");
    sleep(3); // enough time to let reader 2 to acquire rw lock before this reader releases it.
    pthread_rwlock_unlock(&rwLock);
    printf("reader 1: rw lock released \n");
}

void *readerFunc2(void *arg) {
    printf("                reader2's scheduling policy: %d\n", setSchedulePolicyTo2());

    printf("reader 2: trying to acquire rw lock...(on hold)\n");
    pthread_rwlock_rdlock(&rwLock);
    printf("reader 2: rw lock acquired \n");
    sleep(2);
    pthread_rwlock_unlock(&rwLock);
    printf("reader 2: rw lock released \n");
}

int setSchedulePolicyTo2() {
    struct sched_param sp;
        sp.sched_priority = 10;
    int policy;
    int j;
    if((j = pthread_setschedparam(pthread_self(), SCHED_RR, &sp)) != 0) {
        printf("error: %s \n", strerror(errno));
    }
    if((j = pthread_getschedparam(pthread_self(), &policy, &sp)) != 0) {
        printf("error: %s \n", strerror(errno));
    }
    return policy;
}

output:

$ gcc main.c -pthread
$ sudo ./a.out
                reader1's scheduling policy: 2
reader 1: trying to acquire rw lock...(on hold)
reader 1: rw lock acquired 
                writer's scheduling policy: 2
writer 1: trying to acquire rw lock...(on hold)
                reader2's scheduling policy: 2
reader 2: trying to acquire rw lock...(on hold)
reader 2: rw lock acquired 
reader 1: rw lock released 
reader 2: rw lock released 
writer 1: rw lock acquired 
Segmentation fault (end of program)

As per the manpage of pthread_rwlock_rdlock, reader 2 should not acquire the lock because there is writer on hold with same priority and all threads' scheduling policy is set to SCHED_RR (2).

If the Thread Execution Scheduling option is supported, and the threads involved in the lock are executing with the scheduling policies SCHED_FIFO or SCHED_RR, the calling thread shall not acquire the lock if a writer holds the lock or if writers of higher or equal priority are blocked on the lock; otherwise, the calling thread shall acquire the lock.

The writer acquires the lock only when both readers have released the rw lock.

1 Answers1

0

Read the manpage carefully.

Note that the sentence you quoted

The calling thread acquires the read lock if a writer does not hold the lock and there are no writers blocked on the lock

does not say that the reader will not acquire the lock if there are writers blocked (if rather than if and only if.

The following will use the POSIX documentation. The paragraphs immediately following the quoted sentence specify how pthread_rwlock_rdlock() acts if there are writers blocked on the lock:

[TPS] [Option Start] If the Thread Execution Scheduling option is supported, and the threads involved in the lock are executing with the scheduling policies SCHED_FIFO or SCHED_RR, the calling thread shall not acquire the lock if a writer holds the lock or if writers of higher or equal priority are blocked on the lock; otherwise, the calling thread shall acquire the lock. [Option End]

[TPS TSP] [Option Start] If the Threads Execution Scheduling option is supported, and the threads involved in the lock are executing with the SCHED_SPORADIC scheduling policy, the calling thread shall not acquire the lock if a writer holds the lock or if writers of higher or equal priority are blocked on the lock; otherwise, the calling thread shall acquire the lock. [Option End]

If the Thread Execution Scheduling option is not supported, it is implementation-defined whether the calling thread acquires the lock when a writer does not hold the lock and there are writers blocked on the lock. If a writer holds the lock, the calling thread shall not acquire the read lock. If the read lock is not acquired, the calling thread shall block until it can acquire the lock. The calling thread may deadlock if at the time the call is made it holds a write lock.

To provide a complete answer would thus require you posting whether your implementation provides the Thread Execution Scheduling option and if it does, which scheduling policy was selected.

To see what the current scheduling policy is (if you're on Linux), run the following program:

#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>

int main(void)
{
  printf("round-robin scheduling policy: %d\n", SCHED_RR);
  printf("fifo scheduling policy: %d\n", SCHED_FIFO);
  printf("other scheduling policy: %d\n", SCHED_OTHER);
  pthread_attr_t ta;
  pthread_getattr_np(pthread_self(), &ta);
  int ts;
  pthread_attr_getschedpolicy(&ta, &ts);
  printf("current scheduling policy: %d\n", ts);
}

Unless the current scheduling policy is Round-Robin or Fifo, the first two paragraphs of the quoted documentation do not apply. In such a case, the scheduling behavior is implementation defined. In particular, it is easily possible for a reader/writer lock to prefer readers, in which case the writer will almost certainly never get to run for your program, because the readers are serializing on the lock protecting stdout (via printf()), as per C11 draft standard n1570:

7.21 Input/output

7.21.2 Streams

7 Each stream has an associated lock that is used to prevent data races when multiple threads of execution access a stream, and to restrict the interleaving of stream operations performed by multiple threads. Only one thread may hold this lock at a time. The lock is reentrant: a single thread may hold the lock multiple times at a given time.

Since this lock is held while also holding the readlock, and the sleep() is also executed while holding the readlock, and the readers do nothing between releasing the readlock and acquiring it again, the likelihood that at any point there is no reader holding the lock is very small. Thus, the writer never has a chance.

EOF
  • 6,273
  • 2
  • 26
  • 50
  • I tried to read further into the manual. The 3 paragraphs you quoted all state that in my case the reader(s) should not acquire the lock because I have a writer on block, except for the implementation-defined part which is only situation where it is possible that the reader will get the lock anyway. I'm not sure what implementation-defined means here? my whole program is in my op and I build and run with "gcc main.c -lpthread" "./a.out" on linux platform. I have updated #includes to my op to complete my program. –  May 05 '19 at 08:43
  • 1
    The `#include`s are nice, but they don't say anything about the scheduling policy. I've updated the answer to show how to find what the current scheduling policy is on Linux. – EOF May 05 '19 at 12:36
  • Okay I've read more into scheduling and came to accept that there's no easy way knowing how implementation-defined behaves (running your program gave me 0: other scheduling policy). So I tried making even more simpler example that I updated in my op. I don't expect you to waste any more time in this seemingly useless issue of mine, but just for the sake of learning I tried to understand why the manpages are contradicting my program, which means either I'm going crazy, I'm reading the manpages wrong or they are simply wrong (unlikely). Thanks for replies anyway, upvoted for effort & info. –  May 05 '19 at 20:57
  • I'd recommend you look at [this question](https://stackoverflow.com/q/2190090/3185968), which should help you. – EOF May 05 '19 at 22:04