Применение блокировок чтения/записи

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

В таких случаях можно заблокировать данные таким образом, чтобы несколько нитей могли одновременно считывать данные, и только одна нить могла их изменять. Для этого предназначена блокировка типа "несколько читателей, один писатель", или блокировка чтения/записи. Блокировка чтения/записи захватывается для чтения или записи, а затем освобождается. Освободить блокировку чтения-записи может только та нить, которая ее захватила.

Объект атрибутов чтения/записи

Функция pthread_rwlockattr_init инициализирует объект атрибутов блокировки чтения-записи (attr). Значения атрибутов, применяемые по умолчанию, зависят от конкретной реализации. Вызов функции pthread_rwlockattr_init для уже инициализированного объекта атрибутов блокировки чтения/записи приведет к непредсказуемым результатам.

Ниже приведен пример вызова функции pthread_rwlockattr_init для объекта attr:
pthread_rwlockattr_t    attr;
и:
pthread_rwlockattr_init(&attr);

Функции, изменяющие объект атрибутов блокировки чтения/записи (в том числе функция удаления), не влияют на блокировки чтения/записи, инициализированные с помощью этого объекта.

Функция pthread_rwlockattr_destroy удаляет объект атрибутов блокировки чтения/записи. Применение удаленного объекта до его повторной инициализации с помощью функции pthread_rwlockattr_init приведет к непредсказуемым результатам. В некоторых реализациях функция pthread_rwlockattr_destroy может присваивать объекту, на который ссылается объект attr, недопустимое значение.

Создание и удаление блокировок чтения/записи

Функция pthread_rwlock_init присваивает блокировке чтения/записи, связанной с объектом rwlock, значения атрибутов из объекта attr. Если объекту attr присвоено пустое значение, то применяются атрибуты блокировки чтения/записи по умолчанию; то же самое происходит при передаче в функцию адреса объекта атрибутов блокировки чтения/записи по умолчанию. После успешной инициализации блокировка находится в состоянии "разблокирована". Инициализированная блокировка может применяться любое число раз без повторной инициализации. Вызов функции pthread_rwlock_init для уже инициализированной блокировки чтения/записи или применение неинициализированной блокировки приведет к непредсказуемым результатам.

Если при выполнении функции pthread_rwlock_init возникает ошибка, то объект rwlock не инициализируется. Содержимое такого объекта не определено.

Функция pthread_rwlock_destroy удаляет объект блокировки чтения/записи rwlock и освобождает заблокированные ресурсы. Ниже перечислены операции, результат выполнения которых заранее предсказать нельзя:
  • Применение блокировки до ее повторной инициализации с помощью функции pthread_rwlock_init.
  • В некоторых реализациях функция pthread_rwlock_destroy может присваивать объекту rwlock недопустимое значение. Вызов функции pthread_rwlock_destroy для объекта rwlock, который захвачен какой-либо нитью, может привести к непредвиденным результатам.
  • Удаление неинициализированной блокировки чтения/записи. Удаленный объект блокировки чтения/записи можно повторно инициализировать с помощью функции pthread_rwlock_init. Обращение к удаленному объекту блокировки чтения/записи может привести к непредвиденным результатам.
Если вы планируете применять атрибуты блокировки чтения/записи по умолчанию, то для инициализации статической блокировки чтения/записи воспользуйтесь макрокомандой PTHREAD_RWLOCK_INITIALIZER. Например:
pthread_rwlock_t        rwlock1 = PTHREAD_RWLOCK_INITIALIZER;
Действие этой команды аналогично динамической инициализации с помощью функции pthread_rwlock_init с пустым значением параметра attr за исключением того, что проверка на наличие ошибок не выполняется. Например:
pthread_rwlock_init(&rwlock2, NULL);
В следующем примере продемонстрирован вызов функции pthread_rwlock_init с инициализированным параметром attr. Пример инициализации параметра attr приведен в разделе Объект атрибутов блокировки чтения-записи.
pthread_rwlock_init(&rwlock, &attr);

