swiftmultithreadingasynchronousconcurrency

Variables do not add up in swift multi thread loop


I’m trying to understand swift sync/async with the below snippet, but the variables did not add, and the iteration times of the loop varied every time. The outputs were different too.

var mainAsync: Int = 1
var queueAsync: Int = 10
var queueAsyncTwo: Int = 20
var mainAsyncTwo: Int  = 30
let queue = DispatchQueue(label:"com")

DispatchQueue.main.async {
    for i in 0..<10 { mainAsync += 1 }
}

queue.async {
    for i in 0..<10 {
        queueAsync += 1
    }
}

queue.async {
    for i in 0..<10 {
        queueAsyncTwo += 1
    }
}

for i in 0..<10 {
    mainAsyncTwo += 1
}

print("\(mainAsync),-- \(queueAsync),-- \(queueAsyncTwo),--\(mainAsyncTwo)")

The expected outputs:

10,-- 20,-- 30,-- 40

The real outputs varied from time to time like:

1,-- 19,-- 20,--40
1,-- 20,-- 22,--40
1,-- 20,-- 25,--40

I removed the print(thread.current). It wasn’t working to represent the loop times.


Solution

  • These are asynchronous operations - they might not have been completed by the time print is called at the final line. This is a common mistake. For example, the code in this question sends a request to some URL, which is an asynchronous operation. Similar to your code, the code in that question does not wait for the response to be received before printing the contents of the response, and so nothing gets printed.

    In particular, the next line after the queue.async { ... } call is executed immediately after the job is submitted to the queue, not after the job has been completed. Compare this to queue.sync { ... } which will only return after the job has been completed.

    As for the job you scheduled on DispatchQueue.main, it is never executed. This is because the main queue is special - it will not execute anything you submit to it unless you do one of the following (from the documentation):

    • Calling dispatchMain()
    • Starting your app with a call to UIApplicationMain(_:_:_:_:) (iOS) or NSApplicationMain(_:_:) (macOS)
    • Using a CFRunLoop on the main thread

    You did not do any of these things.

    Note that the loop that increments mainAsyncTwo is not a job submitted to the main queue. It is just run in the main thread.

    You can do the third bullet point above - run the main RunLoop for some time before you print:

    ...
    
    RunLoop.main.run(until: .now.addingTimeInterval(1))
    
    print("\(mainAsync),-- \(queueAsync),-- \(queueAsyncTwo),--\(mainAsyncTwo)")
    

    This not only causes the main queue to execute its jobs, and also gives enough time for all the queues to finish everything you submitted to them.

    Now this prints 11,-- 20,-- 30,--40.