locking一个对象被多个线程访问 – Objective-C
在Objective-C中,我有一个关于线程安全性的问题。 我已经阅读了一些其他的答案,一些苹果的文档,并仍然有一些怀疑,所以想我会问自己的问题。
我的问题是三重 :
假设我有一个数组, 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。 如果我有一个数据库文件,我不会一直保存在内存中。 每当我想执行数据库操作时,我都会创build一个databaseManagerInstance
,并在完成后释放它。 因此,基本上,不同的类可以访问数据库。 每个类创build自己的DatabaseManger
实例,但基本上它们都使用相同的单个数据库文件。 在这种情况下,如何确保数据没有因为竞争条件而被破坏?
这将帮助我清除我的一些基本面。 在此先感谢您的帮助。
干杯!
折叠1通常你对@synchronized
的理解是正确的。 但是,从技术上讲,它不会使任何代码“线程安全”。 它可以防止不同的线程同时获得相同的锁,但是您需要确保在执行关键部分时总是使用相同的同步令牌。 如果你不这样做,你仍然可以发现自己处于两个线程同时执行关键部分的情况。 检查文档 。
折叠2大多数人可能会build议你使用NSRecursiveLock。 如果我是你,我会用GCD。 这是一个很好的文档,展示了如何从线程编程迁移到GCD编程 ,我认为这个问题的方法比基于NSLock的方法要好得多。 简而言之,您将创build一个串行队列并将您的任务分派到该队列中。 这样你就可以确保你的关键部分是连续处理的,所以在任何时候只有一个关键部分被执行。
折叠3这与折叠2相同,只是更具体。 数据库是一种资源,通过多种方式,它与数组或其他任何东西都是一样的。 如果您想在数据库编程上下文中看到基于GCD的方法,请查看fmdb实现 。 这正是我在Fold2中所描述的。
作为Fold 3的附注,我不认为每次要使用数据库时都要实例化DatabaseManager ,然后释放它才是正确的方法。 我认为你应该创build一个单一的数据库连接,并保留它通过你的应用程序会话。 这样pipe理起来就更容易了。 同样,fmdb就是如何实现这个目标的一个很好的例子。
编辑如果不想使用GCD,那么是的,你将需要使用某种locking机制,是的,如果你在方法中使用recursion, NSRecursiveLock
将防止死锁,所以这是一个不错的select(它被@synchronized
)。 但是,可能有一个捕获。 如果很multithreading可能会等待相同的资源,并且它们的访问顺序是相关的,那么NSRecursiveLock
是不够的。 您仍然可以用NSCondition
pipe理这种情况,但相信我,在这种情况下,您将使用GCD节省大量时间。 如果线程的顺序不相关,那么使用locking就是安全的。
就像WWDC 2016 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 } } } }
子类NSMutableArray为访问者(读写)方法提供locking。 就像是:
@interface MySafeMutableArray : NSMutableArray { NSRecursiveLock *lock; } @end @implementation MySafeMutableArray - (void)addObject:(id)obj { [self.lock lock]; [super addObject: obj]; [self.lock unlock]; } // ... @end
这种方法将locking作为数组的一部分进行封装。 用户不需要改变他们的呼叫(但是可能需要知道,如果访问时间关键,他们可以阻止/等待访问)。 这种方法的一个显着优点是,如果您决定不使用locking,则可以重新实现MySafeMutableArray来使用调度队列 – 或者任何最适合您的特定问题的方法。 例如,你可以实现addObject为:
- (void)addObject:(id)obj { dispatch_sync (self.queue, ^{ [super addObject: obj] }); }
注意:如果使用锁,你肯定需要NSRecursiveLock,而不是NSLock,因为你不知道addObject的Objective-C实现,等等本身是recursion的。