I have some doubts regarding GCD.
Code snippet 1
serialQ.sync {
print(1)
serialQ.async {
print(2)
}
serialQ.async {
print(3)
}
}
Code snippet 2
serialQ.async {
print(1)
serialQ.async {
print(2)
}
serialQ.sync {
print(3)
}
}
I ran both of them in playground, and found that Code snippet 2 gives deadlock while Code snippet 1 runs fine. I have read a lot about GCD and started playing around with these concepts. Can anyone please provide a detailed explanation for the same ?
PS : serialQ is a serial Queue
According to my understanding,
Serial Queue - generates only one thread at a time, and once that thread is freed up then it is occupied or free to do other tasks
Serial Queue dispatched sync - blocks the caller thread from which the serial queue is dispatched and performs the tasks on that thread.
Serial Queue dispatched async - does'nt not blocks the caller thread, infact it runs on a different thread and keeps the caller
thread running.
But for the above query I am not able to get the proper explanation.
You are calling sync inside a block already executing on the same queue. This will always cause a deadlock. sync is the equivalent of saying “execute this now and wait for it to return.” Since you are already executing on that queue, the queue never becomes available to execute the sync block. It sounds like you’re looking for recursive locks, but that’s not how queues work. It’s also quite arguably an anti-pattern in general. I discuss this more in this answer: How to implement a reentrant locking mechanism in objective-c through GCD?
EDIT: Came back to add some thoughts on your "understandings":
Serial Queue - generates only one thread at a time, and once that thread is freed up then it is occupied or free to do other tasks
A serial queue doesn't "generate" one thread. Queues and threads are different things and have different semantics. A serial queue requires one thread upon which to execute a work item, but there's not a one-to-one relationship between a serial queue and a thread. A thread is a relatively "heavy" resource and a queue is a relatively "light" resource. A single serial queue can execute work items on more than one thread over its lifetime (although never more than one thread at the same time). GCD maintains pools of threads that it uses to execute work items, but that is an implementation detail, and it's not necessary to understand how that's implemented in order to use queues properly.
Serial Queue dispatched sync - blocks the caller thread from which the serial queue is dispatched and performs the tasks on that thread.
A queue (serial or concurrent) is not "dispatched" (sync or otherwise). A work item is, well, enqueued into a queue. That work item will be subsequently executed by an arbitrary thread including, quite possibly, the calling thread. The guarantee is that only one work item enqueued to a given serial queue will be executing (on any thread) at one time.
Serial Queue dispatched async - doesn't block the ~caller~ enqueueing thread, in fact it runs on a different thread and keeps the caller thread running. (minor edits for readability)
This is close, but not quite accurate. It's true that enqueueing a work item onto a serial queue with async does not block the enqueueing thread. It's not necessarily true that the work item is executed by a different thread than the enqueueing thread, although in the common case, that is usually the case.
The thing to know here is that the difference between sync and async is strictly limited to the behavior of the enqueueing thread and has no (guaranteed) bearing or impact on which thread the work item is executed. If you enqueue a work item with sync the enqueueing thread will wait (possibly forever, in the specific case you outlined here) for the work item to complete, whereas if you enqueue a work item with async the enqueueing thread will continue executing.
A sync call, as you point out, blocks the current thread until the block runs. So when you make sync to the same serial queue that you’re currently on, you are blocking the queue, waiting for a block to run on the same queue that you just blocked, resulting in a deadlock.
If you really want to run something synchronously on the current queue, don’t dispatch it with sync at all and just run it directly. E.g.:
serialQ.async {
print(1)
serialQ.async {
print(2)
}
// serialQ.sync { // don't dispatch synchronously to the current serial queue
print(3)
// }
}
Or dispatch asynchronously. E.g.,
serialQ.async {
print(1)
serialQ.async {
print(2)
}
serialQ.async {
print(3)
}
}
Or use a concurrent queue (in which case you have to be careful to make sure you don’t have thread explosion, which could result in deadlock, too). E.g.,
let concurrentQ = DispatchQueue(label: "...", attributes: .concurrent)
concurrentQ.async {
print(1)
concurrentQ.async {
print(2)
}
concurrentQ.sync {
print(3)
}
}
Updating UI on a thread other than the main thread is a common mistake that can result in missed UI updates, visual defects, data corruptions, and crashes.
https://developer.apple.com/documentation/code_diagnostics/main_thread_checker
Example:
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
DispatchQueue.main.async { // Correct
self.label.text = "\(data.count) bytes downloaded"
}
}
}
task.resume()
My question starts here -
I am confused with above statement when we say .async means not simultaneously (Or not parallel) with .main. Can someone explain my problem?
DispatchQueue.main.async means you queue up a task in the main queue, without waiting the task to be executed. The main queue tasks will be run on the main thread one by one automatically, scheduled by the OS.
Think of each DispatchQueue as a worker. Calling .async adds a task under the worker's TODO list and do not wait for the worker to finish the task. DispathQueue.main is the specific worker that work on the main thread.
Oh the other hand, .sync will block the thread until the task block has finished executing. You can call .sync on any thread other than the main thread since main thread must not be blocked.
That doesn't means you cannot call DispatchQueue.main.sync. You can call DispatchQueue.main.sync just like any custom dispathQueue.sync on non- main thread.
e.g.
DispatchQueue(label: "bgqueue", qos: .background).async
{
DispatchQueue.main.sync{}
}
is OK.
But
DispatchQueue.main.async{
DispatchQueue.main.sync{}
}
is NOT.
.sync is usually not quite useful. If you want something to happen after a main queue task, you just queue that "something" into the main queue too. It is not worth to block a thread if not necessary.
That being said, here are two rules to remember when using .sync, regardless of which queue is receiving the .sync call :
never call .sync from a queue to itself, which causes deadlock.
never call .sync from main queue, which blocks the UI thread.
I think you are confused how DispatchQueue works.
DispatchQueue simply manages thread pool, and when we give it a block of code to execute it simply picks an idle thread and run that piece of code on it.
So basically one thread can be used by many queues. A queue is simply a task list which manages all the tasks which will execute in future.
So basically here when you are doing DispatchQueue.main.async then you are simply instructing main queue to execute your code without waiting for pending tasks execution.
This code will deadlock because:
they are in same thread
print(2) has to wait print(3)
print(3) has to wait print(2)
For example:
DispatchQueue.main.async {
print(Thread.current)
DispatchQueue.main.sync {
print(Thread.current)
print(2)
}
print(3)
}
Why in the concurrentQueue won't cause deadlock? They are also in same thread.
DispatchQueue.global().async {
print(Thread.current)
DispatchQueue.global().sync {
print(Thread.current)
print(2)
}
print(3)
}
You ask:
Why in the concurrentQueue won't cause deadlock? They are also in same thread.
No, they're necessarily in the same thread. They're in the same queue, but not necessarily the same thread. (As part of an optimization, see below, it actually might end up running on the same thread, but not necessarily so. But logically, you should think of it as running on a separate thread.)
This is the whole idea behind “concurrent queues” is that they can run individual dispatched tasks on different threads. That’s how they permit concurrent operation. One dispatched task on a concurrent queue can run on one thread while another dispatched task on that same queue can run on a separate thread.
As the old Concurrency Programming Guide says in its definition of a “concurrent queue”:
The currently executing tasks run on distinct threads that are managed by the dispatch queue.
Or, as the DispatchQueue documentation says:
DispatchQueue manages the execution of work items. Each work item submitted to a queue is processed on a pool of threads managed by the system. [emphasis added]
What makes this a little more confusing is that there is a GCD optimization that, if you are dispatching synchronously ...
As a performance optimization, this function executes blocks on the current thread whenever possible...
So, when dispatching synchronously from a queue, it actually can end up running that code on the same thread (unless you’re dispatching from a background queue to the main queue). Consider:
let queue = DispatchQueue.global()
let queue2 = DispatchQueue(label: "queue2")
queue.async {
print(1, Thread.current)
queue2.sync {
print(2, Thread.current)
}
print(3, Thread.current)
}
That second print statement will show that even though you have a completely different queue, it may, as part of the aforementioned sync optimization, run the code on the current thread. The same it true if that inner sync call was to the same queue to which the outer block was dispatched.
Now, when looking at the result of this optimization, it feels like this is just like the serial queue scenario, but it’s not. A serial queue only allows one dispatched task to run at a time, and thus the attempt to dispatch synchronously (blocking the current thread until the dispatched block runs) to itself is, by definition, a deadlock.
But a concurrent queue dispatching to itself will generally not deadlock. The one caveat is that the number of worker threads is fairly limited, so if you have “thread explosion” (where you might have more than 64 worker threads going), then it can deadlock, so make sure your limit your concurrency to some reasonable value.
Example A:- This cause App Crash.
DispatchQueue.main.async {
let url = URL(string: imageUrl)
do {
let data = try Data(contentsOf: url!)
DispatchQueue.main.sync {
self.imageIcon.image = UIImage(data: data)
}
}
Example B:- But This don't
DispatchQueue.global().async {
let url = URL(string: imageUrl)
do {
let data = try Data(contentsOf: url!)
DispatchQueue.main.sync {
self.imageIcon.image = UIImage(data: data)
}
}
As per my knowledge,
x.sync means doing thing in main thread/UI thread and x.async means
doing in background thread.
Global means performing something with concurrent queue i.e Parallel
task.
Quest1:- So why does my app crashed when i performed task in background thread i.e main.async and than call main thread to update UI.
Quest2:- Is there any difference in main.async & global().async.
In simple term i come to conclusion that -
Queue- There are 3 Types of Queue i.e. 1 Main Queue, 4 Global Queue and Any No. of Custom Queues.
Threads- One is Main Thread and other background threads which system
provides to us.
DispatchQueue.main.async
-It means performing task in main queue with using of background thread (w/o blocking of UI) and when task finish it automatic Updated to UI because its already in Main Queue.
DispatchQueue.global().async along with global().sync
It means performing task in Global Queue with using of background thread and when task finish, than global().sync use bring the work from globalQueue to mainQueue which update to UI.
Reason of My App Crash
I was trying to bring the completed task to MainQueue by using(main.sync), but it was already on MainQueue because i hadnt switched the Queue, and this create DeadLock (MainQueue waiting for itself), causes my app crash
GCD
Thread -> GCD -> Operation + OperationQueue(life cycle, dependencies between different queues, cancel)
[Sync vs Async]
[iOS Thread safe]
Grand Central Dispatch GCD libdispatch operates on dispatch queues DispatchQueue with a FIFO order
DispatchQueue.<queue>.<sync/async> means run a <sync/async> task on the <queue>
GCD supports:
global queue - shared between whole iOS operation system
private queue
main - global serial queue on a main thread which is used to working with UI
DispatchQueue.main
global() - global concurrent.
DispatchQueue.global()
DispatchQueue.global(qos: .background)
Custom queue: - private serial\concurrent queue
init(label: String, qos: DispatchQoS = .unspecified, attributes: DispatchQueue.Attributes = [], autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit, target: DispatchQueue? = nil)
DispatchQueue(label: "serialQueue") // without attributes
DispatchQueue(label: "concurrentQueue", attributes: .concurrent)
QUEUE
Usually when we talk about concurrent we talk about queues. Count of threads are depends on OS conditions. There are no run loop[About] for worker thread.
concurrent has different groups of queues with priorities(main thread, hight, default, low, background) which you pass at the task using qos QoSClass(Quality of Service) (priority GlobalQueuePriority is deprecated):
userInteractive - the highest priority. It can be used for very fast calculation which immediately are reflected on UI. For example animation calculations
userInitiated - high priority queue - relevant for UI. Up to several seconds. For example loading data for showing on UI
default - default priority queue
utility - low priority queue - up to several minutes like working with big data like images, processing...
background - background priority queue - up to several hours while app is on background like sync data.
sync/async
sync - block a current thread and wait when it will be finished on a specified queue
async - do not block a current thread and send an execution block of code to the specificified queue
Common mistake: deadlock
If you call DispatchQueue.main.sync on a main thread - the app will be frozen because the calling DispatchQueue.main.sync starts waiting immediately when the dispatched block is finished (dispatched block is not started)
Some notes:
DispatchWorkItem - delaying/cancelling/prioritise a task inside Queue or DispatchGroup
DispatchGroup if you are going to execute several async tasks with a single callback even on different queues. All these task should be grouped. DispatchGroup contains thread safe counter and when it equals 0 notify is called
//create group
let group = DispatchGroup()
//case 1
DispatchQueue.<queue>.async(group: group) //
//case 2 - manual
group.enter() //<- +1
DispatchQueue.global().async {
//logic
group.leave() //<- -1
}
//notification
group.notify(queue: <callback_queue>) {
//logic
}
Barrier flag inside concurrent queue for sync/async task guaranties that there is no race condition[About]. The best place for it is custom queue because does not block any others global tasks:
customQueue.async(flags: .barrier) {
//logic
someProperty = someValue
}
all task which were started are finished
Single Barrier task
Executing all other tasks in the queue
thread safe operation can be reached through Barrier in concurrent queue for shared variable:
read - sync operation on concurrent queue
write - async operation with barrier
[Thread safe singleton]
In first case, you run the code on main and then you use main.sync on the main thread. In essence, you are trying to tell the main queue to wait for itself - which is obviously nonsense and therefore it causes crash.
In the second case, you run the code on the background thread, and then you use main.sync to wait until the main thread can run the block provided in main.sync.
In general, I would use async and not sync all the time, unless sync is necessary - and always sync one thread (DispatchQueue) with a different one, never with the same one.
You were mixing up the terms sync/async and main/global.
Sync - Run some task synchronously (i.e. the thread which can be main/global/any other thread will wait for the task to complete)
Async - Run some task asynchronously (i.e. the thread which can be main/global/any other thread will push the task to a queue and continue executing next steps outside your block. It wont wait)
Now lets go one by one in your code which was crashing :
Lets put some names for our threads so it will be easy for our understanding:
1) ThreadA - Which encounters your dispatch statements (this can also be Main thread, but for explanation purpose I feel its better)
2) ThreadB - Global thread which gets created when you submit some task.
3) ThreadMain - Main thread
Example A:
DispatchQueue.main.async - ThreadA comes and execute this statement and put your block on ThreadMain and moves on (since its async) to next steps after the block. Now lets talk about ThreadMain, what it will do from here. Since ThreadMain got a block (submitted by ThreadA) it starts executing step by step and suddenly it sees 'DispatchQueue.main.sync' and submits the inner block on to the same TheradMain queue and keeps onnnnn waitingggggg (since its sync). So literally you are making the ThreadMain into deadlock situation.
I'm struggling to fully understand the concurrent and serial queues in GCD. I have some issues and hoping someone can answer me clearly and at the point.
I'm reading that serial queues are created and used in order to execute tasks one after the other. However, what happens if:
I create a serial queue
I use dispatch_async (on the serial queue I just created) three times to dispatch three blocks A,B,C
Will the three blocks be executed:
in order A,B,C because the queue is serial
OR
concurrently (in the same time on parralel threads) because I used ASYNC dispatch
I'm reading that I can use dispatch_sync on concurrent queues in order to execute blocks one after the other. In that case, WHY do serial queues even exist, since I can always use a concurrent queue where I can dispatch SYNCHRONOUSLY as many blocks as I want?
Thanks for any good explanation!
A simple example: you have a block that takes a minute to execute. You add it to a queue from the main thread. Let's look at the four cases.
async - concurrent: the code runs on a background thread. Control returns immediately to the main thread (and UI). The block can't assume that it's the only block running on that queue
async - serial: the code runs on a background thread. Control returns immediately to the main thread. The block can assume that it's the only block running on that queue
sync - concurrent: the code runs on a background thread but the main thread waits for it to finish, blocking any updates to the UI. The block can't assume that it's the only block running on that queue (I could have added another block using async a few seconds previously)
sync - serial: the code runs on a background thread but the main thread waits for it to finish, blocking any updates to the UI. The block can assume that it's the only block running on that queue
Obviously you wouldn't use either of the last two for long running processes. You normally see it when you're trying to update the UI (always on the main thread) from something that may be running on another thread.
Here are a couple of experiments that i have done to make me understand about these serial, concurrent queues with Grand Central Dispatch.
func doLongAsyncTaskInSerialQueue() {
let serialQueue = DispatchQueue(label: "com.queue.Serial")
for i in 1...5 {
serialQueue.async {
if Thread.isMainThread{
print("task running in main thread")
}else{
print("task running in background thread")
}
let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
let _ = try! Data(contentsOf: imgURL)
print("\(i) completed downloading")
}
}
}
Task will run in different thread(other than main thread) when you use async in GCD. Async means execute next line do not wait until the block executes which results non blocking main thread & main queue.
Since its serial queue, all are executed in the order they are added to serial queue.Tasks executed serially are always executed one at a time by the single thread associated with the Queue.
func doLongSyncTaskInSerialQueue() {
let serialQueue = DispatchQueue(label: "com.queue.Serial")
for i in 1...5 {
serialQueue.sync {
if Thread.isMainThread{
print("task running in main thread")
}else{
print("task running in background thread")
}
let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
let _ = try! Data(contentsOf: imgURL)
print("\(i) completed downloading")
}
}
}
Task may run in main thread when you use sync in GCD. Sync runs a block on a given queue and waits for it to complete which results in blocking main thread or main queue.Since the main queue needs to wait until the dispatched block completes, main thread will be available to process blocks from queues other than the main queue.Therefore there is a chance of the code executing on the background queue may actually be executing on the main thread
Since its serial queue, all are executed in the order they are added(FIFO).
func doLongASyncTaskInConcurrentQueue() {
let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
for i in 1...5 {
concurrentQueue.async {
if Thread.isMainThread{
print("task running in main thread")
}else{
print("task running in background thread")
}
let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
let _ = try! Data(contentsOf: imgURL)
print("\(i) completed downloading")
}
print("\(i) executing")
}
}
Task will run in background thread when you use async in GCD. Async means execute next line do not wait until the block executes which results non blocking main thread.
Remember in concurrent queue, task are processed in the order they are added to queue but with different threads attached to the
queue. Remember they are not supposed to finish the task as the order
they are added to the queue.Order of task differs each time
threads are created as necessarily automatically.Task are executed in parallel. With more than
that(maxConcurrentOperationCount) is reached, some tasks will behave
as a serial until a thread is free.
func doLongSyncTaskInConcurrentQueue() {
let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
for i in 1...5 {
concurrentQueue.sync {
if Thread.isMainThread{
print("task running in main thread")
}else{
print("task running in background thread")
}
let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
let _ = try! Data(contentsOf: imgURL)
print("\(i) completed downloading")
}
print("\(i) executed")
}
}
Task may run in main thread when you use sync in GCD. Sync runs a block on a given queue and waits for it to complete which results in blocking main thread or main queue.Since the main queue needs to wait until the dispatched block completes, main thread will be available to process blocks from queues other than the main queue.Therefore there is a chance of the code executing on the background queue may actually be executing on the main thread.
Since its concurrent queue, tasks may not finish in the order they are added to queue. But with synchronous operation it does although they may be processed by different threads. So, it behaves as this is the serial queue.
Here is a summary of these experiments
Remember using GCD you are only adding task to the Queue and performing task from that queue. Queue dispatches your task either in main or background thread depending on whether operation is synchronous or asynchronous. Types of queues are Serial,Concurrent,Main dispatch queue.All the task you perform is done by default from Main dispatch queue.There are already four predefined global concurrent queues for your application to use and one main queue(DispatchQueue.main).You can also manually create your own queue and perform task from that queue.
UI Related task should always be performed from main thread by dispatching the task to Main queue.Short hand utility is DispatchQueue.main.sync/async whereas network related/heavy operations should always be done asynchronously no matters which ever thread you are using either main or background
EDIT:
However, There are cases you need to perform network calls operations synchronously in a background thread without freezing UI(e.g.refreshing OAuth Token and wait if it succeed or not).You need to wrap that method inside a asynchronous operation.This way your heavy operations are executed in the order and without Blocking main thread.
func doMultipleSyncTaskWithinAsynchronousOperation() {
let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
concurrentQueue.async {
let concurrentQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
for i in 1...5 {
concurrentQueue.sync {
let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
let _ = try! Data(contentsOf: imgURL)
print("\(i) completed downloading")
}
print("\(i) executed")
}
}
}
EDIT EDIT: You can watch demo video here
First, it's important to know the difference between threads and queues and what GCD really does. When we use dispatch queues (through GCD), we're really queueing, not threading. The Dispatch framework was designed specifically to get us away from threading, as Apple admits that "implementing a correct threading solution [can] become extremely difficult, if not [sometimes] impossible to achieve." Therefore, to perform tasks concurrently (tasks that we don't want freezing the UI), all we need to do is create a queue of those tasks and hand it to GCD. And GCD handles all of the associated threading. Therefore, all we're really doing is queueing.
The second thing to know right away is what a task is. A task is all of the code within that queue block (not within the queue, because we can add things to a queue all of the time, but within the closure where we added it to the queue). A task is sometimes referred to as a block and a block is sometimes referred to as a task (but they are more commonly known as tasks, particularly in the Swift community). And no matter how much or little code, all of the code within the curly braces are considered a single task:
serialQueue.async {
// this is one task
// it can be any number of lines with any number of methods
}
serialQueue.async {
// this is another task added to the same queue
// this queue now has two tasks
}
And it's obvious mentioning that concurrent simply means at the same time with other things and serial means one after the other (never at the same time). To serialize something, or to put something in serial, just means to execute it from start to finish in its order from left to right, top to bottom, uninterrupted.
There are two types of queues, serial and concurrent, but all queues are concurrent relative to each other. The fact that you want to run any code "in the background" means that you want to run it concurrently with another thread (usually the main thread). Therefore, all dispatch queues, serial or concurrent, execute their tasks concurrently relative to other queues. Any serialization performed by queues (by serial queues), have only to do with the tasks within that single [serial] dispatch queue (like in the example above where there are two tasks within the same serial queue; those tasks will be executed one after the other, never simultaneously).
SERIAL QUEUES (often known as private dispatch queues) guarantee the execution of tasks one at a time from start to finish in the order that they were added to that specific queue. This is the only guarantee of serialization anywhere in the discussion of dispatch queues--that the specific tasks within a specific serial queue are executed in serial. Serial queues can, however, run simultaneously with other serial queues if they are separate queues because, again, all queues are concurrent relative to each other. All tasks run on distinct threads but not every task is guaranteed to run on the same thread (not important, but interesting to know). And the iOS framework does not come with any ready-to-use serial queues, you must make them. Private (non-global) queues are serial by default, so to create a serial queue:
let serialQueue = DispatchQueue(label: "serial")
You can make it concurrent through its attribute property:
let concurrentQueue = DispatchQueue(label: "concurrent", attributes: [.concurrent])
But at this point, if you aren't adding any other attributes to the private queue, Apple recommends that you just use one of their ready-to-go global queues (which are all concurrent). At the bottom of this answer, you'll see another way to create serial queues (using the target property), which is how Apple recommends doing it (for more efficient resource management). But for now, labeling it is sufficient.
CONCURRENT QUEUES (often known as global dispatch queues) can execute tasks simultaneously; the tasks are, however, guaranteed to initiate in the order that they were added to that specific queue, but unlike serial queues, the queue does not wait for the first task to finish before starting the second task. Tasks (as with serial queues) run on distinct threads and (as with serial queues) not every task is guaranteed to run on the same thread (not important, but interesting to know). And the iOS framework comes with four ready-to-use concurrent queues. You can create a concurrent queue using the above example or by using one of Apple's global queues (which is usually recommended):
let concurrentQueue = DispatchQueue.global(qos: .default)
RETAIN-CYCLE RESISTANT: Dispatch queues are reference-counted objects but you do not need to retain and release global queues because they
are global, and thus retain and release is ignored. You can access
global queues directly without having to assign them to a property.
There are two ways to dispatch queues: synchronously and asynchronously.
SYNC DISPATCHING means that the thread where the queue was dispatched (the calling thread) pauses after dispatching the queue and waits for the task in that queue block to finish executing before resuming. To dispatch synchronously:
DispatchQueue.global(qos: .default).sync {
// task goes in here
}
ASYNC DISPATCHING means that the calling thread continues to run after dispatching the queue and does not wait for the task in that queue block to finish executing. To dispatch asynchronously:
DispatchQueue.global(qos: .default).async {
// task goes in here
}
Now one might think that in order to execute a task in serial, a serial queue should be used, and that's not exactly right. In order to execute multiple tasks in serial, a serial queue should be used, but all tasks (isolated by themselves) are executed in serial. Consider this example:
whichQueueShouldIUse.syncOrAsync {
for i in 1...10 {
print(i)
}
for i in 1...10 {
print(i + 100)
}
for i in 1...10 {
print(i + 1000)
}
}
No matter how you configure (serial or concurrent) or dispatch (sync or async) this queue, this task will always be executed in serial. The third loop will never run before the second loop and the second loop will never run before the first loop. This is true in any queue using any dispatch. It's when you introduce multiple tasks and/or queues where serial and concurrency really come into play.
Consider these two queues, one serial and one concurrent:
let serialQueue = DispatchQueue(label: "serial")
let concurrentQueue = DispatchQueue.global(qos: .default)
Say we dispatch two concurrent queues in async:
concurrentQueue.async {
for i in 1...5 {
print(i)
}
}
concurrentQueue.async {
for i in 1...5 {
print(i + 100)
}
}
1
101
2
102
103
3
104
4
105
5
Their output is jumbled (as expected) but notice that each queue executed its own task in serial. This is the most basic example of concurrency--two tasks running at the same time in the background in the same queue. Now let's make the first one serial:
serialQueue.async {
for i in 1...5 {
print(i)
}
}
concurrentQueue.async {
for i in 1...5 {
print(i + 100)
}
}
101
1
2
102
3
103
4
104
5
105
Isn't the first queue supposed to be executed in serial? It was (and so was the second). Whatever else happened in the background is not of any concern to the queue. We told the serial queue to execute in serial and it did... but we only gave it one task. Now let's give it two tasks:
serialQueue.async {
for i in 1...5 {
print(i)
}
}
serialQueue.async {
for i in 1...5 {
print(i + 100)
}
}
1
2
3
4
5
101
102
103
104
105
And this is the most basic (and only possible) example of serialization--two tasks running in serial (one after the other) in the background (to the main thread) in the same queue. But if we made them two separate serial queues (because in the above example they are the same queue), their output is jumbled again:
serialQueue.async {
for i in 1...5 {
print(i)
}
}
serialQueue2.async {
for i in 1...5 {
print(i + 100)
}
}
1
101
2
102
3
103
4
104
5
105
And this is what I meant when I said all queues are concurrent relative to each other. These are two serial queues executing their tasks at the same time (because they are separate queues). A queue does not know or care about other queues. Now lets go back to two serial queues (of the same queue) and add a third queue, a concurrent one:
serialQueue.async {
for i in 1...5 {
print(i)
}
}
serialQueue.async {
for i in 1...5 {
print(i + 100)
}
}
concurrentQueue.async {
for i in 1...5 {
print(i + 1000)
}
}
1
2
3
4
5
101
102
103
104
105
1001
1002
1003
1004
1005
That's kind of unexpected, why did the concurrent queue wait for the serial queues to finish before it executed? That's not concurrency. Your playground may show a different output but mine showed this. And it showed this because my concurrent queue's priority wasn't high enough for GCD to execute its task sooner. So if I keep everything the same but change the global queue's QoS (its quality of service, which is simply the queue's priority level) let concurrentQueue = DispatchQueue.global(qos: .userInteractive), then the output is as expected:
1
1001
1002
1003
2
1004
1005
3
4
5
101
102
103
104
105
The two serial queues executed their tasks in serial (as expected) and the concurrent queue executed its task quicker because it was given a high priority level (a high QoS, or quality of service).
Two concurrent queues, like in our first print example, show a jumbled printout (as expected). To get them to print neatly in serial, we would have to make both of them the same serial queue (the same instance of that queue, as well, not just the same label). Then each task is executed in serial with respect to the other. Another way, however, to get them to print in serial is to keep them both concurrent but change their dispatch method:
concurrentQueue.sync {
for i in 1...5 {
print(i)
}
}
concurrentQueue.async {
for i in 1...5 {
print(i + 100)
}
}
1
2
3
4
5
101
102
103
104
105
Remember, sync dispatching only means that the calling thread waits until the task in the queue is completed before proceeding. The caveat here, obviously, is that the calling thread is frozen until the first task completes, which may or may not be how you want the UI to perform.
And it is for this reason that we cannot do the following:
DispatchQueue.main.sync { ... }
This is the only possible combination of queues and dispatching methods that we cannot perform—synchronous dispatching on the main queue. And that's because we are asking the main queue to freeze until we execute the task within the curly braces... which we dispatched to the main queue, which we just froze. This is called deadlock. To see it in action in a playground:
DispatchQueue.main.sync { // stop the main queue and wait for the following to finish
print("hello world") // this will never execute on the main queue because we just stopped it
}
// deadlock
One last thing to mention is resources. When we give a queue a task, GCD finds an available queue from its internally-managed pool. As far as the writing of this answer, there are 64 queues available per qos. That may seem like a lot but they can quickly be consumed, especially by third-party libraries, particularly database frameworks. For this reason, Apple has recommendations about queue management (mentioned in the links below); one being:
Instead of creating private concurrent queues, submit tasks to one of
the global concurrent dispatch queues. For serial tasks, set the
target of your serial queue to one of the global concurrent queues.
That way, you can maintain the serialized behavior of the queue while
minimizing the number of separate queues creating threads.
To do this, instead of creating them like we did before (which you still can), Apple recommends creating serial queues like this:
let serialQueue = DispatchQueue(label: "serialQueue", qos: .default, attributes: [], autoreleaseFrequency: .inherit, target: .global(qos: .default))
And using an extension, we can get it down to this:
extension DispatchQueue {
public class func serial(label: String, qos: DispatchQoS = .default) -> DispatchQueue {
return DispatchQueue(label: label,
qos: qos,
attributes: [],
autoreleaseFrequency: .inherit,
target: .global(qos: qos.qosClass))
}
}
let defaultSerialQueue = DispatchQueue.serial(label: "xyz")
let serialQueue = DispatchQueue.serial(label: "xyz", qos: .userInteractive)
// Which now looks like the global initializer
let concurrentQueue = DispatchQueue.global(qos: .default)
For further reading, I recommend the following:
https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008091-CH1-SW1
https://developer.apple.com/documentation/dispatch/dispatchqueue
I like to think this using this metaphor (Here's the link to the original image):
Let's imagine your dad is doing the dishes and you've just had a glass of soda. You bring the glass to your dad to clean it up, putting it besides the other dish.
Now your dad is doing the dishes all by himself, so he's going to have to do them one by one: Your dad here represents a serial queue.
But you're not really interested in standing there and watching it get cleaned up. So, you drop the glass, and go back to your room: this is called an async dispatch. Your dad might or might not let you know once he's done but the important bit is that you're not waiting for the glass to be cleaned up; you go back to your room to do, you know, kid stuff.
Now let's assume you're still thirsty and want to have some water on that same glass that happens to be your favourite, and you really want it back as soon as it's cleaned up. So, you stand there and watch your dad doing the dishes until yours is done. This is a sync dispatch, since you're blocked while you are waiting for the task to be finished.
And finally let's say your mom decides to help your dad and joins him doing the dishes. Now the queue becomes a concurrent queue since they can clean multiple dishes at the same time; but note that you can still decide to wait there or go back to your room, regardless of how they work.
Hope this helps
If I understand correctly about how GCD works, I think there are two types of DispatchQueue, serial and concurrent, at the same time, there are two way how DispatchQueue dispatch its tasks, the assigned closure, first one is async, and the other is sync. Those together determines how the closure (task) actually is executed.
I found that serial and concurrent mean how many threads that queue can use, serial means one, whereas concurrent means many. And sync and async mean the task will be executed on which thread, the caller's thread or the thread underlying that queue, sync means run on caller's thread whereas async means run on the underlying thread.
The following is experimental code that can run on Xcode playground.
PlaygroundPage.current.needsIndefiniteExecution = true
let cq = DispatchQueue(label: "concurrent.queue", attributes: .concurrent)
let cq2 = DispatchQueue(label: "concurent.queue2", attributes: .concurrent)
let sq = DispatchQueue(label: "serial.queue")
func codeFragment() {
print("code Fragment begin")
print("Task Thread:\(Thread.current.description)")
let imgURL = URL(string: "http://stackoverflow.com/questions/24058336/how-do-i-run-asynchronous-callbacks-in-playground")!
let _ = try! Data(contentsOf: imgURL)
print("code Fragment completed")
}
func serialQueueSync() { sq.sync { codeFragment() } }
func serialQueueAsync() { sq.async { codeFragment() } }
func concurrentQueueSync() { cq2.sync { codeFragment() } }
func concurrentQueueAsync() { cq2.async { codeFragment() } }
func tasksExecution() {
(1...5).forEach { (_) in
/// Using an concurrent queue to simulate concurent task executions.
cq.async {
print("Caller Thread:\(Thread.current.description)")
/// Serial Queue Async, tasks run serially, because only one thread that can be used by serial queue, the underlying thread of serial queue.
//serialQueueAsync()
/// Serial Queue Sync, tasks run serially, because only one thread that can be used by serial queue,one by one of the callers' threads.
//serialQueueSync()
/// Concurrent Queue Async, tasks run concurrently, because tasks can run on different underlying threads
//concurrentQueueAsync()
/// Concurrent Queue Sync, tasks run concurrently, because tasks can run on different callers' thread
//concurrentQueueSync()
}
}
}
tasksExecution()
Hope it can be helpful.
1. I am reading that serial queues are created and used in order to execute tasks one after the other . However, what happens if:-
• I create a serial queue
• I use dispatch_async (on the serial queue I just created) three times to dispatch three blocks A,B,C
ANSWER:-
All three blocks executed one after the another.I have created one sample code that helps to understand.
let serialQueue = DispatchQueue(label: "SampleSerialQueue")
//Block first
serialQueue.async {
for i in 1...10{
print("Serial - First operation",i)
}
}
//Block second
serialQueue.async {
for i in 1...10{
print("Serial - Second operation",i)
}
}
//Block Third
serialQueue.async {
for i in 1...10{
print("Serial - Third operation",i)
}
}