【发布时间】:2008-09-15 21:27:03
【问题描述】:
我正在开发一个可可软件,为了在大量数据导入(核心数据)期间保持 GUI 响应,我需要在主线程之外运行导入。
即使我在主线程中创建它们而不使用锁,访问这些对象是否安全如果我没有在线程运行时显式访问这些对象。
【问题讨论】:
标签: cocoa multithreading macos core-data
我正在开发一个可可软件,为了在大量数据导入(核心数据)期间保持 GUI 响应,我需要在主线程之外运行导入。
即使我在主线程中创建它们而不使用锁,访问这些对象是否安全如果我没有在线程运行时显式访问这些对象。
【问题讨论】:
标签: cocoa multithreading macos core-data
使用 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:] 是专门为支持这种用途而设计的。
【讨论】:
我相信这对于由 CoreData NSManagedObjectContext 管理的 NSManagedObjects(或子类)是不安全的。一般来说,CoreData 可能会对托管对象的状态做很多棘手的事情,包括在单独的线程中触发与这些对象相关的错误。特别是,[NSManagedObject initWithEntity:insertIntoManagedObjectContext:](从 OS X 10.5 开始为 NSManagedObjects 指定的初始化程序)不保证返回的对象可以安全地传递给其他线程。
在 Apple 的 dev site 上详细记录了将 CoreData 用于多线程。
【讨论】:
使用锁的全部意义在于确保两个线程不会尝试访问同一个资源。如果你能通过其他机制保证,那就去吧。
【讨论】:
即使它是安全的,但在不同步对这些字段的访问的情况下在线程之间使用共享数据并不是最佳做法。不管是哪个线程创建了对象,但如果有多个执行行(线程/进程)同时访问该对象,可能会导致数据不一致。
如果您绝对确定只有一个线程会访问该对象,那么不同步访问是安全的。即便如此,我还是宁愿现在就在我的代码中加入同步,而不是等到应用程序发生变化时,让第二个线程共享相同的数据,而不用担心同步访问。
【讨论】:
是的,它是安全的。一种非常常见的模式是创建一个对象,然后将其添加到队列或其他集合中。第二个“消费者”线程从队列中获取项目并对其进行处理。在这里,您需要同步队列,但不需要同步添加到队列中的对象。
同步所有内容并希望获得最好的结果并不是一个好主意。您需要非常仔细地考虑您的设计以及哪些线程可以作用于您的对象。
【讨论】:
需要考虑的两件事是:
【讨论】:
是的,你可以做到,这将是安全的
... 直到第二个程序员出现并且不理解您所做的相同假设。第二个(或第三个、第四个、第五个……)程序员可能会以非安全的方式(在创建者线程中)开始使用对象。造成的问题可能非常微妙且难以追踪。仅出于这个原因,并且因为在多个线程中使用这个对象非常诱人,我会让对象线程安全。
澄清一下,(感谢那些离开 cmets 的人):
“线程安全”是指以编程方式设计一种方案来避免线程问题。我不一定是指围绕您的对象设计一个锁定方案。您可以在您的语言中找到一种方法,使在创建者线程中使用该对象成为非法(或非常困难)。例如,在创建者线程中将范围限制为创建对象的代码块。创建后,将对象传递给用户线程,确保创建者线程不再引用它。
例如,在 C++ 中
void CreateObject()
{
Object* sharedObj = new Object();
PassObjectToUsingThread( sharedObj); // this function would be system dependent
}
然后在你的创建线程中,创建后你不再可以访问对象,责任被传递给使用线程。
【讨论】: