Ответ 1
GCD не гарантирует, что два блока будут выполняться в одном потоке, даже если они принадлежат одной и той же очереди (за исключением основной очереди, конечно). Однако, если вы используете последовательную очередь (DISPATCH_QUEUE_SERIAL
), это не проблема, так как вы знаете, что одновременного доступа к вашим данным нет.
Страница man для dispatch_queue_create
говорит:
Очереди не привязаны к какому-либо конкретному потоку выполнения, и блоки, отправленные в независимые очереди, могут выполняться одновременно.
Я не знаю ни одного способа привязать очередь к определенному потоку (в конце концов, не нужно заботиться о потоках - это главное в GCD). Причина, по которой вы можете использовать последовательную очередь, не беспокоясь о реальном потоке, заключается в следующем:
Все операции записи в память, выполняемые блоком, отправленным в последовательную очередь, гарантированно будут видимы для последующих блоков, отправляемых в ту же очередь.
То есть барьер памяти, похоже, используется.
При работе с проблемами многопоточности ваша главная задача, как правило, состоит в том, чтобы избежать одновременного доступа к двум потокам. Если вы используете последовательную очередь, у вас нет этой проблемы. Обычно не имеет значения, какой поток обращается к вашим ресурсам. Например, мы используем последовательные очереди для управления доступом к основным данным без проблем.
Редактировать:
Кажется, вы действительно нашли редкий случай, когда вам нужно работать над одной и той же веткой. Вы можете реализовать свой собственный рабочий поток:
- Предпосылки:
- NSMutableArray (пусть его называют
blockQueue
). - Условие NSC (пусть оно называется
queueCondition
).
- NSMutableArray (пусть его называют
- Создайте новый NSThread.
- Метод потока имеет бесконечный цикл, в котором он блокирует условие, ожидает его, если очередь пуста (а bool "quit" имеет значение false), удаляет блок из очереди и выполняет его.
- Метод, который блокирует условие и ставит блок в очередь.
Из-за условия поток просто будет спать, пока не будет работы.
Итак, примерно (не проверено, при условии ARC):
- (void)startWorkerThread
{
workerThread = [[NSThread alloc]
initWithTarget:self
selector:@selector(threadMain)
object:nil
];
[workerThread start];
}
- (void)threadMain
{
void (^block)();
NSThread *currentThread;
currentThread = [NSThread currentThread];
while (1) {
[queueCondition lock];
{
while ([blockQueue count] == 0 && ![currentThread isCancelled]) {
[queueCondition wait];
}
if ([currentThread isCancelled]) {
[queueCondition unlock];
return;
}
block = [blockQueue objectAtIndex:0];
[blockQueue removeObjectAtIndex:0];
}
[queueCondition unlock];
// Execute block outside the condition, since it also a lock!
// We want to give other threads the possibility to enqueue
// a new block while we're executing a block.
block();
}
}
- (void)enqueue:(void(^)())block
{
[queueCondition lock];
{
// Copy the block! IIRC you'll get strange things or
// even crashes if you don't.
[blockQueue addObject:[block copy]];
[queueCondition signal];
}
[queueCondition unlock];
}
- (void)stopThread
{
[queueCondition lock];
{
[workerThread cancel];
[queueCondition signal];
}
[queueCondition unlock];
}
Непроверенный порт Swift 5:
var workerThread: Thread?
var blockQueue = [() -> Void]()
let queueCondition = NSCondition()
func startWorkerThread() {
workerThread = Thread() {
let currentThread = Thread.current
while true {
self.queueCondition.lock()
while self.blockQueue.isEmpty && !currentThread.isCancelled {
self.queueCondition.wait()
}
if currentThread.isCancelled {
self.queueCondition.unlock()
return
}
let block = self.blockQueue.remove(at: 0)
self.queueCondition.unlock()
// Execute block outside the condition, since it also a lock!
// We want to give other threads the possibility to enqueue
// a new block while we're executing a block.
block()
}
}
workerThread?.start()
}
func enqueue(_ block: @escaping () -> Void) {
queueCondition.lock()
blockQueue.append(block)
queueCondition.signal()
queueCondition.unlock()
}
func stopThread() {
queueCondition.lock()
workerThread?.cancel()
queueCondition.signal()
queueCondition.unlock()
}