Рекурсивный pthread_rwlock_rdlock в Mac OS X/Darwin

У меня есть следующий пример кода (см. код ниже), который выполняется по потоку:

A: rd-lock
B: wr-lock (waiting)
A: rd-lock (recursive)
A: rd-unlock (recursive)
A: rd-unlock
B: wr-locked (wake after wait)
B: wr-unlock.

В основном блокировка чтения является рекурсивной. Это требуется стандартами POSIX (требует, чтобы блокировки чтения были рекурсивными, но не указаны для блокировок записи). Это работает в Linux, FreeBSD, Solaris, но не работает в Darwin/Mac OS X.

Пример ниже дает следующий вывод в Linux:

read locking
read locked
write locking
read locking 2
read locked 2
read unlocked 2
read unlocked
write locked
write unlocked 2

В то время как на Дарвине он печатает:

read locking
read locked
write locking
read locking 2

И взаимоблокировки здесь (не продолжаются), в основном это не соблюдает рекурсивную блокировку чтения.

Можно ли что-то сделать (пометить, определить, связать со специальной версией библиотеки), чтобы все работало как положено?


Образец кода

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

pthread_rwlock_t lock;

void *thread_r(void *p)
{
    printf("read locking\n");
    pthread_rwlock_rdlock(&lock);
    printf("read locked\n");
    usleep(500*1000);
    printf("read locking 2\n");
    pthread_rwlock_rdlock(&lock);
    printf("read locked 2\n");
    usleep(500*1000);
    pthread_rwlock_unlock(&lock);
    printf("read unlocked 2\n");
    usleep(500*1000);
    pthread_rwlock_unlock(&lock);
    printf("read unlocked\n");
}

void *thread_w(void *p)
{
    usleep(250*1000);
    printf("write locking\n");
    pthread_rwlock_wrlock(&lock);
    printf("write locked\n");
    pthread_rwlock_unlock(&lock);
    printf("write unlocked 2\n");
}

int main()
{
    pthread_t a,b;
    pthread_rwlock_init(&lock,NULL);
    pthread_create(&a,NULL,thread_r,0);
    pthread_create(&b,NULL,thread_w,0);
    pthread_join(a,NULL);
    pthread_join(b,NULL);
    return 0;
}

person Artyom    schedule 10.01.2012    source источник


Ответы (2)


Только rdlock() поддерживает рекурсивную блокировку:

http://pubs.opengroup.org/onlinepubs/007908799/xsh/pthread_rwlock_rdlock.html

Согласно спецификации Unix, поведение вызова wrlock() не определено, если поток уже удерживает блокировку чтения или записи:

http://pubs.opengroup.org/onlinepubs/007908799/xsh/pthread_rwlock_trywrlock.html

Когда вы работаете с OS X, взгляните на NSRecursiveLock:

http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html

person senojsitruc    schedule 10.01.2012
comment
Это не определено только в том случае, если вы попробуете rdlock, когда у вас в данный момент есть wrlock. Эта ситуация представляет собой один поток, выполняющий две блокировки rdlock. - person paxdiablo; 10.01.2012

Да, блокировки чтения блокировки на rwlocks действительно рекурсивны, до определенного момента. Но в документах POSIX есть строка для pthread_rwlock_rdlock, SUSv2, так как это что Apple поддерживает:

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

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

собственные онлайн-документы Apple также поддержите это:

Функция pthread_rwlock_rdlock() устанавливает блокировку чтения на rwlock, при условии, что rwlock в данный момент не удерживается для записи и ни один поток записи не заблокирован на блокировке.

И позже:

Чтобы предотвратить голодание писателей, писатели пользуются преимуществом над читателями.

Опять таки. нет упоминания о разрешении рекурсивных блокировок чтения, пока блокировка записи находится в очереди.

person paxdiablo    schedule 10.01.2012
comment
Заключение в кавычки Поток может удерживать несколько одновременных блокировок чтения на rwlock (то есть успешно вызывать функцию pthread_rwlock_rdlock() n раз). Если это так, поток должен выполнить соответствующие разблокировки (то есть он должен вызвать функцию pthread_rwlock_unlock() n раз). Также первая приведенная вами цитата относится к получению блокировки чтения, а не к тому случаю, когда такая блокировка уже получена потоком. Это правильно, если есть ожидающая блокировка записи, новые блокировки чтения не могут быть получены, но существующие блокировки чтения могут быть повторно получены. - person Artyom; 10.01.2012
comment
@Artyom, это вопрос интерпретации, и я считаю, что реализации различаются. Получение блокировки чтения как действие может включать в себя рекурсивное получение (вы, кажется, предполагаете, что вторая блокировка rdlock не является получением, я говорю, что это может считаться таковым), поэтому она будет заблокирована в ожидании ожидание запроса на блокировку записи. Другими словами, несмотря на то, что он утверждает, что несколько rdlock из одного потока допустимы, похоже, он не полностью определяет, что происходит, когда вы выполняете второй rdlock, когда wrlock находится в ожидании. - person paxdiablo; 11.01.2012
comment
Я понимаю это, но если вы говорите, что разрешены рекурсивные блокировки (это прямо сказано), то заявление о том, что вы не можете писать блокировку в такой ситуации, поскольку это может привести к взаимоблокировке, делает все рекурсивные блокировки бесполезными по определению. Таким образом, даже если вы интерпретируете руководство таким образом, все равно нет смысла поддерживать рекурсивную блокировку. Например, для Windows Vista/7 Slim RWLock прямо указано, что рекурсивные блокировки запрещены: msdn.microsoft.com/en-us/library/windows/desktop/ - person Artyom; 11.01.2012