Захват объекта блокировки чтения/записи для чтения

Функция pthread_rwlock_rdlock захватывает объект блокировки чтения/записи rwlock для чтения. Вызывающая нить получает блокировку для чтения, если блокировка не захвачена для записи и нет нитей, ожидающих получения блокировки для записи. В случае, если блокировка не захвачена для записи, но есть нити, ожидающие получения блокировки для записи, действие функции не определено. Если блокировка захвачена для записи, то вызывающая нить не получит блокировку для чтения. Если блокировку для чтения не удалось захватить сразу, функция pthread_rwlock_rdlock продолжает выполняться в вызывающей нити до тех пор, пока не захватит блокировку. Если вызывающая нить уже захватила блокировку rwlock для записи, то результат вызова функции не определен.

Нить может несколько раз захватить блокировку rwlock для чтения (то есть вызвать функцию pthread_rwlock_rdlock n раз). В этом случае нить должна соответствующее число раз разблокировать объект (то есть вызвать функцию pthread_rwlock_unlock n раз).

Функция pthread_rwlock_tryrdlock, как и функция pthread_rwlock_rdlock, устанавливает блокировку для чтения. Однако в тех случаях, когда объект rwlock захвачен какой-либо нитью для записи, или есть нити, ожидающие получения блокировки rwlock для записи, эта функция возвращает сообщение об ошибке. Вызов любой из этих функций для неинициализированной блокировки чтения/записи может привести к непредсказуемым результатам.

При получении сигнала нить, ожидающая захвата блокировки чтения/записи для чтения, вызывает обработчик сигнала, а после завершения его работы снова переходит в состояние ожидания блокировки.

Захват объекта блокировки чтения/записи для записи

Функция pthread_rwlock_wrlock захватывает объект блокировки чтения/записи rwlock для записи. Вызывающая нить получает блокировку для записи, если блокировка чтения/записи rwlock не захвачена для чтения или записи другими нитями. В противном случае функция pthread_rwlock_wrlock продолжает выполняться в нити до тех пор, пока не удастся захватить блокировку. Если вызывающая нить ранее уже захватила блокировку чтения/записи для чтения или записи, то попытка захватить эту же блокировку для записи может привести к непредсказуемым результатам.

Функция pthread_rwlock_trywrlock, как и функция pthread_rwlock_wrlock, захватывает блокировку для записи. Однако если объект rwlock уже захвачен какой-либо нитью, эта функция возвращает сообщение об ошибке. Вызов любой из этих функций для неинициализированной блокировки чтения/записи может привести к непредсказуемым результатам.

При получении сигнала нить, ожидающая захвата блокировки чтения/записи для записи, вызывает обработчик сигнала, а после завершения его работы снова переходит в состояние ожидания блокировки.

начало изменения

Предпочтительное использование нити записи вместо нити чтения

С помощью функции pthread_rwlock_attr_setfavorwriters_np приложение может инициализировать атрибуты блокировки чтения/записи. Для того чтобы нити, которым блокировка чтения/записи нужна в режиме чтения, имели более высокий приоритет планирования, укажите библиотеку pthread. Когда библиотека pthread планирует нити записи (то есть нити, записывающие данные) для получения блокировки чтения/записи в режиме записи, она не поддерживает рекурсивный возврат к нитям, которым блокировка чтения/записи нужна в режиме чтения. Повторное получение нитью блокировки чтения/записи в режиме чтения может привести к непредсказуемым результатам.

Функция pthread_rwlock_attr_getfavorwriters_np возвращает текущее значение, настроенное в структуре атрибутов блокировки чтения/записи. По умолчанию блокировка чтения/записи предоставляется в первую очередь нитям чтения, а не нитям записи.

конец изменения

Примеры программ с блокировками чтения/записи

В приведенных ниже примерах программ продемонстрировано применение функций блокировки. Для запуска этих программ необходим файл check.h и makefile.

