Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issue with GCD and too many threads

I have an image loader class which provided with NSURL loads and image from the web and executes completion block. Code is actually quite simple

- (void)downloadImageWithURL:(NSString *)URLString completion:(BELoadImageCompletionBlock)completion
{
    dispatch_async(_queue, ^{
//    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        UIImage *image = nil;
        NSURL *URL = [NSURL URLWithString:URLString];
        if (URL) {
            image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]];
        }  
        dispatch_async(dispatch_get_main_queue(), ^{
            completion(image, URLString);
        });
    });

}

When I replace

dispatch_async(_queue, ^{

with commented out

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{

Images are loading much faster, wich is quite logical (before that images would be loaded one at a time, now a bunch of them are loading simultaneously). My issue is that I have perhaps 50 images and I call downloadImageWithURL:completion: method for all of them and when I use global queue instead of _queue my app eventually crashes and I see there are 85+ threads. Can the problem be that my calling dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) 50 times in a row makes GCD create too many threads? I thought that gcd handles all the treading and makes sure the number of threads is not huge, but if it's not the case is there any way I can influence number of threads?

like image 977
dariaa Avatar asked Feb 04 '13 11:02

dariaa


3 Answers

The kernel creates additional threads when workunits on existing GCD worker threads for a global concurrent queue are blocked in the kernel for a significant amount of time (as long as there is further work pending on the global queue).

This is necessary so that the application can continue to make progress overall (e.g. the execution of one of the pending blocks may be what allows the blocked threads to become unblocked).

If the reason for worker threads to be blocked in the kernel is IO (e.g. the +[NSData dataWithContentsOfURL:] in this example), the best solution is replace those calls with an API that will perform that IO asynchronously without blocking, e.g. NSURLConnection for networking or dispatch I/O for filesystem IO.

Alternatively you can limit the number of concurrent blocking operations manually, e.g. by using a counting dispatch semaphore.

The WWDC 2012 GCD session went over this topic in some detail.

like image 80
das Avatar answered Oct 19 '22 20:10

das


Well from http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html

Concurrent queues (also known as a type of global dispatch queue) execute one or more tasks concurrently, but tasks are still started in the order in which they were added to the queue. The currently executing tasks run on distinct threads that are managed by the dispatch queue. The exact number of tasks executing at any given point is variable and depends on system conditions.

and

Serial queues (also known as private dispatch queues) execute one task at a time in the order in which they are added to the queue. The currently executing task runs on a distinct thread (which can vary from task to task) that is managed by the dispatch queue.

By dispatching all your blocks to the high priority concurrent dispatch queue with

[NSData dataWithContentsOfURL:URL]

which is a synchronous blocking network operation, it looks like the default GCD behaviour will be to spawn a load of threads to execute your blocks ASAP.

You should be dispatching to DISPATCH_QUEUE_PRIORITY_BACKGROUND. These tasks are in no way "High Priority". Any image processing should be done when there is spare time and nothing is happening on the main thread.

If you want more control over how many of these things are happening at once i reccommend that you look into using NSOperation. You can take your blocks and embed them in an operation using NSBlockOperation and then you can submit these operations to your own NSOperationQueue. An NSOperationQueue has a - (NSInteger)maxConcurrentOperationCount and as an added benefit operations can also be cancelled after scheduling if needed.

like image 24
jackslash Avatar answered Oct 19 '22 20:10

jackslash


You can use NSOperationqueue, which is supported by NSURLConnection

And it has the following instance method:

- (void)setMaxConcurrentOperationCount:(NSInteger)count
like image 1
Mert Avatar answered Oct 19 '22 20:10

Mert