Using Arc, Is It Fatal Not to Have an Autorelease Pool for Every Thread

Using ARC, is it fatal not to have an autorelease pool for every thread?

Consider it at minimum a programmer error if you do not create an autorelease pool for your new thread. Whether that's fatal to your program is defined by your program's implementation. The classic problem is leaked objects and consequently objects' dealloc which is never executed (could be fatal).

The modern way to create an autorelease pool under ARC is:

void MONThreadsEntry() { // << entry is e.g. a function or method
@autoreleasepool {
...do your work here...
}
}

In more detail, autorelease pools behave as thread-local stacks -- you can push and pop, but there should always be one in place before anything on that thread is autoreleased. Autorelease messages are not transferred from one thread to another.

You may not see issues (e.g. in the console or leaks) if your idea of "creating a thread" is using a higher level asynchronous mechanism, such as using an NSOperationQueue, or if the underlying implementation creates a secondary thread and its own autorelease pool.

Anyways, rather than guessing when to create autorelease pools, just learn where you need to create them and when you should create them. It's all well-defined -- there is no need for guesswork, and there is no need to fear creating them.

Similarly, you will never need to create an autorelease pool for your thread if you are using lower level abstractions, and never autorelease objects on that thread. For example, pthreads and pure C implementations won't need to bother with autorelease pools (unless some API you use assumes they are in place).

Even the main thread in a Cocoa app needs an autorelease pool -- it's just typically not something you write because it exists in the project templates.

Update -- Dispatch Queues

In response to updated question: Yes, you should still create autorelease pools for your programs which run under dispatch queues -- note that with a dispatch queue, you're not creating threads so this is quite a different question from the original question. The reason: Although dispatch queues do manage autorelease pools, no guarantee is made regarding the time/point they are emptied. That is to say, your objects would be released (at some point), but you should also create autorelease pools in this context because the implementation could (in theory) drain the pool every 10,000 blocks it runs, or approximately every day. So in this context, it's really only fatal in scenarios such as when you end up consuming too much memory, or when your programs expects that its objects will be destroyed in some determined fashion -- for example, you could be loading or processing images in the background and wind up consuming a ton of memory if the life of those images is extended unexpectedly because of the autorelease pools. The other example is shared resources or global objects, where you could respond to notifications or introduce race conditions because your "block local" objects may live a lot longer than you expect. Also remember that the implementation/frequency is free to change as its implementors see fit.

using autorelease in ARC enabled projects]

When you migrate from manual memory management to ARC you will replace the:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[pool release];

With

@autoreleasepool {

}

Read the NSAutoreleasePool docs and Advanced Memory Management Programming Guide for a better understanding.

Why use Autorelease pool?

Your assumption is correct. When you can ensure a specific thread is never making use of autoreleased objects, that thread wouldn't need an autorelease pool.

Avoiding the autoreleasepool is a bad advice, the coin has two sides. Using autorelease'd objects carries a certain amount of overhead (although insignificant in most scenarios) that should be avoided when possible. Especially in cases where there are multiple exits to a method, or an exception can be encountered, autoreleasing helps avoiding memory leaks and makes code cleaner.

Be aware though, that this means nothing on that thread can use autorelease, including any frameworks you may call. There are situations where this is the case, such as in a classic producer/consumer scenario. You have a producer that creates objects, dispatches them to the main threads runloop, and can register them in the main threads autoreleasepool consequently.

In general, I would not recommend creating a thread where significant work is carried out (besides a simple, long running computation) without an autoreleasepool.

if i am using GCD should i need to create @autorelease pool

If your block creates more than a few Objective-C objects, you might want to enclose parts of your block’s code in an @autorelease block to handle the memory management for those objects.

Although GCD dispatch queues have their own autorelease pools, they make no guarantees as to when those pools are drained. If your application is memory constrained, creating your own autorelease pool allows you to free up the memory for autoreleased objects at more regular intervals.

So, if you are only allocating a few objects, don't worry about it.However, if you are allocating any significant number of objects (and since you are targeting a memory constrained environment), then you should be creating and draining pools.

Does every thread need its own autorelease pool?

No, every NSThread has its own NSRunLoop, but not its own NSAutoreleasePool. Thus you have to create one and if you are performing a large operation or a operation that needs a lot of time, you really should drain the pool from time to time to keep your memory footprint low.

