gcd / enumerateObjects

Just some baseline questions.

In Objective-C, I was using enumerateObject and it made quite a performance difference.

After some porting, in Swift 2.3, I had code like ...


lotsOfStuff?.enumerateObjects(options: NSEnumerationOptions.concurrent) {
  (item:Any!, idx:Int, stop:UnsafeMutablePointer<ObjCBool>) -> Void in

  //work on stuff
}


Looks like Xcode 8 / Swift 3 things changed more. I'm attempting now to move away for NSArray, NSMutableArray and Any. I see Swift's Array does not support enumerateObjects. I figure that implies we need to use gcd as an alternative?


//set up property
processingQueue = DispatchQueue(label:"com.someplace.somewhere", qos:.userInteractive, attributes:.concurrent)

//Process somewhere else in code...
let group = DispatchGroup()

for p in lotsOfStuff! {
  processingQueue.async(group:group) {

  //work on stuff. p/self retained should be ok
  }
}

group.wait()


I assume arc is going to handle dispatch_release now? So, when group falls out of scope or if the class is deinited then the queue will be destroyed?


Some more lingering questions about gcd. How many threads will it spawn if its concurrent? And will it ever choose main thread or does it always create new threads? Past documents never really said. I recall it has a thread pool internally. I know there is a main queue that can specifically target main thread execution.


Also, I remember in the past I could set the concurrent queue up to only process X items at a time. But not sure the new api has that -- seems like it will manage that for me which is fine. Did not seem like many attribues. There is just one to set concurrent and another to set initially inactive.


Watched latest wwdc video on gcd and it was informative.

This looks wrong:


for p in lotsOfStuff! {
  processingQueue.async(group:group) {


You want to add blocks the group inside the "for" loop, and submit the whole group after the "for" loop. (However, I haven't had to use the new Swift syntax for groups yet, so maybe I have it wrong).


>> I assume arc is going to handle dispatch_release now?


It's done so for several years.


>> How many threads will it spawn if its concurrent?


You have no control over that. (More below)


>> will it ever choose main thread ?


No, only blocks on the main queue run on the main thread. Blocks on other queues never run on the main thread.


>> I remember in the past I could set the concurrent queue up to only process X items at a time


No, not ever in GCD. You're thinking of NSOperationQueue.


Be careful about submitting lots of threads to GCD. If you do it right, the system will start or re-use a suitable number of background threads (up to the number of logical CPUs, typically, but other factors influence the number used, and you have no control over it.)


However, if any GCD thread blocks (e.g. waits for I/O to complete), then its queue will put the current item aside and start working on the next queued item, creating a new thread to do so. If your code introduces such blocks, you can easily get a runaway situation where GCD is constantly starting new threads. To use GCD correctly, you should code your closures to execute without blocking. Instead of blocking, they can submit other closures that work asynchronously, then the original task should exit, and let the asynchonronous closures' completion handlers to finish what remains to be done.


If this fully asynchronous programming pattern is too hard to adopt for the kinds of tasks you're doing, then use NSOperationQueue instead. Its tasks are allowed to block, without exploding in your face like GCD.


[A concise way of stating the difference is that in GCD new tasks are scheduled on idleness; in NSOperationQueue new tasks are scheduled on completion.]

Thanks for the tips.


Ya, the original Objective-C code I was working with had manual reference counting -- game code that was avoiding the use of arc. Thus, my concern about dispatch_release. Since I'm in swift world now, there is no choice about arc. 😁


The block code is not doing i/o . Just processing data and blocking should not be an issue.


Did some more research. I think this is another possibility. But seems like a more long handed approach than the prior code.


//Process somewhere else in code...
let group = DispatchGroup()

for p in lotsOfStuff! {

     let item = DispatchWorkItem(block: {
     
          //work on stuff. p/self retained should be ok
     }

     processingQueue.async(group:group,execute:item)
}

group.wait()


I see that DispatchGroup has a notifiy. But I suspect that is used to chain up another queue when the preceeding blocks are done. ( like refresh the ui after all the blocks on processingQueue are done ) So, its not apparent where I would add items to the group and then in one go add them to the dispatch queue. Looks like I have to add one at a time unless I'm totally missing the point.

Interesting that each work item can have its own qos.

Accepted Answer

Sorry, I misread your code. "processingQueue.async" is correct within the "for" loop in both cases, and your second try (using DispatchWorkItem) is equivalent to your first (using a closure parameter). AFAICT, the "processingQueue.async" call will add the item to the group and the queue, so it can start processing immediately, and there's nothing you need to do to start it.


>> Interesting that each work item can have its own qos.


And its own queue, if you needed that.

gcd / enumerateObjects
 
 
Q