Reputation: 5967
Ok so I have gone through a ton of questions and answers and I know the understanding of it, but when I am trying some code, I am getting some results that doesnt stand by those theories.
What I understand till now :
This is the part that seems fine. Now comes the tricky part
Eg-
let syncQ = DispatchQueue(label:"xyz") // by default it is serial
syncQ.sync{
for _ in 0...10{
print("ABC")
}
}
syncQ.sync{
for _ in 0...10{
print("XYZ")
}
}
Expected Output : ABC * 10 , XYZ * 10 This is fine.
Now when I introduce concurrent serial Q, the output is same. So my question is as concurrent queues say that tasks will be done in the same time or concurrently, it isnt happening.
Eg -
let syncConc = DispatchQueue(label:"con",attributes:.concurrent)
syncConc.sync{
for _ in 0...10{
print("XYZ")
}
for _ in 0...10{
print("ABC")
}
}
syncConc.sync{
for _ in 0...10{
print("HHH")
}
for _ in 0...10{
print("XXX")
}
}
Output : XYZ *10 ,ABC*10, HHH*10, XXX*10
So it seems that Synchronous Concurrent Queue, act like serial Queues, and only way to make concurrent operations is that if we throw a Asynchronous queue in between the action. So from this I cannot understand, what is the purpose of concurrent type of serial queues.
If anyone can give coded examples, it will be much appreciated, as I already know the theory and working of it. Much appreciated.
Upvotes: 9
Views: 13080
Reputation: 438467
A few terminology observations:
The terms “synchronous” and “asynchronous” dictate the behavior of the caller. It governs whether the calling thread will wait for the dispatched item or not.
The terms “serial“ and “concurrent” define the behavior of the queue to which you are dispatching. It dictates whether the queue can run two separately dispatched items on separate worker threads at the same time or not.
It is important to not conflate these terms. Synchronous/asynchronous dictates the calling thread’s behavior. Serial/concurrent dictates the dispatch queue behavior.
With that aside, a few observations:
Consider:
queue.sync {
for _ in 0...10 {
print("ABC")
}
}
// because above is `sync`, calling thread won't even get here until the
// code dispatched above is finished; it is irrelevant whether the queue
// is serial or concurrent.
queue.sync {
for _ in 0...10 {
print("XYZ")
}
}
In this case, it doesn't matter if queue
is a serial queue or a concurrent queue: Because you used sync
, it will not reach any of the XYZ
lines until the ABC
lines are all done.
If you want to see concurrent behavior, you must use async
, not sync
. If you want them to run at the same time, you do not want the caller waiting for the first dispatched item to finish before submitting the second one. You want it to submit one work item and not wait before submitting the second.
Consider:
let serialQueue = DispatchQueue(label: "serial")
let concurrentQueue = DispatchQueue(label: "concurrent", attributes: .concurrent)
serialQueue.async {
// A
}
serialQueue.async {
// B
}
concurrentQueue.async {
// C
}
concurrentQueue.async {
// D
}
Here is a graph of those four tasks (each spinning for one second) using the “Points of Interest” tool in the Instruments’ timeline:
Because we used async
rather than sync
, you now can see that the concurrent queue ran C and D concurrently, whereas the serial queue ran A and B sequentially.
While not central to my observations, for the sake of completeness, here is the actual code used above:
import os.log
func gcdExperiment() {
let pointsOfInterest = OSSignposter(subsystem: "log", category: .pointsOfInterest)
let serialQueue = DispatchQueue(label: "serial")
let concurrentQueue = DispatchQueue(label: "concurrent", attributes: .concurrent)
serialQueue.async {
pointsOfInterest.withIntervalSignpost("serial", "A") {
self.spin(for: .seconds(1))
}
}
serialQueue.async {
pointsOfInterest.withIntervalSignpost("serial", "B") {
self.spin(for: .seconds(1))
}
}
concurrentQueue.async {
pointsOfInterest.withIntervalSignpost("concurrent", "C") {
self.spin(for: .seconds(1))
}
}
concurrentQueue.async {
pointsOfInterest.withIntervalSignpost("concurrent", "D") {
self.spin(for: .seconds(1))
}
}
}
func spin(for delay: Duration) {
let start = ContinuousClock.now
while start.duration(to: .now) < delay { }
}
Upvotes: 6
Reputation: 3605
The problem is that you're mixing up the queue types and the execution model.
There are serial and concurrent queues, and you can dispatch tasks to both types synchronously or asynchronously.
A queue can be:
And we can submit a task to a queue:
To sum it up:
Upvotes: 13
Reputation: 824
Actually, It looks like In your code when you are executing the task in concurrent queue (second snippet) you are dispatching the task through sync block, So that will block the current thread as per the sync behaviour.
"Sync : Control will return once all tasks inside the block will be executed.”
let syncConc = DispatchQueue(label:"con",attributes:.concurrent)
syncConc.sync{
for _ in 0...10{
print("XYZ")
}
for _ in 0...10{
print("ABC")
}
}
syncConc.sync{
for _ in 0...10{
print("HHH")
}
for _ in 0...10{
print("XXX")
}
}
So, Here In this case first the suncConc queue will dispatch the first sync block, now since it is blocking call, Next task is not going to be dispatched immediately, It will be dispatched, Once the first will be completed and then It will be dispatched in suncConc queue and again executed with blocking call.
Now, let me come to your query
"Now when I introduce concurrent serial Q, the output is same. So my question is as concurrent queues say that tasks will be done in the same time or concurrently, it isnt happening."
Yes, sync operations can also concurrently perform but It is only possible when you dispatch both the calls immediately without blocking current thread. Check following snippet, The two sync tasks are dispatched from different queue, Hence It will be executed concurrently.
let syncConc = DispatchQueue(label:"con",attributes:.concurrent)
DispatchQueue.global(qos: .utility).async {
syncConc.sync{
for _ in 0...10{
print("XYZ - \(Thread.current)")
}
for _ in 0...10{
print("ABC - \(Thread.current)")
}
}
}
DispatchQueue.global(qos: .userInitiated).async {
syncConc.sync{
for _ in 0...10{
print("HHH - \(Thread.current)")
}
for _ in 0...10{
print("XXX - \(Thread.current)")
}
}
}
Execute the code & See the magic all the theories will be apply as expected :)
Upvotes: 7
Reputation: 17582
to be able to see what is the difference between the serial/concurrent queue and between sync / async method and how they are dispatched to the queue, try to play with next snippet on you Playground.
import PlaygroundSupport
import Dispatch
PlaygroundPage.current.needsIndefiniteExecution = true
let q = DispatchQueue(label: "concurrect queue", qos: .background, attributes: .concurrent)
func runner0(queue: DispatchQueue) {
for _ in 1..<10 {
let result = queue.sync { ()->Int in
usleep(100)
return 0
}
DispatchQueue.main.async {
print(result, "-")
}
}
}
func runner1(queue: DispatchQueue) {
for _ in 1..<10 {
let result = queue.sync { ()->Int in
usleep(100)
return 1
}
DispatchQueue.main.async {
print("-", result)
}
}
}
let arr = [runner0, runner1]
DispatchQueue.concurrentPerform(iterations: 2) { (i) in
arr[i](q)
}
We have one concurrent queue, where two different runners are dispatching tasks synchronously. As you can see, all the task in our concurrent queue are executed concurrently, the result is then printed serially (asynchronously) on the serial queue.
I hope, this helps you to see how it works.
My Playground prints
- 1
0 -
- 1
0 -
0 -
- 1
0 -
- 1
0 -
- 1
0 -
- 1
0 -
0 -
- 1
0 -
- 1
- 1
let's make the queue to be serial by changing its definition
let q = DispatchQueue(label: "serial queue")
and compare what we have as a result
0 -
- 1
0 -
- 1
0 -
- 1
0 -
- 1
0 -
- 1
0 -
- 1
0 -
- 1
0 -
- 1
0 -
- 1
Upvotes: 0