Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does sync/async behave similar to serial/concurrent i.e., do they both control DispatchQueues or do sync/Async control Threads only

Most answers on stackoverflow implies in a way that sync vs async behaviour is quite similar to serial vs concurrent queue concept difference. Like the link in the first comment by @Roope

I have started to think that Serial and concurrent are related to DispatchQueue, and sync/ async for how an operation will get executed on a thread. Am I right?

Like if we've got DQ.main.sync then task/operation closure will get executed in a synchronous manner on this serial (main) queue. And, if I do DQ.main.async then task will get asynchronously on some other background queue, and on reaching completion will return control on main thread. And, since main is a serial queue, it won't let any other task/operation get into execution state/ start getting executed until the current closure task has finished its execution.

Then, DQ.global().sync would execute a task synchronously on the thread on which its task/operation has been assigned i.e., it will block that thread from doing any other task/operation by blocking any context switching on that particular thread. And, since, global is a concurrent queue it will keep on putting the tasks present in it to the execution state irrespective of previous task/operation's execution state.

DQ.global().async would allow context switching on the thread on which the operation closure has been put for execution

Is this the correct interpretations of the above dispatchQueues and sync vs async?

like image 551
Aron Avatar asked Mar 04 '23 04:03

Aron


1 Answers

You are asking the right questions but I think you got a bit confused (mostly due to not very clear posts about this topic on internet).

Concurrent / Serial

Let's look at how you can create a new dispatch Queue:

let serialQueue = DispatchQueue(label: label)

If you don't specify any other additional parameter, this queue will behave as a serial queue: This means that every block dispatched on this queue (sync or async it doesn't matter) will be executed alone, without the possibility for other blocks to be executed, on that same queue, simultaneously.

This doesn't mean that anything else is stopped, it just means that if something else is dispatched on that same queue, it will wait for the first block to finish before starting it's execution. Other threads and queues will still run on their own.


You can, however, create a concurrent queue, that will not constraint this blocks of code in this manner and, instead, if it happens that more blocks of code are dispatched on that same queue at the same time, it will execute them at the same time (on different threads)

let concurrentQueue = DispatchQueue(label: label,
                      qos: .background,
                      attributes: .concurrent,
                      autoreleaseFrequency: .inherit,
                      target: .global())

So, you just need to pass the attribute concurrent to the queue, and it won't be serial anymore.

(I won't be talking about the other parameters since they are not in focus of this particular question and, I think, you can read about them in the other SO post linked in the comment or, if it's not enough, you can ask another question)


If you want to understand more about concurrent queues (aka: skip if you don't care about concurrent queues)

You could ask: When do I even need a concurrent queue?

Well, just for example, let's think of a use-case where you want to synchronize READS on a shared resource: since the reads can be done simultaneously without issues, you could use a concurrent queue for that.

But what if you want to write on that shared resource? well, in this case a write needs to act as a "barrier" and during the execution of that write, no other write and no reads can operate on that resource simultaneously. To obtain this kind of behavior, the swift code would look something like this

concurrentQueue.async(flags: .barrier, execute: { /*your barriered block*/ })

So, in other words, you can make a concurrent queue work temporarily as a serial queue in case you need.


Once again, the concurrent / serial distinction is only valid for blocks dispatched to that same queue, it has nothing to do with other concurrent or serial work that can be done on another thread/queue.

SYNC / ASYNC

This is totally another issue, with virtually no connection to the previous one.

This two ways to dispatch some block of code are relative to the current thread/queue you are at the time of the dispatch call. This dispatch call blocks (in case of sync) or doesn't block (async) the execution of that thread/queue while executing the code you dispatch on the other queue.

So let's say I'm executing a method and in that method I dispatch async something on some other queue (I'm using main queue but it could be any queue):

func someMethod() {
    var aString = "1"
    DispatchQueue.main.async {
        aString = "2"
    }
    print(aString)
}

What happens is that this block of code is dispatched on another queue and could be executed serially or concurrently on that queue, but that has no correlation to what is happening on the current queue (which is the one on which someMethod is called).

What happens on the current queue is that the code will continue executing and won't wait for that block to be completed before printing that variable. This means that, very likely, you will see it print 1 and not 2. (More precisely you can't know what will happen first)

If instead you would dispatch it sync, than you would've ALWAYS printed 2 instead of 1, because the current queue would've waited for that block of code to be completed, before continuing in it's execution.

So this will print 2:

func someMethod() {
    var aString = "1"
    DispatchQueue.main.sync {
        aString = "2"
    }
    print(aString)
}

But does it mean that the queue on which someMethod is called is actually stopped?

Well, it depends on the current queue:

  • If it's serial, than yes. All the blocks previously dispatched to that queue or that will be dispatched on that queue will have to wait for that block to be completed.
  • If it's concurrent, than no. All concurrent blocks will continue their execution, only this specific block of execution will be blocked, waiting for this dispatch call to finish it's work. Of course if we are in the barriered case, than it's like for serial queues.

What happens when the currentQueue and the queue on which we dispatch are the same?

Assuming we are on serial queues (which I think will be most of your use-cases)

  • In case we dispatch sync, than deadlock. Nothing will ever execute on that queue anymore. That's the worst it could happen.
  • In case we dispatch async, than the code will be executed at the end of all the code already dispatched on that queue (including but not limited to the code executing right now in someMethod)

So be extra careful when you use the sync method, and be sure you are not on that same queue you are dispatching into.

I hope this let you understand better.

like image 196
Enricoza Avatar answered Mar 05 '23 21:03

Enricoza