Could anyone help me to understand this code I created:
let cq = DispatchQueue(label: "downloadQueue", attributes: .concurrent)
cq.sync {
for i in 0..<10 {
sleep(2)
print(i)
}
}
print("all finished!")
And the output is serial order 1->10, with 2 sec waiting in between. In the end it will print out all finished
I understand that the the last part.
However my question is:
Shouldn't concurrent queue start multiple tasks at the same time?
So my original thought was: the 1-10 printing should be done concurrently, not necessarily in the serial order.
Could anyone explain the purpose of sync call on concurrent queue and give me an example why and when we need it?
If you want to demonstrate them running concurrently, you should dispatch the 10 tasks individually:
let cq = DispatchQueue(label: "downloadQueue", attributes: .concurrent)
for i in 0..<10 {
cq.async {
sleep(2)
print(i)
}
}
print("all finished queuing them!")
Note:
There are 10 dispatches to the concurrent queue, not one.
Each dispatched task runs concurrently with respect to other tasks dispatched to that queue (which is why we need multiple dispatches to illustrate the concurrency).
Also note that we dispatch asynchronously because we do not want to calling queue to wait for each dispatched task before dispatching the next.
You ask:
So my original thought was: the 1-10 printing should be done concurrently, not necessarily in the serial order.
Because they are inside a single dispatch, they will run as a single task, running in order. You need to put them in separate dispatches to see them run concurrently.
You go on to ask:
Could anyone explain the purpose of sync call on concurrent queue and give me an example why and when we need it?
The sync has nothing to do with whether the destination queue is serial or concurrent. The sync only dictates the behavior of the calling thread, namely, should the caller wait for the dispatched task to finish or not. In this case, you really do not want to wait, so you should use async.
As a general rule, you should avoid calling sync unless (a) you absolutely have to; and (b) you are willing to have the calling thread blocked until the sync task runs. So, with very few exceptions, one should use async. And, perhaps needless to say, we never block the main thread for more than a few milliseconds.
While using sync on a concurrent dispatch queue is generally avoided, one example you might encounter is the “reader-writer” synchronization pattern. In this case, “reads” happen synchronously (because you need to wait the result), but “writes” happen asynchronously with a barrier (because you do not need to wait, but you do not want it to happen concurrently with respect to anything else on that queue). A detailed discussion of using GCD for synchronization (esp the reader-writer pattern), is probably beyond the scope of this question. But search the web or StackOverflow for “GCD reader-writer” and you will find discussions on the topic.)
Let us graphically illustrate my revamped rendition of your code, using OSLog to create intervals in Instruments’ “Points of Interest” tool:
import os.log
private let log = OSLog(subsystem: "Foo", category: .pointsOfInterest)
class Foo {
func demonstration() {
let queue = DispatchQueue(label: "downloadQueue", attributes: .concurrent)
for i in 0..<10 {
queue.async { [self] in
let id = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: "async", signpostID: id, "%d", i)
spin(for: 2)
os_signpost(.end, log: log, name: "async", signpostID: id)
}
}
print("all finished queuing them!")
}
func spin(for seconds: TimeInterval) {
let start = CACurrentMediaTime()
while CACurrentMediaTime() - start < seconds { }
}
}
When I profile this in Instruments (e.g. “Product” » “Profile”), choosing “Time Profiler” template (which includes “Points of Interest” tool), I see a graphical timeline of what is happening:
So let me draw your attention to two interesting aspects of the above:
The concurrent queue runs tasks concurrently, but because my iPhone’s CPU only has six cores, only six of them can actually run at the same time. The next four will have to wait until a core is available for that particular worker thread.
Note, this demonstration only works because I am not just calling sleep, but rather I am spinning for the desired time interval, to more accurately simulate some slow, blocking task. Spinning is a better proxy for slow synchronous task than sleep is.
This illustrates, as you noted, concurrent tasks may not appear in the precise order that they were submitted. This is because (a) they all were queued so quickly in succession; and (b) they run concurrently: There is a “race” as to which concurrently running thread gets to the logging statements (or “Points of Interest” intervals) first.
Bottom line, for those tasks running concurrently, because of races, they may not appear to run in order. That is how concurrent execution works.
No, you have to add multiple operations to the DispatchQueue, it will then run the multiple queues in parallel.
Related
My program has many parallel processes each on their own queue. I'd like to be able to visualize/measure the back pressure of the queues.
One approach is to count every block that enters and exits, but I'm sure GCD has this information already. Is there a better approach to measuring back pressure?
There is no API for querying the number of pending blocks in a GCD queue. That said, since you're asking about a queue that you "own", you can wrap it in a way that lets you keep track of that. For instance, you could make your own wrappers around dispatch_[a]sync that would increment a counter whenever you enqueued a block, and also wrapped the block to decrement the counter when the block completes. This really wouldn't be that hard to implement. (dispatch_queue_get_specific would likely be a good place to start...)
FWIW, a few years back, I would've suggested you use NSOperationQueue, but even NSOperationQueue has deprecated its operationCount property, so it's probably fair to say that Apple is unlikely to provide this functionality going forward, so implementing it yourself is probably your best option, if you really need this functionality.
This is probably not immediately helpful to you, but if you need to measure "back pressure" on GCD queues, you're probably not using them in the right way. If you're enqueuing work items that block during execution, that's bad, and will eventually lead to thread starvation. If you're enqueuing tons of work items that you might want to cancel, say if the user changed screens or something, then you should work on a pattern for cancellable work items (or use NSOperation). I'm struggling to think of a use case that would demand that you be able to measure "back pressure" that couldn't be solved with your own code.
If you want to visualize what is going on, you can use OSLog, post .begin and .end events and watch it in Instruments’ “Points of Interest”. (See WWDC 2019 Getting Started with Instruments.)
So, import os.log:
import os.log
Then create a log:
private let pointsOfInterestLog = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: .pointsOfInterest)
Enqueue 100 tasks:
for i in 0..<100 {
enqueueTask(i) {
print("done \(i)")
}
}
Where the routine posts a .begin event to the points of interest log before dispatching the task, and posts a .end event when the task actually starts, e.g.
func enqueueTask(_ index: Int, completion: #escaping () -> Void) {
let id = OSSignpostID(log: pointsOfInterestLog)
os_signpost(.begin, log: pointsOfInterestLog, name: "backlog", signpostID: id, "queued %d", index)
queue.async {
os_signpost(.end, log: pointsOfInterestLog, name: "backlog", signpostID: id, "started %d", index)
...
completion()
}
}
Then profile the app (with command+i or “Product” » “Profile”) and choose, for example, “Time Profiler” (which includes the “Points of Interest” tool). Start a recording an you will see a visual representation of your backlog:
(I expanded the “Points of Interest” to be big enough to show all 100 backlogged tasks.)
This is one way to visualize your backlog. I must confess that I generally use “Points of Interest” not to show the backlog, but rather to show when these tasks are actually running (i.e. .begin when the dispatched task actually starts running and .end when the dispatched task finishes). Or I use the “thread” view in Instruments to see how my worker threads are being used. Or I use the “CPU” view in Instruments to see how my CPUs are being used.
Regardless, as you can see, Instruments can be used to visualize whatever time ranges you want, in this case, the time between the adding of the task to the queue and when it starts running.
There are many examples on how to enforce code to run on main thread. I have an opposite need for unit testing purposes. I want to exclude main thread from taking on some work.
Why do I need this: I would like to test that a particular function runs correctly, specifically when it's simultaneously called from 2 or more background threads (while main thread is free and available). This is because function itself makes use of main thread, while it is a typical use case that it will be called from background threads, possibly concurrently.
I already have one test case where one of the calling threads is the main thread. But I also want to test the case when main thread is not busy, when 2 or more other threads call the function.
The problem is that there's seems no way to leave main thread free. For instance even if queue is defined with qoc: .background, main thread was still taken:
private let queue = DispatchQueue(label: "bgqueue", qos: .background, attributes: .concurrent)
let iterations = 10
DispatchQueue.concurrentPerform(iterations: iterations) { _ in
queue.async {
callMyFunction() // still may run on main thread, even with 1-2 iterations
}
}
The only approach I can think of is blocking all threads at the same spot (using CountDownLatch for example), and then proceed to function from all threads, but main:
let latch = CountDownLatch(count: iterations)
DispatchQueue.concurrentPerform(iterations: iterations) { _ in
latch.countdown()
latch.await()
if !Thread.isMainThread {
callMyFunction()
}
}
The problems here are 1 - have to make sure iterations < available threads; 2 - feels wrong to block main thread.
So is there any better solution? How to exclude main thread from picking up DispatchQueue work within unit tests?
As long as you're not dispatching to the main queue (or some other queue that uses the main queue as its “target”), async will never use the main thread. There are some interesting optimizations with sync which affect which thread is used (which isn’t relevant here), but not for async. The asynchronously dispatched task will run on a worker thread associated with that QoS, not the main thread.
So, go ahead and insert a thread assertion in your test to make sure it’s not on the main thread, and I think you’ll find that it is fine:
XCTAssertFalse(Thread.isMainThread, "Shouldn't be on main thread")
That having been said, you allude to the fact that “function itself makes use of main thread”. If that’s the case, you can easily deadlock if your test is blocking the main thread, waiting for the called functions to finish processing on the main thread as well.
You can often avoid these sorts of unit test deadlocks by using “expectations”.
func testWillNotDeadlock() throws {
let expectation = self.expectation(description: "testWillNotDeadlock")
someAsyncMethod {
expectation.fulfill()
}
waitForExpectations(timeout: 100)
}
In this case, although someAsyncMethod may have used the main thread, we didn't deadlock because we made sure the test used expectations rather than blocking the current thread.
So, bottom line, if you're testing some asynchronous methods that must use the main thread, make sure that the tests don't block, but rather that they use expectations.
There are, admittedly, other sources of potential problems. For example, you are calling async with 100 iterations. You need to be careful with that pattern, because you can easily exhaust all of the worker threads with this “thread explosion”. The attempt to introduce concurrentPerform (which is often used to solve thread explosion problems) won't work if the code in the closure is calling async.
Considering this trivial piece of code
DispatchQueue.global().async {
print("2")
}
print("1")
we can say that output will be as following:
1
2
Are there any circumstances under which order of execution will be different (disregarding kind of used queue)? May they be forced to appear manually if any?
You said:
we can say that output will be as following ...
No, at best you can only say that the output will often/frequently be in that order, but is not guaranteed to be so.
The code snippet is dispatching code asynchronously to a global, concurrent queue, which will run it on a separate worker thread, and, in the absence of any synchronization, you have a classic race condition between the current thread and that worker thread. You have no guarantee of the sequence of these print statements, though, in practice, you will frequently see 1 before 2.
this is one of the common questions at tech interview; and for some reason interviewers expect conclusion that order of execution is constant here. So (as it was not aligned with my understanding) I decided to clarify.
Your understanding is correct, that the sequence of these two print statements is definitely not guaranteed.
Are there any circumstances under which order of execution will be different
A couple of thoughts:
By adjusting queue priorities, for example, you can change the likelihood that 1 will appear before 2. But, again, not guaranteed.
There are a variety of mechanisms to guarantee the order.
You can use a serial queue ... I gather you didn’t want to consider using another/different queue, but it is generally the right solution, so any discussion on this topic would be incomplete without that scenario;
You can use dispatch group ... you can notify on the global queue when the current queue satisfies the group;
You can use dispatch semaphores ... semaphores are a classic answer to the question, but IMHO semaphores should used sparingly as it’s so easy to make mistakes ... plus blocking threads is never a good idea;
For the sake of completeness, we should mention that you really can use any synchronization mechanism, such as locks.
I do a quick test too. Normally I will do a UI update after code execution on global queue is complete, and I would usually put in the end of code block step 2.
But today I suddenly found even I put that main queue code in the beginning of global queue block step 1, it still is executed after all global queue code execution is completed.
DispatchQueue.global().async {
// step 1
DispatchQueue.main.async {
print("1. on the main thread")
}
// code global queue
print("1. off the main thread")
print("2. off the main thread")
// step 2
DispatchQueue.main.async {
print("2. on the main thread")
}
}
Here is the output:
1. off the main thread
2. off the main thread
1. on the main thread
2. on the main thread
I'm watching the concurrent programming talk from WWDC, found here and I'm a little confused about the meaning of async. In Javascript world, we use "async" to describe processes that happen "out of order" or practically, processes that are not guaranteed to return before the next line of code is executed. However, in this talk it seems as though what the presenter is demonstrating is in fact a series of tasks being executed one after the other (ie. synchronous behavior)
Then in this code example
let queue = DispatchQueue(label: "com.example.imagetransform")
queue.async {
let smallImage = image.resize(to: rect)
DispatchQueue.main.async {
imageView.image = smallImage
}
}
This DispatchQueue doesn't seem to behave like the FIFO data structure it's supposed to be on first glance. I see that they are passing in a closure into the queue.async method, but I'm not sure how to add more blocks, or tasks, for it to execute.
I'm guessing the async nature of this is similar to Javascript callbacks in the sense that all the variables being assigned in the closure are only scoped to that closure and we can only act on that data within the closure.
DispatchQueues are FIFO with regard to the start of tasks. If they are concurrent queues, or if you're submitting async tasks, then there's no FIFO guarantee on when they finish
Concurrent queues allow multiple tasks to run at the same time. Tasks are guaranteed to start in the order they were added. Tasks can finish in any order and you have no knowledge of the time it will take for the next task to start, nor the number of tasks that are running at any given time.
- https://www.raywenderlich.com/148513/grand-central-dispatch-tutorial-swift-3-part-1
I have read the tutorial about GCD and Dispatch Queue in Swift 3
But I'm really confused about the order of synchronous execution and asynchronous execution and main queue and background queue.
I know that if we use sync then we execute them one after the precious one, if we use async then we can use QoS to set their priority, but how about this case?
func queuesWithQoS() {
let queue1 = DispatchQueue(label: "com.appcoda.myqueue1")
let queue2 = DispatchQueue(label: "com.appcoda.myqueue2")
for i in 1000..<1005 {
print(i)
}
queue1.async {
for i in 0..<5{
print(i)
}
}
queue2.sync {
for i in 100..<105{
print( i)
}
}
}
The outcome shows that we ignore the asynchronous execution. I know queue2 should be completed before queue1 since it's synchronous execution but why we ignore the asynchronous execution and
what is the actual difference between async, sync and so-called main queue?
You say:
The outcome shows that we ignore the asynchronous execution. ...
No, it just means that you didn't give the asynchronously dispatched code enough time to get started.
I know queue2 should be completed before queue1 since it's synchronous execution ...
First, queue2 might not complete before queue1. It just happens to. Make queue2 do something much slower (e.g. loop through a few thousand iterations rather than just five) and you'll see that queue1 can actually run concurrently with respect to what's on queue2. It just takes a few milliseconds to get going and the stuff on your very simple queue2 is finishing before the stuff on queue1 gets a chance to start.
Second, this behavior is not technically because it's synchronous execution. It's just that async takes a few milliseconds to get it's stuff running on some worker thread, whereas the synchronous call, because of optimizations that I won't bore you with, gets started more quickly.
but why we ignore the asynchronous execution ...
We don't "ignore" it. It just takes a few milliseconds to get started.
and what is the actual difference between async, sync and so-called main queue?
"Async" merely means that the current thread may carry on and not wait for the dispatched code to run on some other thread. "Sync" means that the current thread should wait for the dispatched code to finish.
The "main thread" is a different topic and simply refers to the primary thread that is created for driving your UI. In practice, the main thread is where most of your code runs, basically running everything except that which you manually dispatch to some background queue (or code that is dispatched there for you, e.g. URLSession completion handlers).
sync and async are related to the same thread / queue. To see the difference please run this code:
func queuesWithQoS() {
let queue1 = DispatchQueue(label: "com.appcoda.myqueue1")
queue1.async {
for i in 0..<5{
print(i)
}
}
print("finished")
queue1.sync {
for i in 0..<5{
print(i)
}
}
print("finished")
}
The main queue is the thread the entire user interface (UI) runs on.
First of all I prefer to use the term "delayed" instead of "ignored" about the code execution, because all your code in your question is executed.
QoS is an enum, the first class means the highest priority, the last one the lowest priority, when you don't specify any priority you have a queue with default priority and default is in the middle:
userInteractive
userInitiated
default
utility
background
unspecified
Said that, you have two synchronous for-in loops and one async, where the priority is based by the position of the loops and the kind of the queues (sync/async) in the code because here we have 3 different queues (following the instructions about your link queuesWithQoS() could be launched in viewDidAppearso we can suppose is in the main queue)
The code show the creation of two queues with default priority, so the sequence of the execution will be:
the for-in loop with 1000..<1005 in the main queue
the synchronous queue2 with default priority
the asynchronous queue1 (not ignored, simply delayed) with default priority
Main queue have always the highest priority where all the UI instructions are executed.