Поточное уничтожение блокировки чтения-записи в C

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

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

Если вызывается destroy и никакой другой поток не находится в блокировке, он блокирует wrt (используемый писателями), чтобы предотвратить доступ любого другого потока к данным, защищенным блокировкой. Затем функция уничтожения должна уничтожить семафоры и освободить память, выделенную для структуры ReadWriteLock. Но что, если другой поток ожидает блокировки? Согласно документации этот поток останется в неопределенном состоянии.

Это то, чего я пытаюсь избежать, чтобы упростить использование блокировки.

РЕДАКТИРОВАТЬ:

текущий код:

typedef struct ReadWriteLock
{
sem_t wrt;
sem_t mtx;
sem_t delFlag;
int readcount;
int active;
}ReadWriteLock;

//forward declaration
/* This function is used to take the state of the lock.
 * Return values:
 *      [*] 1 is returned when the lock is alive.
 *      [*] 0 is returned when the lock is marked for delete.
 *      [*] -1 is returned if an error was encountered.
 */
int isActive(ReadWriteLock*);

int rwl_init(ReadWriteLock* lock)
{
lock = malloc(sizeof(ReadWriteLock));
if (lock == NULL)
{
    perror("rwl_init - could not allocate memory for lock\n");
    return -1;
}
if (sem_init(&(lock->wrt), 0, 1) == -1)
{
    perror("rwl_init - could not allocate wrt semaphore\n");
    free(lock);
    lock = NULL;
    return -1;
}
if (sem_init(&(lock->mtx), 0, 1) == -1)
{
    perror("rwl_init - could not allocate mtx semaphore\n");
    sem_destroy(&(lock->wrt));
    free(lock);
    lock = NULL;
    return -1;
}
if (sem_init(&(lock->delFlag), 0 , 1) == -1)
{
    perror("rwl_init - could not allocate delFlag semaphore\n");
    sem_destroy(&(lock->wrt));
    sem_destroy(&(lock->mtx));
    free(lock);
    lock = NULL;
    return -1;
}

lock->readcount = 0;
lock->active = 1;
return 0;
}

int rwl_destroy(ReadWriteLock* lock)
{
errno = 0;
if (sem_trywait(&(lock->wrt)) == -1)
    perror("rwl_destroy - trywait on wrt failed.");
if ( errno == EAGAIN)
    perror("rwl_destroy - wrt is locked, undefined behaviour.");

errno = 0;
if (sem_trywait(&(lock->mtx)) == -1)
    perror("rwl_destroy - trywait on mtx failed.");
if ( errno == EAGAIN)
    perror("rwl_destroy - mtx is locked, undefined behaviour.");

if (sem_destroy(&(lock->wrt)) == -1)
    perror("rwl_destroy - destroy wrt failed");
if (sem_destroy(&(lock->mtx)) == -1)
    perror("rwl_destroy - destroy mtx failed");
if (sem_destroy(&(lock->delFlag)) == -1)
    perror("rwl_destroy - destroy delFlag failed");

free(lock);
lock = NULL;
return 0;
}

int isActive(ReadWriteLock* lock)
{
errno = 0;
if (sem_trywait(&(lock->delFlag)) == -1)
{
    perror("isActive - trywait on delFlag failed.");
    return -1;
}
if ( errno == EAGAIN)
{//delFlag is down, lock is marked for delete
    perror("isActive - tried to lock but ReadWriteLock was marked for delete");
    return 0;
}
return 1;
}

У меня также есть эти функции:

int rwl_writeLock(ReadWriteLock*);

int rwl_writeUnlock(ReadWriteLock*);

int rwl_readLock(ReadWriteLock*);

int rwl_readUnlock(ReadWriteLock*);

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

Функция isActive() и семафор delFlag в настоящее время не используются, они были сделаны в моей попытке решить проблему.


