我对Objective-C中的线程安全性有疑问。我已经阅读了其他几个答案,一些Apple文档,但仍然对此有一些疑问,所以我想问自己的问题。
我的问题是三折:
假设我有一个数组NSMutableArray *myAwesomeArray;
折叠1:
如果我错了,现在纠正我,但根据我的理解,使用@synchronized(myAwesomeArray){...}
将阻止两个线程访问相同的代码块。所以,基本上,如果我有类似的东西:
-(void)doSomething {
@synchronized(myAwesomeArray) {
//some read/write operation on myAwesomeArray
}
}
然后,如果两个线程在相同时间访问相同的方法,那么该代码块将是线程安全的。我猜我已经理解了这部分。
折叠2:
如果来自不同方法的多个线程正在访问myAwesomeArray
,我该怎么办?
如果我有类似的东西:
- (void)readFromArrayAccessedByThreadOne {
//thread 1 reads from myAwesomeArray
}
- (void)writeToArrayAccessedByThreadTwo {
//thread 2 writes to myAwesomeArray
}
现在,两个方法同时由两个不同的线程访问。如何确保myAwesomeArray
不会出现问题?我是否使用类似NSLock或NSRecursiveLock的东西?
折叠3:
现在,在上述两种情况下,myAwesomeArray
在内存中是一个iVar。如果我有一个数据库文件,我并不总是留在内存中怎么办?每当我想执行数据库操作时,我都会创建一个databaseManagerInstance
,并在完成后释放它。因此,基本上,不同的类可以访问数据库。每个类都创建自己的DatabaseManger
实例,但基本上,它们都使用相同的单个数据库文件。在这种情况下,如何确保数据不会因竞争条件而损坏?
这将帮助我清除我的一些基本原理。
答案 0 :(得分:43)
折叠1
通常,您对@synchronized
所做的事情的理解是正确的。但是,从技术上讲,它不会使任何代码“线程安全”。它可以防止不同的线程同时获取同一个锁,但是您需要确保在执行关键部分时始终使用相同的同步令牌。如果不这样做,您仍然可以发现自己处于两个线程同时执行关键部分的情况。 Check the docs
折叠2 大多数人可能会建议你使用NSRecursiveLock。如果我是你,我会使用GCD。 Here is a great document showing how to migrate from thread programming to GCD programming,我认为这种方法比基于NSLock的方法要好得多。简而言之,您创建一个串行队列并将任务分派到该队列中。这样,您可以确保关键部分是按顺序处理的,因此在任何给定时间只执行一个关键部分。
折叠3 这与 Fold 2 相同,只是更具体。数据库是一种资源,通过许多方式它与数组或任何其他东西相同。 If you want to see the GCD based approach in database programming context, take a look at fmdb implementation。它完全符合我在 Fold2 中描述的内容。
作为 Fold 3 的附注,我不认为每次要使用数据库然后释放它都是实例化 DatabaseManager 是正确的方法。我认为你应该创建一个单一的数据库连接并通过你的应用程序会话保留它。这样就可以更轻松地进行管理。同样,fmdb是如何实现这一目标的一个很好的例子。
修改强>
如果不想使用GCD那么是,你将需要使用某种锁定机制,是的,NSRecursiveLock
将防止你的方法中使用递归的死锁,所以它是一个很好的选择(它被使用由@synchronized
)。但是,可能有一个问题。如果许多线程可能等待相同的资源并且它们获取访问的顺序是相关的,那么NSRecursiveLock
是不够的。您仍然可以使用NSCondition
来管理这种情况,但请相信我,在这种情况下,您将节省大量使用GCD的时间。如果线程的顺序不相关,那么使用锁是安全的。
答案 1 :(得分:5)
与WWDC 2016 Session Session 720 Concurrent Programming With GCD in Swift 3中的 Swift 3 一样,您应该使用queue
class MyObject {
private let internalState: Int
private let internalQueue: DispatchQueue
var state: Int {
get {
return internalQueue.sync { internalState }
}
set (newValue) {
internalQueue.sync { internalState = newValue }
}
}
}
答案 2 :(得分:1)
子类NSMutableArray为访问器(读取和写入)方法提供锁定。类似的东西:
@interface MySafeMutableArray : NSMutableArray { NSRecursiveLock *lock; } @end
@implementation MySafeMutableArray
- (void)addObject:(id)obj {
[self.lock lock];
[super addObject: obj];
[self.lock unlock];
}
// ...
@end
此方法将锁定封装为数组的一部分。用户无需更改其呼叫(但可能需要知道,如果访问时间紧迫,他们可以阻止/等待访问)。这种方法的一个显着优点是,如果您决定不使用锁,则可以重新实现MySafeMutableArray以使用调度队列 - 或者对您的特定问题最有效的方法。例如,您可以将addObject实现为:
- (void)addObject:(id)obj {
dispatch_sync (self.queue, ^{ [super addObject: obj] });
}
注意:如果使用锁,你肯定需要NSRecursiveLock,而不是NSLock,因为你不知道addObject的Objective-C实现等本身是递归的。