Reputation: 517
The reason for this question is because of the reactions to this question.
I realized the understanding of the problem was not fully there as well as the reason for the question in the first place. So I am trying to boil down the reason for the other question to this one at it's core.
First a little preface, and some history, I know NSOperation(Queue) existed before GCD, and and they were implemented using threads before dispatch queues.
The next thing is that you need to understand is that by default, meaning no "waiting" methods being use on operations or operation queues (just a standard "addOperation:"), an NSOperation's main method is executed on the underlying queue of the NSOperationQueue asynchronously (e.g. dispatch_async()).
To conclude my preface, I'm questioning the purpose of setting NSOperationQueue.mainQueue.maxConcurrentOperationCount to 1 in this day and age, now that the underlyingQueue is actually the main GCD serial queue (e.g. the return of dispatch_get_main_queue()).
If NSOperationQueue.mainQueue already executes it's operation's main methods serially, why worry about maxConcurrentOperationCount at all?
To see the issue of it being set to 1, please see the example in the referenced question.
Upvotes: 1
Views: 988
Reputation: 385890
It's set to 1 because there's no reason to set it to anything else, and it's probably slightly better to keep it set to 1 for at least three reasons I can think of.
Because NSOperationQueue.mainQueue
's underlyingQueue
is dispatch_get_main_queue()
, which is serial, NSOperationQueue.mainQueue
is effectively serial (it could never run more than a single block at a time, even if its maxConcurrentOperationCount
were greater than 1).
We can check this by creating our own NSOperationQueue
, putting a serial queue in its underlyingQueue
target chain, and setting its maxConcurrentOperationCount
to a large number.
Create a new project in Xcode using the macOS > Cocoa App template with language Objective-C. Replace the AppDelegate
implementation with this:
@implementation AppDelegate {
dispatch_queue_t concurrentQueue;
dispatch_queue_t serialQueue;
NSOperationQueue *operationQueue;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
concurrentQueue = dispatch_queue_create("q", DISPATCH_QUEUE_CONCURRENT);
serialQueue = dispatch_queue_create("q2", nil);
operationQueue = [[NSOperationQueue alloc] init];
// concurrent queue targeting serial queue
//dispatch_set_target_queue(concurrentQueue, serialQueue);
//operationQueue.underlyingQueue = concurrentQueue;
// serial queue targeting concurrent queue
dispatch_set_target_queue(serialQueue, concurrentQueue);
operationQueue.underlyingQueue = serialQueue;
operationQueue.maxConcurrentOperationCount = 100;
for (int i = 0; i < 100; ++i) {
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation %d starting", i);
sleep(3);
NSLog(@"operation %d ending", i);
}];
[operationQueue addOperation:operation];
}
}
@end
If you run this, you'll see that operation 1 doesn't start until operation 0 has ended, even though I set operationQueue.maxConcurrentOperationCount
to 100. This happens because there is a serial queue in the target chain of operationQueue.underlyingQueue
. Thus operationQueue
is effectively serial, even though its maxConcurrentOperationCount
is not 1.
You can play with the code to try changing the structure of the target chain. You'll find that if there is a serial queue anywhere in that chain, only one operation runs at a time.
But if you set operationQueue.underlyingQueue = concurrentQueue
, and do not set concurrentQueue
's target to serialQueue
, then you'll see that 64 operations run simultaneously. For operationQueue
to run operations concurrently, the entire target chain starting with its underlyingQueue
must be concurrent.
Since the main queue is always serial, NSOperationQueue.mainQueue
is effectively always serial.
In fact, if you set NSOperationQueue.mainQueue.maxConcurrentOperationCount
to anything but 1, it has no effect. If you print NSOperationQueue.mainQueue.maxConcurrentOperationCount
after trying to change it, you'll find that it's still 1. I think it would be even better if the attempt to change it raised an assertion. Silently ignoring attempts to change it is more likely to lead to confusion.
NSOperationQueue
submits up to maxConcurrentOperationCount
blocks to its underlyingQueue
simultaneously. Since the mainQueue.underlyingQueue
is serial, only one of those blocks can run at a time. Once those blocks are submitted, it may be too late to use the -[NSOperation cancel]
message to cancel the corresponding operations. I'm not sure; this is an implementation detail that I haven't fully explored. Anyway, if it is too late, that is unfortunate as it may lead to a waste of time and battery power.
As with mentioned with reason 2, NSOperationQueue
submits up to maxConcurrentOperationCount
blocks to its underlyingQueue
simultaneously. Since mainQueue.underlyingQueue
is serial, only one of those blocks can execute at a time. The other blocks, and any other resources the dispatch_queue_t
uses to track them, must sit around idly, waiting for their turns to run. This is a waste of resources. Not a big waste, but a waste nonetheless. If mainQueue.maxConcurrentOperationCount
is set to 1, it will only submit a single block to its underlyingQueue
at a time, thus preventing GCD from allocating resources uselessly.
Upvotes: 5