Safely locking variable in Swift 3 using GCD - swift

How to lock variable and prevent from different thread changing it at the same time, which leads to error?
I tried using
func lock(obj: AnyObject, blk:() -> ()) {
objc_sync_enter(obj)
blk()
objc_sync_exit(obj)
}
but i still have multithreading issues.

Shared Value
If you have a shared value that you want to access in a thread safe way like this
var list:[Int] = []
DispatchQueue
You can create your own serial DispatchQueue.
let serialQueue = DispatchQueue(label: "SerialQueue")
Dispatch Synch
Now different threads can safely access list, you just need to write the code into a closure dispatched to your serial queue.
serialQueue.sync {
// update list <---
}
// This line will always run AFTER the closure on the previous line 👆👆👆
Since the serial queue executes the closures one at the time, the access to list will be safe.
Please note that the previous code will block the current thread until the closure is executed.
Dispatch Asynch
If you don't want to block the current thread until the closure is processed by the serial queue, you can dispatch the closure asynchronously
serialQueue.async {
// update list <---
}
// This line can run BEFORE the closure on the previous line 👆👆👆

Swift's concurrency support isn't there yet. It sounds like it might be developed a bit in Swift 5. An excellent article is Matt Gallagher's Mutexes and Closure Capture in Swift, which looks at various solutions but recommends pthread_mutex_t. The choice of approach depends on other aspects of what you're writing - there's much to consider with threading.
Could you provide a specific simple example that's failing you?

Related

The callback inside Task is automatically called on the main thread

Once upon the time, before Async/Await came, we use to make a simple request to the server with URLSession dataTask. The callback being not automatically called on the main thread and we had to dispatch manually to the main thread in order to perform some UI work. Example:
DispatchQueue.main.async {
// UI work
}
Omitting this will lead to the app to crash since we try to update the UI on different queue than the main one.
Now with Async/Await things got easier. We still have to dispatch to the main queue using MainActor.
await MainActor.run {
// UI work
}
The weird thing is that even when I don't use the MainActor the code inside my Task seems to run on the main thread and updating the UI seems to be safe.
Task {
let api = API(apiConfig: apiConfig)
do {
let posts = try await api.getPosts() // Checked this and the code of getPosts is running on another thread.
self.posts = posts
self.tableView.reloadData()
print(Thread.current.description)
} catch {
// Handle error
}
}
I was expecting my code to lead to crash since I am trying to update the table view theorically not from the main thread but the log says I am on the main thread. The print logs the following:
<_NSMainThread: 0x600003bb02c0>{number = 1, name = main}
Does this mean there is no need to check which queue we are in before performing UI stuff?
Regarding Task {…}, that will “create an unstructured task that runs on the current actor” (see Swift Concurrency: Unstructured Concurrency). That is a great way to launch an asynchronous task from a synchronous context. And, if called from the main actor, this Task will also be on the main actor.
In your case, I would move the model update and UI refresh to a function that is marked as running on the main actor:
#MainActor
func update(with posts: [Post]) async {
self.posts = posts
tableView.reloadData()
}
Then you can do:
Task {
let api = API(apiConfig: apiConfig)
do {
let posts = try await api.getPosts() // Checked this and the code of getPosts is running on another thread.
self.update(with: posts)
} catch {
// Handle error
}
}
And the beauty of it is that if you’re not already on the main actor, the compiler will tell you that you have to await the update method. The compiler will tell you whether you need to await or not.
If you haven’t seen it, I might suggest watching WWDC 2021 video Swift concurrency: Update a sample app. It offers lots of practical tips about converting code to Swift concurrency, but specifically at 24:16 they walk through the evolution from DispatchQueue.main.async {…} to Swift concurrency (e.g., initially suggesting the intuitive MainActor.run {…} step, but over the next few minutes, show why even that is unnecessary, but also discuss the rare scenario where you might want to use this function).
As an aside, in Swift concurrency, looking at Thread.current is not reliable. Because of this, this practice is likely going to be prohibited in a future compiler release.
If you watch WWDC 2021 Swift concurrency: Behind the scenes, you will get a glimpse of the sorts of mechanisms underpinning Swift concurrency and you will better understand why looking at Thread.current might lead to all sorts of incorrect conclusions.

Swift Concurrency : Why Task is not executed on other background thread

