简体   繁体   中英

Prioritization of pthread_rwlock?

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM