Блокирует ли помещение в очередь синхронизации GCD блокировку, которая блокирует и приостанавливает другие? - PullRequest
4 голосов
/ 08 сентября 2011

Я прочитал, что синхронные очереди GCD (dispatch_sync) должны использоваться для реализации критических участков кода. Примером может служить блок, который вычитает сумму транзакции из баланса счета. Интересной частью вызовов синхронизации является вопрос, как это влияет на работу других блоков в нескольких потоках?

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

ИМХО, неважно, одно или несколько ядер, режим синхронизации должен заморозить всю работу приложения. Тем не менее, это только мои мысли, поэтому, пожалуйста, прокомментируйте это и поделитесь своими мыслями:)

Ответы [ 3 ]

7 голосов
/ 08 сентября 2011

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

dispatch_sync(somewhere, ^{ something });
// Reached later, when the block is finished.

dispatch_async(somewhere, ^{ something });
// Reached immediately. The block might be waiting
// to be executed, executing or already finished.

И есть два вида очередей отправки: последовательная и параллельная. Последовательные отправляют блоки строго один за другим в порядке их добавления. Когда один заканчивается, другой начинается. Для такого выполнения необходим только один поток. Параллельные очереди отправляют блоки одновременно, параллельно. Там используются другие темы.

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

- (void) addFoo: (id) foo {
    dispatch_sync(guardingQueue, ^{ [sharedFooArray addObject:foo]; });
}

- (void) removeFoo: (id) foo {
    dispatch_sync(guardingQueue, ^{ [sharedFooArray removeObject:foo]; });
}

Теперь, если guardingQueue - последовательная очередь, операции добавления / удаления никогда не могут конфликтовать, даже если методы addFoo: и removeFoo: вызываются одновременно из разных потоков.

2 голосов
/ 08 сентября 2011

Нет, это не так.

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

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

Это не влияет на другие очереди.

1 голос
/ 08 сентября 2011

Если вам нужно сериализовать доступ к определенному ресурсу, то вам доступны как минимум два механизма.Если у вас есть объект учетной записи (который уникален для данного номера учетной записи), то вы можете сделать что-то вроде:

@synchronize(accountObject) { ... }

Если у вас нет объекта, но вы используете структуру C, для которой существуеттолько одна такая структура для данного номера учетной записи, то вы можете сделать следующее:

// Should be added to the account structure. 
// 1 => at most 1 object can access accountLock at a time.
dispatch_semaphore_t accountLock = dispatch_semaphore_create(1);

// In your block you do the following:
block = ^(void) {
    dispatch_semaphore_wait(accountLock,DISPATCH_TIME_FOREVER);
    // Do something
    dispatch_semaphore_signal(accountLock);
};

// -- Edited: semaphore was leaking.
// At the appropriate time release the lock
// If the semaphore was created in the init then 
// the semaphore should be released in the release method.
dispatch_release(accountLock);

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

Существует еще много типов объектов синхронизации, но эти два просты в использовании и достаточно гибки.

...