Object storage isn't bound to a thread at all, you can access every object from every thread you want, but it is possible that the accessor to the object isn't threadsafe and thus kills your app. However, this depends on your app and your code and not on threads.

Create autorelease pool on posix thread

Under ARC, using @autoreleasepool is quite fast. If you're concerned about performance in there, I would worry much more about avoiding unnecessary logging calls in the first place than about the autorelease pool.

You can't generally create a pool that is associated with "the thread" if the thread is long-lived. You need to to drain the pool periodically, and that won't happen if you just bracket the entire thread main function in an @autoreleasepool{} or anything similar to that.

Objective C - ARC, memory management and performSelectorInBackground?

While there might be hints to an existing autorelease pool in NSThread's implementation there is no such guarantee the documentation. In contrary, the documentation explicitly states otherwise:

performSelectorInBackground:withObject:

The method represented by aSelector must set up the thread environment just as you would for any other new thread in your program.

Threading Programming Guide

If your application uses the managed memory model [MRC and ARC, as opposed to Garbage Collection], creating an autorelease pool should be the first thing you do in your thread entry routine.

Conclusion: While in some specific scenarios there might be an autorelease pool in place it's not advisable to rely on this fact. It's undocumented behavior and can change with every release of the OS or other circumstances. It should generally be avoided in shipping code.

When autorelease pool release object in background thread?

https://developer.apple.com/documentation/foundation/nsautoreleasepool?language=objc

autorelease pool is just a unlimited stack for keeping autorelease object.
when you create a autorelease pool, pool stack push a watcher.
when you call autorelease on object, the object is pushed into the pool stack.
when you release autorelease pool, it release all pushed object after watcher, and then remove the watcher.

@autorelease in objc or autorelease in swift, is just a wrapper for create a autorelease pool, call block and then release pool.

runloop will automatically wrapper task into a autorelease pool.

but when you use autorelease pool with a custom thread, which haven't a runloop, in my observe, object will release when thread exit..

How to observe autorelease timeing

you can create a custom watch class with deinit defined, and manual retain and autorelease it, to observe the deinit timeing. code as below

class A {
deinit {
print("a dealloced")
}
}

var p: pthread_t?
_ = pthread_create(&p, nil, { (p) -> UnsafeMutableRawPointer? in
do {
let a = A()
_ = Unmanaged.passRetained(a).autorelease()
}
print("will exit pthread")
return nil
}, nil)
pthread_join(p!, nil)
print("finish")

this script will print

will exit pthread
a dealloced
finish

also you can breakpoint at deinit to see the backtrace of autorelease

How is a thread related to its NSAutorelease pool?

the pool is checked for objects with retaincount as +1, and are thus deallocated.

I'm not sure I understand that statement completely, but it sounds incorrect to me. There is nothing conditional about autorelease. If you autorelease an object, it will be released when the pool is drained, regardless of its retain count at that point (even if the object had already been deallocated!) It's better to think of "autorelease" as "deferred release".

As for the other question, each thread maintains its own stack of autorelease pools. Each pool is associated with one (and only one) thread.

Which thread is a given pool associated with? The answer is whichever thread created the pool. If you create a new pool where one exists already, then the new pool is "nested" within the existing pool. Objects autoreleased within the scope of that new pool will be released when that pool is drained (when the scope of that pool ends).

I hope that helps?

EDIT

To address your edit:

Your explanation is not correct. The autorelease pool can and does release the object as soon as it is drained. It doesn't wait for objB to release it first. It doesn't even know what other objects may have retained autorel_obj from your example. I think you're confusing release with deallocation.

So the scenario is this:

  • createAndReturn allocates and autoreleases autorel_obj (retain count is +1)
  • objB retains autorel_obj (retain count +2)
  • pool is drained, autorel_obj is released by the pool (retain count +1)
  • at some point in the future, objB releases autorel_obj (retain count 0)
  • autorel_obj is deallocated

So, again, the pool doesn't know and doesn't care what other objects may have retained the object it is releasing. It does the release, unconditionally, when drained. That may not cause the object to be deallocated immediately, but that's not the pool's concern.



Related Topics



Leave a reply



Submit