37

我对 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,但基本上,它们都使用相同的单个数据库文件。在这种情况下,如何确保数据不会因竞争条件而损坏?

这将帮助我理清一些基本面。

4

3 回答 3

43

折叠 1 通常你对做什么的理解@synchronized是正确的。但是,从技术上讲,它不会使任何代码“线程安全”。它可以防止不同的线程同时获取相同的锁,但是您需要确保在执行关键部分时始终使用相同的同步令牌。如果你不这样做,你仍然会发现自己处于两个线程同时执行临界区的情况。检查文档

折叠 2 大多数人可能会建议您使用 NSRecursiveLock。如果我是你,我会使用 GCD。这是一个很棒的文档,展示了如何从线程编程迁移到 GCD 编程,我认为这种解决问题的方法比基于 NSLock 的方法要好得多。简而言之,您创建一个串行队列并将您的任务分派到该队列中。这样可以确保您的关键部分是按顺序处理的,因此在任何给定时间只执行一个关键部分。

折叠 3 这与折叠 2相同,只是更具体。数据库是一种资源,在许多方面它与数组或任何其他东西相同。如果您想在数据库编程上下文中查看基于 GCD 的方法,请查看 fmdb implementation。它完全符合我在Fold2中的描述。

作为Fold 3的旁注,我不认为每次要使用数据库时都实例化DatabaseManager然后释放它是正确的方法。我认为您应该创建一个单一的数据库连接并通过您的应用程序会话保留它。这样更容易管理它。同样,fmdb 是一个很好的例子,说明了如何实现这一点。

编辑 如果不想使用 GCD,那么是的,您将需要使用某种锁定机制,是的,NSRecursiveLock如果您在方法中使用递归,将防止死锁,所以这是一个不错的选择(它被 使用@synchronized)。但是,可能有一个问题。如果许多线程可能会等待相同的资源并且它们获得访问的顺序是相关的,那么NSRecursiveLock是不够的。您仍然可以使用 来处理这种情况NSCondition,但相信我,在这种情况下使用 GCD 会节省大量时间。如果线程的顺序不相关,则使用锁是安全的。

于 2012-06-01T16:17:06.453 回答
5

Swift 3 in WWDC 2016 Session Session 720 Concurrent Programming With GCD in 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 }
    }
  }
}
于 2016-06-17T23:27:30.050 回答
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 实现等本身是递归的。

于 2012-06-01T16:06:34.080 回答