Файл check.h:
#include stdio.h
#include stdio.h
#include stdio.h
#include stdio.h

/* Простая функция, проверяющая код возврата и завершающая программу,
   если функция не была выполнена
   */
static void compResults(char *string, int rc) {
  if (rc) {
    printf("Ошибка в : %s, rc=%d",
           string, rc);
    exit(EXIT_FAILURE);
  }
  return;
}
Файл Make:
CC_R = xlc_r

TARGETS = test01 test02 test03

OBJS = test01.o test02.o test03.o

SRCS = $(OBJS:.o=.c)

$(TARGETS): $(OBJS)
    $(CC_R) -o $@ $@.o

clean:
    rm $(OBJS) $(TARGETS)

run:
    test01
    test02
    test03

Пример с одной нитью

В следующем примере функция pthread_rwlock_tryrdlock применяется одной нитью. Пример применения функции Pthread_rwlock_tryrdlock с несколькими нитями приведен в разделе Пример с несколькими нитями.

Пример: test01.c

#define _MULTI_THREADED
#include pthread.h
#include stdio.h
#include "check.h"

pthread_rwlock_t       rwlock = PTHREAD_RWLOCK_INITIALIZER;

void *rdlockThread(void *arg)
{
  int rc;
  int             count=0;

  printf("Выполняется нить, получение блокировки для чтения с ожиданием\n");
  Retry:
  rc = pthread_rwlock_tryrdlock(&rwlock);
  if (rc == EBUSY) {
    if (count >= 10) {
      printf("Достигнуто ограничение на число попыток, ошибка!\n");

      exit(EXIT_FAILURE);
    }
    ++count;
    printf("Не удалось получить блокировку, выполнение других операций, а затем повтор...\n");
    sleep(1);
    goto Retry;
  }
  compResults("pthread_rwlock_tryrdlock() 1\n", rc);

  sleep(2);

  printf("освобождение блокировки для чтения\n");
  rc = pthread_rwlock_unlock(&rwlock);
  compResults("pthread_rwlock_unlock()\n", rc);

  printf("Дополнительная нить завершена\n");
  return NULL;
}

int main(int argc, char **argv)
{
  int                   rc=0;
  pthread_t thread;

  printf("Запуск тестового набора - %s\n", argv[0]);

  printf("Главная нить, получение блокировки для записи\n");
  rc = pthread_rwlock_wrlock(&rwlock);
  compResults("pthread_rwlock_wrlock()\n", rc);

  printf("Главная нить, создание нити для вызова функции pthread_rwlock_tryrdlock()\n");
  rc = pthread_create(&thread, NULL, rdlockThread, NULL);
  compResults("pthread_create\n", rc);

  printf("Главная нить удерживает блокировку для записи\n");
  sleep(5);

  printf("Главная нить, освобождение блокировки для записи\n");
  rc = pthread_rwlock_unlock(&rwlock);
  compResults("pthread_rwlock_unlock()\n", rc);

  printf("Главная нить, ожидание завершения выполнения нити\n");
  rc = pthread_join(thread, NULL);
  compResults("pthread_join\n", rc);

  rc = pthread_rwlock_destroy(&rwlock);
  compResults("pthread_rwlock_destroy()\n", rc);
  printf("Главная нить завершена\n");
  return 0;
}

Вывод этой программы будет выглядеть приблизительно следующим образом:

Запуск тестового набора - ./test01
Главная нить, получение блокировки для записи
Главная нить, создание нити для вызова функции pthread_rwlock_tryrdlock()
Главная нить удерживает блокировку для записи

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

Пример с несколькими нитями

В следующем примере функция pthread_rwlock_tryrdlock вызывается несколькими нитями. Пример применения функции Pthread_rwlock_tryrdlock в одной нити приведен в разделе Пример с одной нитью.

Пример: test01.c

