Приоритизация pthread_rwlock? - PullRequest
0 голосов
/ 04 мая 2019

С учетом этого примера кода:

#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);
    }
}

У меня есть один поток, повторяющийся в func1, в котором запрашивается блокировка записи в течение 1 секунды, и 3 других, повторяющиеся в func 2, в котором запрашивается блокировка чтения1 секунда.

На справочной странице pthread_rwlock_rdlock написано: «Вызывающий поток получает блокировку чтения, если записывающее устройство не удерживает блокировку, и нет записывающих устройств, заблокированных в блокировке.».Из моей вставки вывода в строке 5 вы можете увидеть в «func 1: пытаемся заблокировать», что писатель явно находится в блоке, так почему же мои читатели все равно получат блокировку?После строки 5 каждую секунду печатаются 3 строки.Уменьшение количества тем в моем читателе увеличивает вероятность того, что писатель получит блокировку.

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... 
...

Добавлен еще один пример

#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)

Согласно man-странице pthread_rwlock_rdlock, считыватель 2 должен , а не получить блокировку, потому что имеется блокировщик записи с тем же приоритетом, и политика планирования всех потоков установлена ​​в SCHED_RR (2).

Если поддерживается параметр планирования выполнения потоков и потоки, участвующие в блокировке, выполняются с помощью политик планирования SCHED_FIFO или SCHED_RR, вызывающий поток не должен получать блокировку, если записывающее устройство удерживает блокировку или если записывающие устройства с более высоким или равным приоритетомзаблокированы на замке;в противном случае вызывающий поток должен получить блокировку.

Писатель получает блокировку только тогда, когда оба считывателя сняли блокировку rw.

1 Ответ

1 голос
/ 05 мая 2019

Внимательно прочитайте man-страницу .

Обратите внимание, что предложение, которое вы цитировали

Вызывающий поток получает блокировку чтения, если записывающее устройство не удерживает блокировку и нет блокирующих запись писателей в блокировке

не не говорит, что читатель не получит блокировку, если заблокированы писатели (if вместо if and only if.

Следующее будет использовать документацию POSIX . Параграфы, следующие сразу за цитируемым предложением, определяют, как pthread_rwlock_rdlock() действует, если блокируют писателей:

[TPS] [Option Start] Если поддерживается параметр планирования выполнения потока, и потоки, вовлеченные в блокировку, выполняются с планированием политики SCHED_FIFO или SCHED_RR, вызывающий поток не должен получать замок, если писатель держит замок или если писатели выше или равны приоритет блокируется на замок; в противном случае вызывающий поток должен получить замок. [Конец опции]

[TPS TSP] [Запуск опции] Если опция Планирования выполнения потоков поддерживается, и потоки, вовлеченные в блокировку, выполняются с Политика планирования SCHED_SPORADIC, вызывающий поток не должен получать замок, если писатель держит замок или если писатели выше или равны приоритет блокируется на замок; в противном случае вызывающий поток должен получить замок. [Конец опции]

Если опция Планирования выполнения потоков не поддерживается, она определяется реализацией, получает ли вызывающий поток блокировку когда писатель не держит блокировку и есть писатели заблокированы на замок. Если писатель держит блокировку, вызывающий поток не должен получить блокировку чтения. Если блокировка чтения не установлена, вызывающий поток должен блокировать, пока не сможет получить блокировку. Вызывающая нить может заблокироваться, если во время вызова удерживается блокировка записи.

Чтобы предоставить полный ответ, вам потребуется опубликовать, предоставляет ли ваша реализация параметр Планирование выполнения потоков и, если да, то какая политика планирования была выбрана.

Чтобы увидеть текущую политику планирования (если вы работаете в Linux), запустите следующую программу:

#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);
}

Если текущая политика планирования не является Round-Robin или Fifo, первые два абзаца цитируемой документации не применяются. В таком случае поведение планирования определяется реализацией. В частности, для блокировки чтения / записи можно легко предпочесть читателей , и в этом случае писатель почти наверняка никогда не запустится для вашей программы, потому что читатели сериализуются на замке, защищающем stdout (через printf()), согласно проекту стандарта C11 n1570:

7.21 Вход / выход

7.21.2 Потоки

7 Каждый поток имеет связанную блокировку, которая используется для предотвращения гонок данных, когда несколько потоков выполнения обращаются к потоку, и для ограничения чередования потоковых операций, выполняемых несколькими потоками. Только один поток может удерживать эту блокировку одновременно. Блокировка реентерабельна: один поток может удерживать блокировку несколько раз в данное время.

Так как эта блокировка удерживается при удержании блокировки чтения, а sleep() также выполняется при удерживании блокировки чтения, и читатели ничего не делают между разблокировкой блокировки и ее повторным получением, вероятность того, что в любой момент отсутствует ридер с блокировкой очень маленький. Таким образом, у писателя никогда нет шансов.

...