Reputation: 105
I have many codes like this:
dispatch_async(dispatch_get_global_queue(0, 0), ^{});
dispatch_async
will create a new thread when I call it one time.
When app run a while, I got many threads: number 50, 60, 70. It's not good.
How to reuse those threads. Like tableview.dequeueReusableCellWithIdentifier
This is my code. It's need do some image stitching things after download, then save.
- (void)sdImageWith:(NSString *)urlString saveIn:(NSString *)savePath completion:(completionSuccess)successCompletion failure:(completionFalse)failureCompletion {
[[SDWebImageDownloader sharedDownloader] downloadImageWithURL:[NSURL URLWithString:urlString] options:SDWebImageDownloaderUseNSURLCache progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
if (data.length <= 100 || error != nil) { failureCompletion(error); return;}
dispatch_async(imageStitch, ^{
NSLog(@"thread:%@", [NSThread currentThread]);
[[DLStitchingWarper shareSingleton] StitchingImage:data savePath:savePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:savePath]) {
successCompletion(savePath);
}else {
NSError *error = [[NSError alloc] initWithDomain:@"x" xxxcode:404 userInfo:nil];
failureCompletion(error);
}
});
}];
}
Upvotes: 2
Views: 1014
Reputation: 438467
The dispatch_get_global_queue
doesn't necessarily create new threads. It will pull threads from a limited pool of "worker" threads that GCD manages for you. When it's done running your dispatched task, it will return this thread back to the pool of worker threads.
When you dispatch something to a GCD queue, it will grab an available worker thread from this pool. You have no assurances as to which one it uses from one invocation to the next. But you simply don't need to worry about whether it's a different thread, as GCD is managing this pool of threads to ensure that threads are not created and destroyed unnecessarily. It's one of the main reasons we use GCD instead of doing our own NSThread
programming. It's a lot more efficient.
The only thing you need to worry about is the degree of concurrency that you employ in your app so that you don't exhaust this pool of worker threads (having unintended impact on other background tasks that might be drawing on the same pool of worker threads).
The most draconian way of limiting the degree of concurrency is to employ a shared serial queue that you create yourself. That means that only one thing will run on that serial queue at a time. (Note, even in this situation you don't have assurances that it will use the same thread every time; only that you'll only be using one background worker thread at a time.)
A slightly more refined way to constrain the degree of concurrency in your app is to use NSOperationQueue
(a layer above GCD) and set its maxConcurrentOperationCount
. With this, you can constrain the degree of concurrency to something greater than 1, but still small enough to not exhaust the worker threads. E.g. for network queues, it's not unusual to specify a maxConcurrentOperationCount
of 4 or 5.
In your revised question, you show us a code snippet. So, a couple of thoughts:
Don't worry about what [NSThread currentThread]
. GCD will manage the threads for you.
Is this stitching process slow and potentially using a fair degree of memory?
If so, I would not suggest either a serial queue (only allowing one at a time might be too constraining), nor a global queue (because you could have enough of these running concurrently that you'd use up all the available worker threads), nor a GCD concurrent queue (again, the degree of concurrency is unbound), but instead use an NSOperationQueue
with some reasonable limited degree of concurrency:
@property (nonatomic, strong) NSOperationQueue *stitchQueue;
And
self.stitchQueue = [[NSOperationQueue alloc] init];
self.stitchQueue.name = @"com.domain.app.stitch";
self.stitchQueue.maxConcurrentOperationCount = 4;
And
- (void)sdImageWith:(NSString *)urlString saveIn:(NSString *)savePath completion:(completionSuccess)successCompletion failure:(completionFalse)failureCompletion {
[[SDWebImageDownloader sharedDownloader] downloadImageWithURL:[NSURL URLWithString:urlString] options:SDWebImageDownloaderUseNSURLCache progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
if (data.length <= 100 || error != nil) { failureCompletion(error); return;}
[self.stitchQueue addOperationWithBlock:^{
// NSLog(@"thread:%@", [NSThread currentThread]); // stop worrying about `NSThread`
[[DLStitchingWarper shareSingleton] StitchingImage:data savePath:savePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:savePath]) {
successCompletion(savePath);
}else {
NSError *error = [[NSError alloc] initWithDomain:@"x" xxxcode:404 userInfo:nil];
failureCompletion(error);
}
}];
}];
}
If you prefer to use a custom GCD serial queue (with only one stitching operation possible at a time) or a custom GCD concurrent queue (with no limit as to how many stitching tasks running at any given time), feel free. You know how time consuming and/or resource intensive these operations are, so only you can make that call. But operation queues offer the benefits of concurrency, but simple control over the degree of concurrency.
Upvotes: 6
Reputation: 50139
the default implementation to concurrent queue does reuse threads but doesnt wait for free threads: if none are free, itll create.
it will thus overcommit and you need to assure you dont spawn too many long running tasks yourself. For a 'real' thread pool
You need a) your own queue(s) b) a 'limiter' so that you
for an example see : IOS thread pool
Upvotes: 2
Reputation: 318954
Instead of using dispatch_get_global_queue
, use dispatch_queue_create
and save a reference to the queue. Reuse it like any other instance variable.
Upvotes: 4