#define _MULTI_THREADED
#include pthread.h
#include stdio.h
#include "check.h"

pthread_rwlock_t       rwlock = PTHREAD_RWLOCK_INITIALIZER;

void *wrlockThread(void *arg)
{
  int rc;
  int             count=0;

  printf("%.8x: Выполняется нить, получение блокировки для записи\n",
         pthread_self());
  Retry:
  rc = pthread_rwlock_trywrlock(&rwlock);
  if (rc == EBUSY) {
    if (count >= 10) {
      printf("%.8x: Достигнуто ограничение на число попыток, ошибка!\n",
             pthread_self());
      exit(EXIT_FAILURE);
    }

    ++count;
    printf("%.8x: Выполнение других операций, затем повтор...\n",
           pthread_self());
    sleep(1);
    goto Retry;
  }
  compResults("pthread_rwlock_trywrlock() 1\n", rc);
  printf("%.8x: Получена блокировка для записи\n", pthread_self());

  sleep(2);

  printf("%.8x: Освобождение блокировки для записи\n",
         pthread_self());
  rc = pthread_rwlock_unlock(&rwlock);
  compResults("pthread_rwlock_unlock()\n", rc);

  printf("%.8x: Дополнительная нить завершена\n",
         pthread_self());
  return NULL;
}

int main(int argc, char **argv)
{
  int                   rc=0;
  pthread_t             thread, thread2;

  printf("Запуск тестового набора - %s\n", argv[0]);

  printf("Главная нить, получение блокировки для записи\n");
  rc = pthread_rwlock_wrlock(&rwlock);
  compResults("pthread_rwlock_wrlock()\n", rc);

  printf("Главная нить, создание нитей, использующих блокировки для записи\n");
  rc = pthread_create(&thread, NULL, wrlockThread, NULL);
  compResults("pthread_create\n", rc);

  rc = pthread_create(&thread2, NULL, wrlockThread, NULL);
  compResults("pthread_create\n", rc);

  printf("Главная нить удерживает блокировку для записи\n");
  sleep(1);

  printf("Главная нить, освобождение блокировки для записи\n");
  rc = pthread_rwlock_unlock(&rwlock);
  compResults("pthread_rwlock_unlock()\n", rc);

  printf("Главная нить, ожидание завершения выполнения нитей\n");
  rc = pthread_join(thread, NULL);
  compResults("pthread_join\n", rc);

  rc = pthread_join(thread2, NULL);
  compResults("pthread_join\n", rc);

  rc = pthread_rwlock_destroy(&rwlock);
  compResults("pthread_rwlock_destroy()\n", rc);
  printf("Главная нить завершена\n");
  return 0;
}

Вывод этой программы будет выглядеть приблизительно следующим образом:

Запуск тестового набора - ./test02
Главная нить, получение блокировки для записи
Главная нить, создание нитей, использующих блокировки для записи
Главная нить удерживает блокировку для записи
00000102: Выполняется нить, получение блокировки для записи 
00000102: Выполнение других операций, затем повтор...
00000203: Выполняется нить, получение блокировки для записи
00000203: Выполнение других операций, затем повтор...
Главная нить, освобождение блокировки для записи
Главная нить, ожидание завершения выполнения нитей
00000102: Получена блокировка для записи
00000203: Выполнение других операций, затем повтор...
00000203: Выполнение других операций, затем повтор...
00000102: Освобождение блокировки для записи
00000102: Дополнительная нить завершена
00000203: Получена блокировка для записи
00000203: Освобождение блокировки для записи
00000203: Дополнительная нить завершена
Главная нить завершена

Пример захвата блокировки чтения-записи для чтения

В следующем примере продемонстрировано применение функции pthread_rwlock_rdlock для захвата блокировки чтения/записи для чтения:

Пример: test03.c

#define _MULTI_THREADED
#include pthread.h
#include stdio.h
#include "check.h"

pthread_rwlock_t       rwlock;

