Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a single shared background thread for iOS data processing?

I have an app where I'm downloading a number of resources from the network, and doing some processing on each one. I don't want this work happening on the main thread, but it's pretty lightweight and low-priority, so all of it can really happen on the same shared work thread. That seems like it'd be a good thing to do, because of the work required to set up & tear down all of these work threads (none of which will live very long, etc.).

Surprisingly, though, there doesn't seem to be a simple way to get all of this work happening on a single, shared thread, rather than spawning a new thread for each task. This is complicated by the large number of paths to achieving concurrency that seem to have cropped up over the years. (Explicit NSThreads, NSOperationQueue, GCD, etc.)

Am I over-estimating the overhead involved in spawning all of these threads? Should I just not sweat it, and use the easier thread-per-task approaches? Use GCD, and assume that it's smarter than I about thread (re)use?

like image 247
Sixten Otto Avatar asked Nov 14 '11 17:11

Sixten Otto


2 Answers

Use GCD — it's the current official recommendation and it's less effort than any of the other solutions. If you explicitly need the things you pass in to occur serially (ie, as if on a single thread) then you can achieve that but it's probably smarter just to change, e.g.

[self doCostlyTask];

To:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^()
{
    [self doCostlyTask];

    dispatch_async(dispatch_get_main_queue(), ^()
    {
        // most UIKit tasks are permissible only from the main queue or thread,
        // so if you want to update an UI as a result of the completed action,
        // this is a safe way to proceed
        [self costlyTaskIsFinished];
    });
});

That essentially tells the OS "do this code with low priority wherever it would be most efficient to do it". The various things you post to any of the global queues may or may not execute on the same thread as each other and as the thread that dispatched them and may or may not occur concurrently. The OS applies the rules it considers optimal.

Exposition:

GCD is Apple's implementation of thread pooling, and they introduced closures (as 'blocks') at the same time to make it usable. So the ^(C-style args){code} syntax is a block/closure. That is, it's code plus the state of any variables (subject to caveats) that the code references. You can store and call blocks yourself with no GCD knowledge or use.

dispatch_async is a GCD function issues a block to the nominated queue. It executes the block on some thread at some time, and applies unspecified internal rules to do so in an optimal fashion. It'll judge that based on factors such as how many cores you have, how busy each is, what it's currently thinking on power saving (which may depend on power source), how the power costs for that specific CPU work out, etc.

So as far as the programmer is developed, blocks make code into something you can pass around as an argument. GCD lets you request that blocks are executed according to the best scheduling the OS can manage. Blocks are very lightweight to create and copy — a lot more so than e.g. NSOperations.

GCD goes beyond the basic asynchronous dispatch in the above example (eg, you can do a parallel for loop and wait for it to finish in a single call) but unless you have specific needs it's probably not all that relevant.

like image 128
Tommy Avatar answered Oct 12 '22 10:10

Tommy


Surprisingly, though, there doesn't seem to be a simple way to get all of this work happening on a single, shared thread, rather than spawning a new thread for each task.

This is exactly what GCD is for. GCD maintains a pool of threads that can be used for executing arbitrary blocks of code, and it takes care of managing that pool for best results on whatever hardware is at hand. This avoids the cost of constantly creating and destroying threads and also saves you from having to figure out how many processors are available, etc.

Tommy provides the right answer if you really care that only a single thread should be used, but it sounds like you're really just trying to avoid creating one thread per task.

This is complicated by the large number of paths to achieving concurrency that seem to have cropped up over the years. (Explicit NSThreads, NSOperationQueue, GCD, etc.)

NSOperationQueue uses GCD, so you can use that if it makes life easier than using GCD directly.

Use GCD, and assume that it's smarter than I about thread (re)use?

Exactly.

like image 44
Caleb Avatar answered Oct 12 '22 10:10

Caleb