[ Страница назад | Страница вперед | Содержание | Индекс | Библиотека | Юридическая информация | Поиск ]

Программирование: Разработка и отладка программ


Создание сложных объектов синхронизации

С помощью функций из библиотеки работы с нитями можно создавать более сложные объекты синхронизации. В этом разделе приведены примеры использования некоторых таких объектов:

Долговременные блокировки

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

Следующий пример является самым простым. Принадлежность блокировки не проверяется, и любая нить может снять любую блокировку. Обработка ошибок и процедуры завершения нитей не выполняются. Как будет описано ниже, если разрешено завершение работы нитей, то устанавливать продолжительные блокировки не следует. Согласование данных при очистке показано в следующем примере (Семафоры). В этом примере показан типичный способ применения переменных условия.

Продолжительные блокировки относятся к типу данных 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. Операции P и V выполняют процедуры 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);
        sem->count++;
        if (sem->count <= 0)
                pthread_cond_signal(&sem->cond);
        pthread_mutex_unlock(&sem->lock);
}

Счетчик задает число пользователей, которые могут получить семафор. Это значение никогда не бывает строго отрицательным; таким образом, в отличие от стандартных семафоров, здесь оно не означает число ожидающих пользователей. Такой подход иллюстрирует стандартное решение проблемы множественной активации при выполнение функции pthread_cond_wait. Обратите внимание, что операцию P можно завершать, так как в функции pthread_cond_wait есть точка завершения.

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

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

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

Блокировка для чтения и записи с приоритетом записи относится к типу данных 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. По этой причине программы очистки нужно регистрировать до вызова этой функции.

Связанная информация

Рекомендации по созданию программ с нитями.

Дополнительные функции работы с нитями.

Обзор синхронизации.

Список дополнительных функций для работы с нитями.


[ Страница назад | Страница вперед | Содержание | Индекс | Библиотека | Юридическая информация | Поиск ]