简体   繁体   中英

Swift weird behavior with multithreading dispatch queues

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()

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 eg https://www.marcosantadev.com/capturing-values-swift-closures/

There are 2 issues with your code:

  1. Int type is not thread-safe. Meaning that if one thread changes the value of var index , other threads do't have to see that change. Instead, you need to either use thread-safe type (eg from Swift Atomics ), or use some other mechanism that allows thread-safe reading / writing of the values

  2. Generally, 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM