GCD - 串行队列是否需要“NSLock”或内存屏障来同步工作?
GCD - does a serial queue require an `NSLock` or a memory barrier to synchronize work?
我阅读了有关 GCD 队列的 Apple 文档,开始想知道如果我让修改类型为 NSMutableArray
的实例成员(它在串行队列中不是线程安全的)会发生什么情况?串行队列可以保证我串行执行操作,但我仍然觉得我需要做一个 @syncrhonized
块或其他技术来强制内存屏障,因为据我了解我串行上的任务队列可以在不同的线程上调用。那是对的吗?这是一个简单的例子:
@interface Foo : NSObject
-(void)addNumber:(NSNumber*)number;
-(void)printNumbers;
-(void)clearNumbers;
@end
@implementation Foo
{
dispatch_queue_t _queue;
NSMutableArray<NSNumber*>* _numbers;
}
-(instancetype)init
{
if (self = [super init])
{
_queue = dispatch_queue_create(NULL, NULL);
_numbers = [NSMutableArray array];
}
return self;
}
-(void)addNumber:(NSNumber*)number
{
dispatch_async(_queue,
^{
[_numbers addObject:number];
});
}
-(void)printNumbers
{
dispatch_async(_queue,
^{
for (NSNumber* number in _numbers)
{
NSLog(@“%@“, number);
}
});
}
-(void)clearNumbers
{
dispatch_async(_queue,
^{
_numbers = [NSMutableArray array];
});
}
@end;
据我所知,如果我从任意线程调用成员方法,我可以 运行 进入内存问题吗?或者 GCD 在底层提供了一些保证,为什么我不需要强制内存屏障?查看示例,我在任何地方都找不到这样的构造,但是来自 C++ 时,在锁下触摸成员变量是有意义的。
如果您的队列是一个串行队列,它将一次只允许一个操作,无论它运行在哪个线程上。因此,如果对资源的每次访问都发生在队列中,则无需使用锁或信号量进一步保护该资源。事实上,可以使用调度队列作为一种锁定机制,对于某些应用程序,它可以很好地工作。
现在,如果您的队列是 并发 队列,那就另当别论了,因为多个操作 can 运行 在同时在并发队列上。但是,GCD 提供了 dispatch_barrier_sync
和 dispatch_barrier_async
API。您通过这两个函数调用启动的操作将导致队列在执行您的块之前等待所有其他操作完成,然后在块完成之前不允许 运行ning 的任何更多操作。通过这种方式,它可以暂时使队列表现得像串行队列,甚至允许将并发队列用作一种锁定机制(例如,允许通过正常的 dispatch_sync
调用读取资源,但是通过 dispatch_barrier_async
进行写入。如果读取非常频繁而写入非常不频繁,这可以执行得很好)。
串行队列是一个数据锁,所以不需要进一步的锁定/同步,至少就这段代码而言是这样。同一个队列可以使用不同的线程执行这一事实是您不应该考虑的实现细节;队列是领域的硬币。
当然,在该队列与 main 队列之间共享数组可能存在问题,但那是另一回事。
我阅读了有关 GCD 队列的 Apple 文档,开始想知道如果我让修改类型为 NSMutableArray
的实例成员(它在串行队列中不是线程安全的)会发生什么情况?串行队列可以保证我串行执行操作,但我仍然觉得我需要做一个 @syncrhonized
块或其他技术来强制内存屏障,因为据我了解我串行上的任务队列可以在不同的线程上调用。那是对的吗?这是一个简单的例子:
@interface Foo : NSObject
-(void)addNumber:(NSNumber*)number;
-(void)printNumbers;
-(void)clearNumbers;
@end
@implementation Foo
{
dispatch_queue_t _queue;
NSMutableArray<NSNumber*>* _numbers;
}
-(instancetype)init
{
if (self = [super init])
{
_queue = dispatch_queue_create(NULL, NULL);
_numbers = [NSMutableArray array];
}
return self;
}
-(void)addNumber:(NSNumber*)number
{
dispatch_async(_queue,
^{
[_numbers addObject:number];
});
}
-(void)printNumbers
{
dispatch_async(_queue,
^{
for (NSNumber* number in _numbers)
{
NSLog(@“%@“, number);
}
});
}
-(void)clearNumbers
{
dispatch_async(_queue,
^{
_numbers = [NSMutableArray array];
});
}
@end;
据我所知,如果我从任意线程调用成员方法,我可以 运行 进入内存问题吗?或者 GCD 在底层提供了一些保证,为什么我不需要强制内存屏障?查看示例,我在任何地方都找不到这样的构造,但是来自 C++ 时,在锁下触摸成员变量是有意义的。
如果您的队列是一个串行队列,它将一次只允许一个操作,无论它运行在哪个线程上。因此,如果对资源的每次访问都发生在队列中,则无需使用锁或信号量进一步保护该资源。事实上,可以使用调度队列作为一种锁定机制,对于某些应用程序,它可以很好地工作。
现在,如果您的队列是 并发 队列,那就另当别论了,因为多个操作 can 运行 在同时在并发队列上。但是,GCD 提供了 dispatch_barrier_sync
和 dispatch_barrier_async
API。您通过这两个函数调用启动的操作将导致队列在执行您的块之前等待所有其他操作完成,然后在块完成之前不允许 运行ning 的任何更多操作。通过这种方式,它可以暂时使队列表现得像串行队列,甚至允许将并发队列用作一种锁定机制(例如,允许通过正常的 dispatch_sync
调用读取资源,但是通过 dispatch_barrier_async
进行写入。如果读取非常频繁而写入非常不频繁,这可以执行得很好)。
串行队列是一个数据锁,所以不需要进一步的锁定/同步,至少就这段代码而言是这样。同一个队列可以使用不同的线程执行这一事实是您不应该考虑的实现细节;队列是领域的硬币。
当然,在该队列与 main 队列之间共享数组可能存在问题,但那是另一回事。