I am trying to learn the swift concurrency but it brings in a lot of confusion. I understood that a Task {} is an asynchronous unit and will allow us to bridge the async function call from a synchronous context. And it is similar to DispatchQueue.Global() which in turn will execute the block on some arbitrary thread.
override func viewDidLoad() {
super.viewDidLoad()
Task {
do {
let data = try await asychronousApiCall()
print(data)
} catch {
print("Request failed with error: \(error)")
}
}
for i in 1...30000 {
print("Thread \(Thread.current)")
}
}
my asychronousApiCall function is below
func asychronousApiCall() async throws -> Data {
print("starting with asychronousApiCall")
print("Thread \(Thread.current)")
let url = URL(string: "https://www.stackoverflow.com")!
// Use the async variant of URLSession to fetch data
// Code might suspend here
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
When I try this implementation. I always see that "starting with asychronousApiCall" is printed after the for loop is done and the thread is MainThread.
like this
Thread <_NSMainThread: 0x600000f10500>{number = 1, name = main}
You said:
I understood that a Task {} is an asynchronous unit and will allow us to bridge the async function call from a synchronous context.
Yes.
You continue:
And it is similar to DispatchQueue.global() which in turn will execute the block on some arbitrary thread.
No, if you call it from the main actor, it is more akin to DispatchQueue.main.async { … }. As the documentation says, it “[r]uns the given nonthrowing operation asynchronously as part of a new top-level task on behalf of the current actor” [emphasis added]. I.e., if you are currently on the main actor, the task will be run on behalf of the main actor, too.
While it is a probably mistake to dwell on direct GCD-to-concurrency mappings, Task.detached { … } is more comparable to DispatchQueue.global().async { … }.
You commented:
Please scroll to figure 8 in last of the article. It has a normal task and Thread is print is some other thread.
figure 8
In that screen snapshot, they are showing that prior to the suspension point (i.e., before the await) it was on the main thread (which makes sense, because it is running it on behalf of the same actor). But they are also highlighting that after the suspension point, it was on another thread (which might seem counterintuitive, but it is what can happen after a suspension point). This is very common behavior in Swift concurrency, though it can vary.
FWIW, in your example above, you only examine the thread before the suspension point and not after. The take-home message of figure 8 is that the thread used after the suspension point may not be the same one used before the suspension point.
If you are interested in learning more about some of these implementation details, I might suggest watching WWDC 2021 video Swift concurrency: Behind the scenes.
While it is interesting to look at Thread.current, it should be noted that Apple is trying to wean us off of this practice. E.g., in Swift 5.7, if we look at Thread.current from an asynchronous context, we get a warning:
Class property 'current' is unavailable from asynchronous contexts; Thread.current cannot be used from async contexts.; this is an error in Swift 6
The whole idea of Swift concurrency is that we stop thinking in terms of threads and we instead let Swift concurrency choose the appropriate thread on our behalf (which cleverly avoids costly context switches where it can; sometimes resulting code that runs on threads other than what we might otherwise expect).

Swift: DispatchQueue always Serial?

I receive a lot of data and want to process it off the main thread. Most of the things can be asynchronous in nature, however sometimes there is critical data coming in with each update invalidating the previous iteration. At the moment I set up everything like this:
func onReceivedData(d: Data){ //is on main thread unfortunately
//some work 1
DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).async {
//some work 2
if(saveToFile) {
DispatchQueue.global(qos: DispatchQoS.QoSClass.utility).async {
//UserDefaults write
}
}
}
}
Now I am not sure if I understood the DispatchQueue correctly. Is it possible when data A comes in before data B, that data B for some reason may reach the UserDefaults write earlier than data A? Do the DispatchQueues push operations in a serial manner or can they also operate in parallel, potentially writing data B before data A, leaving the UserDefaults in an invalid state?
From the Apple Developer documentation on DispatchQueue.global():
Tasks submitted to the returned queue are scheduled concurrently with respect to one another.
This means that the global() queues are concurrent dispatch queues.
If you want to ensure that the tasks execute in the order that you schedule them, you should create your own serial DispatchQueue.
let queue = DispatchQueue(label: "my-queue")
queue.async {
// Work 1...
}
queue.async {
// Work 2...
}
"Work 1" will always run before "Work 2" in this serial queue (which may not be the case with a concurrent queue).
Alternatively, you could look into using a Swift actor which can more efficiently control access to shared mutable state in a memory-safe way.

How does retain count with synchronous dispatch work?

