Как определить, есть ли у потока блокировка?

Я пишу класс Objective-C, который я хочу быть потокобезопасным. Для этого я использую pthreads и pthread_rwlock (с помощью @synchronized излишне, и я хочу узнать немного больше о pthreads). Замок заложен в обозначенных объектах init метод и уничтожен в dealloc, У меня есть три метода манипулирования замком; readLock, writeLock, unlock, Эти три метода просто вызывают связанные функции pthread и в настоящее время ничего больше.

Вот два метода объектов, каждый из которых требует writeLock:

-(void)addValue:(const void *)buffer
{
    [self writeLock];

    NSUInteger lastIndex = self.lastIndex;
    [self setValue:buffer atIndex:(lastIndex == NSNotFound) ? 0 : lastIndex+1];

    [self unlock];
}


-(void)setValue:(const void *)buffer atIndex:(NSUInteger)index
{
    [self writeLock];
    //do work here
    [self unlock];
}

Вызов setAddValue: сначала получит блокировку записи, а затем вызовет setValue:atIndex: который также попытается получить блокировку записи. Документация заявляет, что поведение не определено, когда это происходит. Следовательно, как я могу проверить, есть ли у потока блокировка, прежде чем пытаться получить блокировку?

(Я мог бы гарантировать, что критическая секция не будет вызывать, что вызовет другой запрос блокировки, но это будет означать повторение кода, и я хочу сохранить мой код СУХИМЫМ).

3 ответа

Решение

Не совсем понятно, какой тип блокировки вы используете. Вы указываете, что используете pthreads и блокировку чтения / записи, поэтому я пришел к выводу, что вы используете pthread_rwlock.

Если это правда, то вы должны быть в состоянии использовать pthread_rwlock_trywrlock на замке. Со страницы руководства,

В случае успеха функции pthread_rwlock_wrlock() и pthread_rwlock_trywrlock() вернут ноль. В противном случае будет возвращен номер ошибки, чтобы указать на ошибку.

И одна из ошибок:

[EDEADLK] Вызывающий поток уже имеет блокировку чтения / записи (для чтения или записи).

Поэтому я считаю, что вы должны быть в состоянии позвонить pthread_rwlock_trywrlock() и вы либо будете успешны, он вернется EBUSY если другой поток имеет блокировку, или вы получите EDEADLK если текущий поток имеет блокировку.

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

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

Решение, вероятно, состоит в том, чтобы переместить readLock а также writeLock вызывает в вызывающие функции, но не зная больше, невозможно сказать.

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

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

int wrlock_wrap(pthread_rwlock_t *l, int *cnt)
{
    int r = *cnt ? 0 : pthread_rwlock_wrlocK(l);
    if (!r) ++*cnt;
    return r;
}

int wrunlock_wrap(pthread_rwlock_t *l, int *cnt)
{
    --*cnt;
    return pthread_rwlock_unlock(l);
}

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

Другие вопросы по тегам