【问题标题】:Is it safe to manipulate objects that I created outside my thread if I don't explicitly access them on the thread which created them?如果我没有在创建它们的线程上显式访问它们,那么操作我在线程之外创建的对象是否安全?
【发布时间】:2008-09-15 21:27:03
【问题描述】:

我正在开发一个可可软件,为了在大量数据导入(核心数据)期间保持 GUI 响应,我需要在主线程之外运行导入。

即使我在主线程中创建它们而不使用锁,访问这些对象是否安全如果我没有在线程运行时显式访问这些对象。

【问题讨论】:

    标签: cocoa multithreading macos core-data


    【解决方案1】:

    使用 Core Data,您应该有一个单独的托管对象上下文用于您的导入线程,连接到相同的协调器和持久存储。您不能简单地将在主线程使用的上下文中创建的对象扔到另一个线程中并期望它们工作。此外,您不能为此进行自己的锁定;您必须至少锁定对象所在的托管对象上下文,视情况而定。但是,如果这些对象被您的视图绑定到控件,则没有“钩子”可以添加上下文锁定。

    没有免费的午餐。

    Ben Trumbull 在this great post from late 2004 on the webobjects-dev list 中解释了您需要使用单独上下文的一些原因,以及为什么“仅阅读”不像您想象的那么简单或安全。 (整个线程都很棒。)他正在讨论 Enterprise Objects Framework 和 WebObjects,但他的建议也完全适用于 Core Data。只需在他的消息内容中将“EC”替换为“NSManagedObjectContext”,将“EOF”替换为“Core Data”即可。

    Core Data 中线程间共享数据问题的解决方案,就像之前的 Enterprise Objects Framework 一样,是“不要”。如果您进一步考虑过,并且确实确实必须在线程之间共享数据,那么解决方案是将独立的对象图保存在线程隔离的上下文中,并使用来自一个上下文的保存通知中的信息来告诉其他上下文要重新获取的内容。 -[NSManagedObjectContext refreshObject:mergeChanges:] 是专门为支持这种用途而设计的。

    【讨论】:

    • 对于任何遇到这种情况的人来说,时代已经改变,但 Core Data 的并发仍然以线程限制为中心。有关详细信息,请参阅 OS X 10.7 和 iOS 5.0 developer.apple.com/library/mac/#releasenotes/DataManagement/… 的核心数据发行说明。这个问题的上下文中最令人兴奋的变化是新的基于块的并发 API 和嵌套 NSManagedObjectContext 用于并发和隔离。
    【解决方案2】:

    我相信这对于由 CoreData NSManagedObjectContext 管理的 NSManagedObjects(或子类)是安全的。一般来说,CoreData 可能会对托管对象的状态做很多棘手的事情,包括在单独的线程中触发与这些对象相关的错误。特别是,[NSManagedObject initWithEntity:insertIntoManagedObjectContext:](从 OS X 10.5 开始为 NSManagedObjects 指定的初始化程序)不保证返回的对象可以安全地传递给其他线程。

    在 Apple 的 dev site 上详细记录了将 CoreData 用于多线程。

    【讨论】:

      【解决方案3】:

      使用锁的全部意义在于确保两个线程不会尝试访问同一个资源。如果你能通过其他机制保证,那就去吧。

      【讨论】:

        【解决方案4】:

        即使它是安全的,但在不同步对这些字段的访问的情况下在线程之间使用共享数据并不是最佳做法。不管是哪个线程创建了对象,但如果有多个执行行(线程/进程)同时访问该对象,可能会导致数据不一致。

        如果您绝对确定只有一个线程会访问该对象,那么不同步访问是安全的。即便如此,我还是宁愿现在就在我的代码中加入同步,而不是等到应用程序发生变化时,让第二个线程共享相同的数据,而不用担心同步访问。

        【讨论】:

          【解决方案5】:

          是的,它是安全的。一种非常常见的模式是创建一个对象,然后将其添加到队列或其他集合中。第二个“消费者”线程从队列中获取项目并对其进行处理。在这里,您需要同步队列,但不需要同步添加到队列中的对象。

          同步所有内容并希望获得最好的结果并不是一个好主意。您需要非常仔细地考虑您的设计以及哪些线程可以作用于您的对象。

          【讨论】:

            【解决方案6】:

            需要考虑的两件事是:

            • 您必须能够保证对象已完全创建并初始化,然后才可供其他线程使用。
            • 主 (GUI) 线程必须通过某种机制检测数据已加载且一切正常。为了线程安全,这将不可避免地涉及某种锁定。

            【讨论】:

              【解决方案7】:

              是的,你可以做到,这将是安全的

              ... 直到第二个程序员出现并且不理解您所做的相同假设。第二个(或第三个、第四个、第五个……)程序员可能会以非安全的方式(在创建者线程中)开始使用对象。造成的问题可能非常微妙且难以追踪。仅出于这个原因,并且因为在多个线程中使用这个对象非常诱人,我会让对象线程安全。

              澄清一下,(感谢那些离开 cmets 的人):

              “线程安全”是指以编程方式设计一种方案来避免线程问题。我不一定是指围绕您的对象设计一个锁定方案。您可以在您的语言中找到一种方法,使在创建者线程中使用该对象成为非法(或非常困难)。例如,在创建者线程中将范围限制为创建对象的代码块。创建后,将对象传递给用户线程,确保创建者线程不再引用它。

              例如,在 C++ 中

              void CreateObject()
              {
                  Object* sharedObj = new Object();
                  PassObjectToUsingThread( sharedObj); // this function would be system dependent
              }
              

              然后在你的创建线程中,创建后你不再可以访问对象,责任被传递给使用线程。

              【讨论】:

              • 大多数语言没有很多语言级工具来帮助进行并发编程。我们几乎被文档和开发人员沟通所困。我仍然认为使对象线程安全“以防万一”是一个坏主意,但也许使对象不可变是这里的最佳选择。
              • 一般来说,您不能“使对象成为线程安全的”。一个对象通常是一个更大的对象图的一部分,并且通常整个图都需要事务语义。这就是为什么我们按照我们的方式设计 Core Data,锁定上下文而不是对象。
              • C++ 示例具有误导性。尽管您可以使用 alloc/init 创建 NSObject 实例,然后按照示例中的方式进行操作,但 NSManagedObject 不保证您可以对其实例执行相同操作。
              猜你喜欢
              • 2013-01-16
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-08-04
              • 2011-01-15
              • 2013-07-06
              相关资源
              最近更新 更多