GCD dispatch_async memory leak?
Asked Answered
C

2

10

The following code will occupy ~410MB of memory and will not release it again. (The version using dispatch_sync instead of dispatch_async will require ~8MB memory)
I would expect a spike of high memory usage but it should go down again... Where is the leak?

int main(int argc, const char * argv[]) {
  @autoreleasepool {
    for (int i = 0; i < 100000; i++) {
      dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
        NSLog(@"test");
      });
    }
    NSLog(@"Waiting.");
    [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60]];
  }
  return 0;
}  

I tried:

  • Adding @autoreleasepool around and inside the loop
  • Adding NSRunLoop run to the loop

I tried several combinations and never saw a decrease of memory (even after waiting minutes). I'm aware of the GCD reference guide which contains the following statement:

Although GCD dispatch queues have their own autorelease pools, they make no guarantees as to when those pools are drained.

Is there a memory leak in this code? If not, is there a way to enforce the queue to release/drain the finished blocks?

Chiller answered 16/2, 2015 at 8:55 Comment(0)
O
1

Objective-C block it is a C structure, I think you create 100000 the block objects to execute them in background threads and them wait while system can run them. Your device can execute limited count of threads, it means that many blocks will wait before OS start them.

If you change "async" to "sync", a next block object will be created after a previous block will be finished and destroyed.

UPD

About GCD pool.

GCD executes tasks on GCD thread pool, threads are created by the system, and managed by system. System caches threads to save CPU time, every dispatch task executes on free thread.

From documentation:

——

Blocks submitted to dispatch queues are executed on a pool of threads fully managed by the system. No guarantee is made as to the thread on which a task executes.

——

If you run the tasks as synchronized tasks, then exist the free thread (from GCD thread pool) to execute next task, after current task’s finished (because main thread is waiting while task execute, and does not add new tasks to the queue), and system does not allocate new NSThread (On my mac I’ve seen 2 threads). If you run the tasks as async, then the system can allocate many NSThreads (to achieve of maximum performance, on my mac it is near 67 threads), because the global queue contain many tasks.

Here you can read about max count of GCD thread pool.

I’ve seen in Alocations profiler that there are many NSThreads allocated and not destructed. I think it is system pool, that will be freed if necessary.

Overripe answered 16/2, 2015 at 9:16 Comment(1)
That's true but not a answer to the question. The memory usage should go down again (at least) after all blocks have finished. But that's not the case here. A spike of high memory usage would be perfectly fine.Chiller
B
1

Always put @autoreleasepool inside every GCD call and you will have no problems. I had the same problem and this is the only workaround.

int main(int argc, const char * argv[]) {
  @autoreleasepool {
    for (int i = 0; i < 100000; i++) {
      dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
        // everything INSIDE in an @autoreleasepool
        @autoreleasepool {
          NSLog(@"test");
        }
      });
    }
    NSLog(@"Waiting.");
    [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60]];
  }
  return 0;
}  
Beria answered 1/10, 2016 at 12:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.