【问题标题】:Locking an object from being accessed by multiple threads - Objective-C锁定一个对象不被多个线程访问 - Objective-C
【发布时间】:2012-06-06 21:16:46
【问题描述】:

我对 Objective-C 中的线程安全有疑问。我已经阅读了其他几个答案,一些 Apple 文档,但对此仍有一些疑问,所以我想问我自己的问题。

我的问题是三折

假设我有一个数组,NSMutableArray *myAwesomeArray;

折叠 1:

如果我弄错了,请纠正我,但据我了解,使用@synchronized(myAwesomeArray){...} 会阻止两个线程访问同一个代码块。所以,基本上,如果我有类似的东西:

-(void)doSomething {
    @synchronized(myAwesomeArray) {
        //some read/write operation on myAwesomeArray
    }
}

那么,如果两个线程在same 时间访问same 方法,那么该代码块将是线程安全的。我猜我已经正确理解了这部分。

折叠 2:

如果myAwesomeArray 正被多个线程从不同的方法访问,我该怎么办? 如果我有类似的东西:

- (void)readFromArrayAccessedByThreadOne {
    //thread 1 reads from myAwesomeArray
}

- (void)writeToArrayAccessedByThreadTwo {
    //thread 2 writes to myAwesomeArray
}

现在,两个方法同时被两个不同的线程访问。如何确保myAwesomeArray 不会出现问题?我是否使用 NSLock 或 NSRecursiveLock 之类的东西?

3 折:

现在,在上述两种情况下,myAwesomeArray 是内存中的 iVar。如果我有一个数据库文件,我并不总是保存在内存中怎么办。每当我想执行数据库操作时,我都会创建一个databaseManagerInstance,并在完成后释放它。因此,基本上,不同的类可以访问数据库。每个类都创建自己的DatabaseManger 实例,但基本上,它们都使用相同的单一数据库文件。在这种情况下,如何确保数据不会因竞争条件而损坏?

这将帮助我理清一些基础知识。

【问题讨论】:

  • @synchronize 阻止其他线程访问您锁定的同一个变量,而不是特定的代码块。
  • 啊。我知道了。好吧,我想我对@synchronize 指令的理解还不止于此。谢谢! :D
  • @codeBearer 我在stackoverflow.com/a/15393623/412916 Kinda 回答了你的问题。

标签: objective-c ios multithreading


【解决方案1】:

折叠 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 将防止死锁,所以这是一个不错的选择(@ 987654327@)。但是,可能有一个问题。如果许多线程可能会等待相同的资源并且它们获得访问的顺序是相关的,那么NSRecursiveLock 是不够的。您仍然可以使用NSCondition 处理这种情况,但相信我,在这种情况下使用 GCD 会节省大量时间。如果线程的顺序不相关,则使用锁是安全的。

【讨论】:

  • 非常感谢您的回复!我想这可以解决我的大部分疑问。作为确认:如果我不使用 GCD,我猜我也会将 NSRecursiveLock 用于折叠 3,对吗?这更多是为了我的知识库,以便我清楚我的概念。此外,感谢您对 Fold 3 的附注。我同意你的观点,最好保留一个类,并在需要时打开和关闭连接。我只是重写了一些执行多个实例化的代码——那真是一场噩梦。 >_
  • 完美!再次感谢一吨! :D myKnowledge++ 我一直计划在我们所有的新项目中开始使用 GCD,您提供的链接是一个很好的起点! :)
  • +1 用于 GCD 和串行队列。避免问题的好方法
  • 在折叠 2 的变体中,您可以使用读写器模式(并发队列,使用dispatch_sync 读取,使用dispatch_barrier_async 写入),如WWDC 2012 video - Asynchronous Design Patterns 中所述。
  • 与 GCD 一样,下一个任务只会等到第二个任务完成,但是如果第一个任务正在运行,我根本不希望下一个任务运行怎么办?我只想在什么都没有运行的情况下运行下一个任务,但是可怜的 GCD 没有像 isRunningisExecuting 这样的方法,我们不知道 GCD 任务是否仍在执行
【解决方案2】:

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 }
    }
  }
}

【讨论】:

    【解决方案3】:

    子类 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 实现本身就是递归的。

    【讨论】:

    • 感谢您的回复。如果我要创建对象的线程安全子类,这是一个好主意。 :)
    • 如果您将线程安全留给调用者(也就是说,您没有创建线程安全的子类,如何实现),那么您将重复一遍,为自己设置问题。你会为自己学习一些关于 Spin 技术的知识。祝你好运。
    • 太棒了。谢谢你的提示。我会仔细看看的。 :D
    • NSArray 的任何子类也需要实现其他几个方法;这不是一个很好的例子。
    • @ilyan。如果您不提供任何细节来证实您的主张,您的评论也不是一个好评论。你能说得更具体点吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多