person JasonPap    schedule 20.01.2015    source источник
comment
Пожалуйста, включите в вопрос код, с которым у вас возникли проблемы. Если ссылка умрет, вопрос останется здесь без контекста.   -  person xxbbcc    schedule 20.01.2015
comment
Это вопрос управления на всю жизнь. Если программа использует объект после того, как он был уничтожен, это обычно называется ошибкой.   -  person Nick Zavaritsky    schedule 20.01.2015
comment
@xxbbcc Я мог бы опубликовать код, но не думаю, что это поможет, проблема более теоретическая. Я обновлю свой вопрос.   -  person JasonPap    schedule 20.01.2015
comment
Я не думаю, что ReadWriteLock* является правильным типом для rwl_init -- указатель malloced в rwl_init эффективно теряется после возврата функции. Вы можете предпочесть ReadWriteLock***lock = malloc(...) и т. д.). Аналогично, lock = NULL в rwl_destroy не имеет длительного эффекта.   -  person Dirk    schedule 20.01.2015
comment
@Dirk Спасибо, что указали на это, я исправляю это. До сих пор остается открытым вопрос о разрушении замка.   -  person JasonPap    schedule 21.01.2015


Ответы (1)


Вы должны реализовать «удаленное» состояние вашего экземпляра ReadWriteLock (поле «активное» выглядит уместно, но вы его не используете, почему?).

Проверьте это дважды в rwl_writeLock / rwl_readLock, до и после вызова sem_wait(). Этот прием известен как «шаблон блокировки с двойной проверкой». Если вы обнаружите, что ваша блокировка будет удалена до входа в sem_wait, просто покиньте функцию. Если вы обнаружите, что ваша блокировка будет удалена после ввода sem_wait, немедленно выполните sem_post и уходите.

В вашей процедуре destroy() установите active=0, затем sem_post для обоих семафоров (не беспокойтесь, если sem_post не работает). Если после этого вам все еще нужен sem_destroy, подождите немного (чтобы у всех читателей и авторов было время получить сигнал) и выполните sem_destroy.

P.S. На самом деле вам не нужно вызывать sem_destroy, если вы уверены, что семафор больше не используется.

person Yury Rumega    schedule 20.01.2015
comment
Синхронизация с помощью sleep() является широко известным антипаттерном. - person Nick Zavaritsky; 20.01.2015
comment
@NickZavaritsky: самописная реализация rwlock, предназначенная для динамического размещения в куче и уничтожения в произвольный момент времени ... Да, это правильное место для заботы об антипаттернах. Вы действительно думаете, что у OP есть хорошая процедура завершения, которая позволяет ему синхронизировать уничтожение рабочих потоков? .. Хорошо, я не должен спрашивать... - person Yury Rumega; 20.01.2015
comment
@NickZavaritsky Я понимаю, почему вы называете использование sleep () антипаттерном, но что вы предлагаете вместо этого? Я до сих пор не могу придумать что-то неправильное в предложении Юрия, но жду других мнений. - person JasonPap; 21.01.2015
comment
@JasonPap Использование сна можно избежать, внедрив процедуру завершения. Если у вас есть подпрограмма завершения, которая выполняется после остановки всех рабочих потоков, вы можете разделить метод rwlock_destroy() на два: rwlock_close и rwlock_destory. Затем установите active=0 при закрытии и вызовите sem_destroy при уничтожении. Но для этого вам нужно будет присоединиться к рабочим потокам. - person Yury Rumega; 21.01.2015
comment
@YuryRumega Я не хочу присоединяться к какой-либо теме, поскольку она не создана этой структурой API. Потоки могут выполнять какую-то другую работу для пользователя, поэтому я не могу решить их закончить. - person JasonPap; 21.01.2015
comment
@JasonPap Хорошо, я понял тебя. Вы также можете подумать о реализации объекта ссылки блокировки с подсчетом ссылок. - person Yury Rumega; 21.01.2015
comment
@YuryRumega Я не уверен, что понял то, что вы предлагаете. С другой стороны, блокировка чтения-записи POSIX имеет ту же проблему, что и моя. См. ссылку . Результаты не определены, если pthread_rwlock_destroy() вызывается, когда какой-либо поток удерживает rwlock. - person JasonPap; 21.01.2015