使用ARC,每个线程都没有自己的自动释放池会有致命问题吗?

19

我看到了这个:

如果你在应用程序中创建了一个次要线程,你需要为其提供自己的自动释放池。自动释放池和其中包含的对象在

iOS 5开发者食谱书

中有更详细的介绍。

我正在使用ARC编译。我已经创建了许多后台线程,似乎一切都挺好。我的所有后台线程都没有长时间运行。那些对象会被主线程的自动释放池之类的东西释放吗?或者怎么样?

这是我用来调用后台线程的代码:

+(void)doBackground:(void (^)())block
{
    //DISPATCH_QUEUE_PRIORITY_HIGH
    //dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0), ^{
    dispatch_async(dispatch_get_global_queue(-2,0), ^{
        block();
    });
}

我应该把它改成什么?

+(void)doBackground:(void (^)())block
{
    //DISPATCH_QUEUE_PRIORITY_HIGH
    //dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0), ^{
    dispatch_async(dispatch_get_global_queue(-2,0), ^{
        @autoreleasepool{
        block();
        }
    });
}

2
你在使用GCD、NSOperation还是NSThread? - J2theC
2
GCD,那是业界标准,对吧? - user4951
1
iOS文档中的这句话可以帮助理解:“Cocoa应用程序中的每个线程都维护着自己的autorelease池块栈。如果您正在编写仅基于Foundation的程序或者您分离了一个线程,那么您需要创建自己的autorelease池块。” - mask
是的,这句话来自于<高级内存管理编程指南> https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html#//apple_ref/doc/uid/20000047-CJBFBEDI - black_pearl
2个回答

35
如果您在新线程中没有创建自动释放池,那么请将其视为程序员错误,无论这是否对您的程序造成致命影响都由程序实现定义。经典问题是泄漏的对象以及因此未执行的对象的dealloc(可能是致命的)。
在ARC下创建自动释放池的现代方法是:
void MONThreadsEntry() { // << entry is e.g. a function or method
  @autoreleasepool {
    ...do your work here...
  }
}

更详细地说,自动释放池的行为类似于本地线程堆栈——您可以推入和弹出,但在该线程上有任何自动释放对象之前应始终存在一个自动释放池。自动释放消息不会从一个线程传递到另一个线程。
如果您的“创建线程”想法是使用更高级别的异步机制(例如使用NSOperationQueue),或者底层实现创建了一个辅助线程和它自己的自动释放池,那么您可能看不到问题(例如控制台或泄漏)。
无论如何,与其猜测何时创建自动释放池,不如学习何时需要创建它们以及何时应该创建它们。这都是明确定义的——没有猜测的必要,也没有必要担心创建它们。
同样,如果您正在使用较低级别的抽象,并且从未在该线程上自动释放对象,那么您将永远不需要为该线程创建自动释放池。例如,pthread和纯C实现将不需要费心处理自动释放池(除非您使用的某些API假定它们已经就位)。
甚至Cocoa应用程序中的主线程也需要自动释放池——通常不需要编写代码,因为它已经存在于项目模板中。 更新——调度队列

针对更新的问题:是的,在运行在调度队列下的程序中仍应该创建自动释放池——请注意,调度队列并不会创建线程,因此这与原始问题有很大不同。原因是:虽然调度队列确实管理自动释放池,但不能保证它们被清空的时间/点。也就是说,您的对象会被释放(在某个时候),但在这种情况下,应该创建自动释放池,因为在理论上,实现可能每运行10,000块代码就会排空池子,或者大约每天排空一次池子。所以,在这种情况下,只有在您的程序消耗过多内存或您的程序期望以某种确定的方式销毁其对象时才会造成真正致命的影响——例如,在后台加载或处理图像时,如果由于自动释放池的存在而意外延长了这些图像的生命周期,您可能会消耗大量的内存。另一个例子是共享资源或全局对象,您可能会对通知做出响应或引入竞态条件,因为您的“块本地”对象的寿命可能比您预期的要长得多。还要记住,实现/频率可以根据实现者的需要自由更改。


6
我认为您误解了,我并没有说GCD不会创建线程,我是说没有创建线程。GCD调度队列更像线程池而不是线程;它不会为您提交的每个任务生成新的线程。是的,GCD将在背后创建和管理线程,但机制(大部分)是对您进行抽象的。当您显式创建自己的线程时(例如使用pthread或NSThread),设置线程时需要经过不同的步骤/过程。这是一个重要的区别,因为自动释放池是线程本地的。 - justin
1
我明白了。我的意思是,GCD会自己创建自动释放池,当线程变为空闲状态或完成时执行。只要我使用GCD,就不需要创建自己的自动释放池。 - user4951
5
“GCD会自己创建自动释放池”,正确。“只要使用GCD,我就不需要创建自己的自动释放池。”这不是我在“更新-调度队列”中写的。我鼓励你创建自动释放池,即使在绝大多数情况下你实际上不需要创建它们。问题在于你无法动态确定何时不需要它们。许多人对98%的覆盖率感到满意,因此基于这个原因他们就从来不创建自动释放池。但是,具有高标准的库开发人员可能总是会创建它们。 - justin
1
@JimThio 附加说明: a) 我曾经读到过他们在某个时候(内部细节)清空它们,但是实现随时可以更改。 b) 在现代操作系统中创建自动释放池非常便宜。 - justin
1
这篇文章显然更为全面,+1。 - user4951
这在 iOS 8+ 上仍然有效吗? - nekonari

4

似乎现在自动为新线程创建自动释放池。不知道这个什么时候改变了,以及为什么文档说明相反,但就是这样。


2
“现代”是什么意思?以前,日志记录的消息类似于“在没有池的情况下自动释放,只是泄漏”。我现在检查了一下,没有任何消息,对象被正确释放,并且当我调试时,在thread_exit内部某处进行了池排空。这与以前的行为和文档有明显不同。最近肯定发生了一些变化... - pronvit
4
引入了ARC后,自动释放池被重新编写。因此,这会影响iOS 5.0及以后的版本,以及使用ARC的任何OS X版本。在此之前,你有一个NSAutoreleasePool对象的线程锁栈。现在,它是一种不同的机制,可以将其视为带有标记值的单个数组,其中包含池边界。由于没有离散池对象的堆栈,因此每个线程都有一个隐含的“池”,但如果没有使用自己的池,我认为它仍然会尝试记录第一次。 - Lily Ballard
1
看来你是对的。你从哪里知道这个的?我想知道为什么线程的官方文档没有更新以反映这些变化。 - pronvit
1
是的,我指的是这些指令。当然最好为每个线程拥有自己的池(不仅仅是这样)。但他们至少可以在发布说明中包含这个信息:) 无论如何,感谢您提供的信息。 - pronvit
2
并不完全不同意。我同意你应该创建自己的池。虽然当前GCD队列和NSThreads都会自动提供池,而且你的对象在任何情况下都不会泄漏,但是在代码的某些部分创建许多Cocoa对象时,最好创建自己的池 - 不仅在后台线程中,而且在具有许多迭代的循环中也是如此。主UI线程池在每个运行循环迭代中都会被清空,而系统提供的后台线程池将在线程退出之前不会被清空。 - pronvit
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接