void *rdlockThread(void *arg)
{
  int rc;

  printf("Выполняется нить, получение блокировки для чтения\n");
  rc = pthread_rwlock_rdlock(&rwlock);
  compResults("pthread_rwlock_rdlock()\n", rc);
  printf("блокировка rwlock захвачена для чтения\n");

  sleep(5);

  printf("освобождение блокировки для чтения\n");
  rc = pthread_rwlock_unlock(&rwlock);
  compResults("pthread_rwlock_unlock()\n", rc);
  printf("Дополнительная нить разблокировала\n");
  return NULL;
}

void *wrlockThread(void *arg)
{
  int rc;

  printf("Выполняется нить, получение блокировки для записи\n");
  rc = pthread_rwlock_wrlock(&rwlock);
  compResults("pthread_rwlock_wrlock()\n", rc);

  printf("Блокировка rwlock захвачена для записи, освобождение блокировки\n");
  rc = pthread_rwlock_unlock(&rwlock);
  compResults("pthread_rwlock_unlock()\n", rc);
  printf("Дополнительная нить разблокировала\n");
  return NULL;
}



int main(int argc, char **argv)
{
  int                   rc=0;
  pthread_t             thread, thread1;

  printf("Запуск тестового набора - %s\n", argv[0]);

  printf("Главная нить, инициализация блокировки чтения-записи\n");
  rc = pthread_rwlock_init(&rwlock, NULL);
  compResults("pthread_rwlock_init()\n", rc);

  printf("Главная нить, захват блокировки для чтения\n");
  rc = pthread_rwlock_rdlock(&rwlock);
  compResults("pthread_rwlock_rdlock()\n",rc);

  printf("Главная нить, повторный захват этой же блокировки для чтения\n");
  rc = pthread_rwlock_rdlock(&rwlock);
  compResults("pthread_rwlock_rdlock() second\n", rc);

  printf("Главная нить, создание нити для захвата блокировки для чтения\n");
  rc = pthread_create(&thread, NULL, rdlockThread, NULL);
  compResults("pthread_create\n", rc);

  printf("Главная нить - освобождение первой блокировки для чтения\n");
  rc = pthread_rwlock_unlock(&rwlock);
  compResults("pthread_rwlock_unlock()\n", rc);

  printf("Главная нить, создание нити для захвата блокировки для записи\n");
  rc = pthread_create(&thread1, NULL, wrlockThread, NULL);
  compResults("pthread_create\n", rc);

  sleep(5);
  printf("Главная нить - освобождение второй блокировки для чтения\n");
  rc = pthread_rwlock_unlock(&rwlock);
  compResults("pthread_rwlock_unlock()\n", rc);

  printf("Главная нить, ожидание завершения нитей\n");
  rc = pthread_join(thread, NULL);
  compResults("pthread_join\n", rc);

  rc = pthread_join(thread1, NULL);
  compResults("pthread_join\n", rc);

  rc = pthread_rwlock_destroy(&rwlock);
  compResults("pthread_rwlock_destroy()\n", rc);

  printf("Главная нить завершена\n");
  return 0;
}

Вывод этой программы будет выглядеть приблизительно следующим образом:

$ ./test03
Запуск тестового набора - ./test03
Главная нить, инициализация блокировки чтения-записи
Главная нить, захват блокировки для чтения
Главная нить, повторный захват этой же блокировки для чтения
Главная нить, создание нити для захвата блокировки для чтения
Главная нить - освобождение первой блокировки для чтения
Главная нить, создание нити для захвата блокировки для записи
Выполняется нить, получение блокировки для чтения
блокировка rwlock захвачена для чтения
Выполняется нить, получение блокировки для записи
Главная нить - освобождение второй блокировки для чтения
Главная нить, ожидание завершения нитей
освобождение блокировки для чтения
Дополнительная нить разблокировала
Блокировка rwlock захвачена для записи, освобождение блокировки
Дополнительная нить разблокировала
Главная нить завершена