I'm trying to explain ownership of objects and how GCD does its work.
These are the things I've learned:
a function will increase the retain count of the object its calling against
a dispatch block, unless it captures self weakly will increase the count.
after a dispatched block is executed it release the captured object, hence the retain count of self should decrease. But that's not what I'm seeing here. Why is that?
class C {
var name = "Adam"
func foo () {
print("inside func before sync", CFGetRetainCount(self)) // 3
DispatchQueue.global().sync {
print("inside func inside sync", CFGetRetainCount(self)) // 4
}
sleep(2)
print("inside func after sync", CFGetRetainCount(self)) // 4 ?????? I thought this would go back to 3
}
}
Usage:
var c: C? = C()
print("before func call", CFGetRetainCount(c)) // 2
c?.foo()
print("after func call", CFGetRetainCount(c)) // 2
A couple of thoughts:
If you ever have questions about precisely where ARC is retaining and releasing behind the scenes, just add breakpoint after “inside func after sync”, run it, and when it stops use “Debug” » “Debug Workflow” » “Always Show Disassembly” and you can see the assembly, to precisely see what’s going on. I’d also suggest doing this with release/optimized builds.
Looking at the assembly, the releases are at the end of your foo method.
As you pointed out, if you change your DispatchQueue.global().sync call to be async, you see the behavior you’d expect.
Also, unsurprisingly, if you perform functional decomposition, moving the GCD sync call into a separate function, you’ll again see the behavior you were expecting.
You said:
a function will increase the retain count of the object its calling against
Just to clarify what’s going on, I’d refer you to WWDC 2018 What’s New in Swift, about 12:43 into the video, in which they discuss where the compiler inserts the retain and release calls, and how it changed in Swift 4.2.
In Swift 4.1, it used the “Owned” calling convention where the caller would retain the object before calling the function, and the called function was responsible for performing the release before returning.
In 4.2 (shown in the WWDC screen snapshot below), they implemented a “Guaranteed” calling convention, eliminating a lot of redundant retain and release calls:
This results, in optimized builds at least, in more efficient and more compact code. So, do a release build and look at the assembly, and you’ll see that in action.
Now, we come to the root of your question, as to why the GCD sync function behaves differently than other scenarios (e.g. where its release call is inserted in a different place than other scenarios with non-escaping closures).
It seems that this is potentially related to optimizations unique to GCD sync. Specifically, when you dispatch synchronously to some background queue, rather than stopping the current thread and then running the code on one of the worker threads of the designated queue, the compiler is smart enough to determine that the current thread would be idle and it will just run the dispatched code on the current thread if it can. I can easily imagine that this GCD sync optimization, might have introduced wrinkles in the logic about where the compiler inserted the release call.
IMHO, the fact that the release is done at the end of the method as opposed to at the end of the closure is a somewhat academic matter. I’m assuming they had good reasons (or practical reasons at least), to defer this to the end of the function. What’s important is that when you return from foo, the retain count is what it should be.

Is DispatchSemaphore a good replacement for NSLock?

I've been using NSLocks to synchronize touchy parts of code, but have been running into issues due to the fact that they must be unlocked from the same thread that they were locked from. Then I found that GCD's DispatchSemaphores seem to do the same thing, with the added convenience that they can be signaled from any thread. I was wondering, though, if this convenience comes at the price of thread-safety. Is it advisable to replace
let lock = NSLock()
lock.lock()
// do things...
lock.unlock()
with
let semaphore = DispatchSemaphore(value: 1)
semaphore.wait()
// do things...
semaphore.signal()
or will I run into issues regarding thread-safety anyway?
Yes they have the same function, both to deal with producer-consumer problem.
Semaphore allows more than one thread to access a shared resource if it is configured accordingly. You can make the execution of the blocks in the same concurrent dispatchQueue.
{
semaphore.wait()
// do things...
semaphore.signal()
}
Actually the same applies to Lock, if you only want one thread to touch the resource at one time, in the concurrent way.
I found this to be helpful: https://priteshrnandgaonkar.github.io/concurrency-with-swift-3/
Since asking this, I have mostly switched over to another way of locking blocks of code: serial dispatch queues. I use it like this:
let queue = DispatchQueue(label: "<your label here>")
queue.async {
// do things...
}
The queue is serial by default, meaning it acts as a lock that releases when the block is exited. Therefore, it's not appropriate if you need to lock on an asynchronous operation, but it works a charm in most cases.