Создание сложных объектов синхронизации
С помощью функций из библиотеки работы с нитями можно создавать более сложные объекты синхронизации.
Долговременные блокировки
Предусмотренные в библиотеке взаимные блокировки не отвечают требованиям активной конкуренции, поэтому их не следует устанавливать на длительное время. Для установки продолжительных блокировок используют взаимные блокировки и переменные условия, что позволяет установить блокировку на длительное время, не снижая производительность программы. Продолжительные блокировки не следует устанавливать в том случае, если разрешена отмена нитей.
Продолжительные блокировки относятся к типу данных long_lock_t и инициализируются с помощью функции long_lock_init. Функции long_lock, long_trylock и long_unlock выполняют операции, аналогичные pthread_mutex_lock, pthread_mutex_trylock и pthread_mutex_unlock.
typedef struct {
pthread_mutex_t lock;
pthread_cond_t cond;
int free;
int wanted;
} long_lock_t;
void long_lock_init(long_lock_t *ll)
{
pthread_mutex_init(&ll->lock, NULL);
pthread_cond_init(&ll->cond);
ll->free = 1;
ll->wanted = 0;
}
void long_lock_destroy(long_lock_t *ll)
{
pthread_mutex_destroy(&ll->lock);
pthread_cond_destroy(&ll->cond);
}
void long_lock(long_lock_t *ll)
{
pthread_mutex_lock(&ll->lock);
ll->wanted++;
while(!ll->free)
pthread_cond_wait(&ll->cond);
ll->wanted--;
ll->free = 0;
pthread_mutex_unlock(&ll->lock);
}
int long_trylock(long_lock_t *ll)
{
int got_the_lock;
pthread_mutex_lock(&ll->lock);
got_the_lock = ll->free;
if (got_the_lock)
ll->free = 0;
pthread_mutex_unlock(&ll->lock);
return got_the_lock;
}
void long_unlock(long_lock_t *ll)
{
pthread_mutex_lock(&ll->lock);
ll->free = 1;
if (ll->wanted)
pthread_cond_signal(&ll->cond);
pthread_mutex_unlock(&ll->lock);
}Семафоры
Одно из стандартных средств синхронизации процессов - это семафоры систем UNIX. При необходимости можно реализовать семафоры для синхронизации нитей.
Семафор относится к типу данных sema_t, инициализируется функцией sema_init, а удаляется функцией sema_destroy. Операции ожидания и записи значения семафора выполняют функции sema_p и sema_v.
typedef struct {
pthread_mutex_t lock;
pthread_cond_t cond;
int count;
} sema_t;
void sema_init(sema_t *sem)
{
pthread_mutex_init(&sem->lock, NULL);
pthread_cond_init(&sem->cond, NULL);
sem->count = 1;
}
void sema_destroy(sema_t *sem)
{
pthread_mutex_destroy(&sem->lock);
pthread_cond_destroy(&sem->cond);
}
void p_operation_cleanup(void *arg)
{
sema_t *sem;
sem = (sema_t *)arg;
pthread_mutex_unlock(&sem->lock);
}
void sema_p(sema_t *sem)
{
pthread_mutex_lock(&sem->lock);
pthread_cleanup_push(p_operation_cleanup, sem);
while (sem->count <= 0)
pthread_cond_wait(&sem->cond, &sem->lock);
sem->count--;
/*
* Обратите внимание: функция pthread_cleanup_pop
* будет выполнять функцию p_operation_cleanup
*/
pthread_cleanup_pop(1);
}
void sema_v(sema_t *sem)
{
pthread_mutex_lock(&sem->lock);
if (sem->count <=0)
pthread_cond_signal(&sem->cond);
sem->count++;
pthread_mutex_unlock(&sem->lock);
}Счетчик задает число пользователей, которые могут получить семафор. Это значение никогда не бывает строго отрицательным; таким образом, в отличие от стандартных семафоров, здесь оно не означает число ожидающих пользователей. Такой подход иллюстрирует стандартное решение проблемы множественной активации при выполнение функции pthread_cond_wait. Операцию ожидания семафора можно отменить, так как в функции pthread_cond_wait предусмотрена точка отмены.
Блокировка для чтения и записи с приоритетом записи
Блокировка для чтения/записи с приоритетом записи предоставляет нескольким нитям возможность одновременного доступа к защищенному ресурсу для чтения и одной нити - для записи (запрещая при этом операции чтения). После снятия блокировки записывающей нитью остальные нити, ожидающие доступа на запись, имеют приоритет перед любой из считывающих нитей. Блокировки для чтения/записи с приоритетом записи обычно применяют для защиты тех ресурсов, которые чаще считывают, чем записывают.
Блокировка для чтения и записи с приоритетом записи относится к типу данных rwlock_t и инициализируется с помощью функции rwlock_init. Функция rwlock_lock_read блокирует ресурс для считывающей нити (или нитей), а функция rwlock_unlock_read - разблокирует. Функция rwlock_lock_write блокирует ресурс для записывающей нити, а rwlock_unlock_write - снимает блокировку. Важно правильно выбирать процедуры снятия блокировки для записывающих или считывающих нитей.
typedef struct {
pthread_mutex_t lock;
pthread_cond_t rcond;
pthread_cond_t wcond;
int lock_count; /* < 0 .. блокировано для записи */
/* > 0 .. блокировано для чтения */
/* = 0 .. никем не блокировано */
int waiting_writers; /* число ожидающих запросов на запись */
} rwlock_t;
void rwlock_init(rwlock_t *rwl)
{
pthread_mutex_init(&rwl->lock, NULL);
pthread_cond_init(&rwl->wcond, NULL);
pthread_cond_init(&rwl->rcond, NULL);
rwl->lock_count = 0;
rwl->waiting_writers = 0;
}
void waiting_reader_cleanup(void *arg)
{
rwlock_t *rwl;
rwl = (rwlock_t *)arg;
pthread_mutex_unlock(&rwl->lock);
}
void rwlock_lock_read(rwlock_t *rwl)
{
pthread_mutex_lock(&rwl->lock);
pthread_cleanup_push(waiting_reader_cleanup, rwl);
while ((rwl->lock_count < 0) && (rwl->waiting_writers))
pthread_cond_wait(&rwl->rcond, &rwl->lock);
rwl->lock_count++;
/*
* Обратите внимание: функция pthread_cleanup_pop
* будет выполнять функцию waiting_reader_cleanup
*/
pthread_cleanup_pop(1);
}
void rwlock_unlock_read(rwlock_t *rwl)
{
pthread_mutex_lock(&rwl->lock);
rwl->lock_count--;
if (!rwl->lock_count)
pthread_cond_signal(&rwl->wcond);
pthread_mutex_unlock(&rwl->lock);
}
void waiting_writer_cleanup(void *arg)
{
rwlock_t *rwl;
rwl = (rwlock_t *)arg;
rwl->waiting_writers--;
if ((!rwl->waiting_writers) && (rwl->lock_count >= 0))
/*
* Выполняется только в случае завершения
*/
pthread_cond_broadcast(&rwl->wcond);
pthread_mutex_unlock(&rwl->lock);
}
void rwlock_lock_write(rwlock_t *rwl)
{
pthread_mutex_lock(&rwl->lock);
rwl->waiting_writers++;
pthread_cleanup_push(waiting_writer_cleanup, rwl);
while (rwl->lock_count)
pthread_cond_wait(&rwl->wcond, &rwl->lock);
rwl->lock_count = -1;
/*
* Обратите внимание: функция pthread_cleanup_pop
* будет выполнять функцию waiting_writer_cleanup
*/
pthread_cleanup_pop(1);
}
void rwlock_unlock_write(rwlock_t *rwl)
{
pthread_mutex_lock(&rwl->lock);
l->lock_count = 0;
if (!rwl->wating_writers)
pthread_cond_broadcast(&rwl->rcond);
else
pthread_cond_signal(&rwl->wcond);
pthread_mutex_unlock(&rwl->lock);
}В отношении считывающих нитей выполняется только их подсчет. Когда их число станет равным нулю, блокировка может быть передана ожидающей записывающей нити. Блокировка может принадлежать только одной записывающей нити. После снятия блокировки будет активизирована следующая записывающая нить, если она есть. В противном случае будут активизированные все ожидающие читающие нити.
Функции блокировки можно отменить, так как они вызывают функцию pthread_cond_wait. В связи с этим перед вызовом этой функции регистрируются процедуры очистки.