I am trying to use dispatch queues in swift, but some weired beheviour appeared when calling the async function. In the following code I called the function "fun" 10 times each time with the .async function, and as I know from other languages that it will push the function call with it parameters to the queue to be executed at some time, but the output was most of the time printing 10 on all calls, meaning that the value parameter that was given to the function was took when the function start executing, so it took its final value, which is not logical for me.
import Foundation
let queue = DispatchQueue(label: "swiftlee.concurrent.queue", attributes: .concurrent)
let lock = NSLock()
func fun(_ idx: Int) {
print(idx)
}
func calc() {
var index = 0
let dispatchGroup = DispatchGroup()
while index < 10{
dispatchGroup.enter()
queue.async {
fun(index)
dispatchGroup.leave()
}
index = 1
}
dispatchGroup.wait()
}
calc()
If I change the function parameter to an assigned local variable in the loop as in the next code it works fine and prints all the numbers from 0 to 9, but if we want to apply the same logic as the previous one, when the function executes how did it find the value of the variable x, while it was gone on the next iterations.
import Foundation
let queue = DispatchQueue(label: "swiftlee.concurrent.queue", attributes: .concurrent)
let lock = NSLock()
func fun(_ idx: Int) {
print(idx)
}
func calc() {
var index = 0
let dispatchGroup = DispatchGroup()
while index < 10{
dispatchGroup.enter()
let x = index
queue.async {
fun(x)
dispatchGroup.leave()
}
index = 1
}
dispatchGroup.wait()
}
calc()
CodePudding user response:
This is not strange or weired, but intended. A closure is capturing its surrounding variables by reference, even if they are value types. What happens is:
- Before the first block is executed, multiple closures are created and added by
queue.async
- Each closure references the same
index
variable. - When (after some milliseconds) the closures are executed in the queue, the then-valid
index
value is outputted
If you do not want this behaviour, you could either
- copy the
index
value to a local variable outside the closure and use that or - use a capture clause like:
queue.async {
[index] in
fun(index)
dispatchGroup.leave()
}
See e.g. https://www.marcosantadev.com/capturing-values-swift-closures/
CodePudding user response:
There are 2 issues with your code:
Int
type is not thread-safe. Meaning that if one thread changes the value ofvar index
, other threads do't have to see that change. Instead, you need to either use thread-safe type (e.g. from Swift Atomics), or use some other mechanism that allows thread-safe reading / writing of the valuesGenerally, usage of
DispatchGroup
is something to avoid, and there are very few cases when it's needed. There are many better tools nowadays to achieve the same thing.
In your case I would just use concurrentPerform instead, which solves both problems at once:
func calc() {
DispatchQueue.concurrentPerform(iterations: 10) { index in
fun(index)
}
}
Since you just call fun(index)
10 times, you don't need index
as a variable in function scope, and hence don't need to worry about index
being thread safe. The iteration-scope variable index
will be correct on each iteration.
Note that while it's guaranteed that fun(index)
will be executed 10 times, exactly once for each value for index
from 0 to 9, the order of execution is not guaranteed. But it's not guaranteed in your original code either, so it's all good.