Suppose I have a class like this:
class Bla {
var callback: (() -> Void)?
// doStuff will be called from multiple threads
func doStuff() {
callback?()
}
}
Is it thread safe to call a closure from multiple threads? The closure will not be modified during this time, only called.
Inside the closure is thread safe code.
Unfortunately thread safety isn't a straight forward question. Specifically though the read of the block is safe from multiple threads.
In swift closures are passed by reference. When you assign it to "var callback" it receives a memory address in the heap. That address can be read by multiple threads at once as long as the variable is not changing at the time of read access by some other thread. Even if you captured a variable in the block, that variable would be captured by the block and if it wasn't a reference type could even be modified by the closure. You definitely want to be careful what you do inside the calling site though. If for instance the closure is called by multiple threads and inside you passed say..
var i = 0
callback = {
i += 1
}
for _ in 0...100 {
DispatchQueue.global().async {
bla.closure()
}
}
This would obviously no long be considered "thread safe" because you are still mutating by multiple threads. For example two or three threads may read 1 and add 1, another twelve threads may read 5 and write 6 on return; or worse the second thread finished last and after 100 calls you have i = 2.
Additionally, if "var i" were a reference type the program would likely crash on a bad access exception.
Related
Based on my printed output in the console window, the work in que2 was only executed after the que1 fully finished its work, so my question is why did I get the Data race warning even though the first block of work in que1 was completely synchronous?
Data race in closure #2 () -> () in BlankSwift at BlankSwift.porsche : BlankSwift.Car
struct Car {
var name: String
}
let que1 = DispatchQueue(label: "que1", qos: .background)
let que2 = DispatchQueue(label: "que2", qos: .userInteractive)
var porsche = Car(name: "Porsche")
for i in 0...100 {
que1.sync {
porsche.name = "porsche1"
print(porsche.name)
porsche.name = "Porsche11"
print(porsche.name)
if i == 100 { print("returned ")}
}
que2.async {
porsche.name = "porsche2"
print(porsche.name)
porsche.name = "Porsche22"
print(porsche.name)
}
}
While que1.sync is indeed called synchronously, que2.async is asynchronous on a different queue, so it schedules its closure and immediately returns, at which point you go to the next iteration of the loop.
There is some latency before the que2 closure begins executing. So for example, the closure for que2.async that was scheduled for iteration 0, is likely to start executing while que1.sync is executing for some later iteration, let's say iteration 10.
Not only that, que2 may well have multiple tasks queued up before the first one begins. It's a serial queue, because you didn't specify the .concurrent attribute, so you don't have to worry about que2 tasks racing on another que2 closure access of porsche.name , but they definitely can race on que1 closure accesses.
As for output ordering, ultimately the output will go to FileHandle.standardOutput to which the OS has a buffer attached, and you don't know what kind of synchronization scheme the OS uses to order writes to that buffer. It may well use it's own call to DispatchQueue.async to ensure that I/O is done in a sensible way, much the way UI updates on AppKit/UIKit have to be done on the main thread.
I was doing some research on mutexes and came across the following Swift code:
class Lock {
private var mutex: pthread_mutex_t = {
var mutex = pthread_mutex_t()
pthread_mutex_init(&mutex, nil)
return mutex
}()
func someFunc() {
pthread_mutex_lock(&mutex)
defer { pthread_mutex_unlock(&mutex) }
...
}
}
The code defines and initializes a pthread_mutex_t within the closure, then assigns the returned value to a class property. It then lock and unlocks within several of the functions as shown.
Since one should also call pthread_mutex_destroy, it implies that some sort of allocation is occurring within the mutex which may or may not reference the address of the original value.
In effect, the mutex is initialized in one place and stored in another.
The question is whether or not it's safe or correct to do this?
What if the mutex initializer needed arguments?
private var mutex: pthread_mutex_t = {
var recursiveMutex = pthread_mutex_t()
var recursiveMutexAttr = pthread_mutexattr_t()
pthread_mutexattr_init(&recursiveMutexAttr)
pthread_mutexattr_settype(&recursiveMutexAttr, PTHREAD_MUTEX_RECURSIVE)
pthread_mutex_init(&recursiveMutex, &recursiveMutexAttr)
return recursiveMutex
}()
The later strikes me as definitely being incorrect, as the attribute storage whose address is passed into the mutex will disappear when the closure collapses.
It's not, this code is broken.
To work, the pthread_mutex_t would need to be initialized in-place within a class instance, and never copied out. The class would need to expose lock/unlock methods which operator on the instance-variable in-place.
Value Types
Note that pthread_mutex_t, pthread_rwlock_t, and os_unfair_lock are value types, not reference types. That means that if you use = on them, you make a copy. This is important, because these types can't be copied! If you copy one of the pthread types, the copy will be unusable and may crash when you try to use it.
– By Mike Ash, Friday Q&A 2017-10-27: Locks, Thread Safety, and Swift: 2017 Edition
Check out https://cocoawithlove.com/blog/2016/06/02/threads-and-mutexes.html
And this example usage of pthread_mutex_t in Swift: https://github.com/mattgallagher/CwlUtils/blob/0bfc4587d01cfc796b6c7e118fc631333dd8ab33/Sources/CwlUtils/CwlMutex.swift#L60-L105
Suppose I have a Swift class like this:
#objc final MyClass : NSObject
{
let classPropertyString = "A class property"
func doStuff()
{
let localString = "An object local to this function"
DispatchQueue.global(qos: .userInitiated).async { [classPropertyString] in
// Do things with 'classPropertyString' and 'localString'
}
}
}
My question is: when I write a capture list, am I responsible for EXHAUSTIVELY listing all the things to which I want the closure to hold a strong reference?
In other words, if I omit localString from my capture list (as I've done here), will the closure still automatically capture a strong reference to it or am I in for a bad time?
There are several minor quirks with your question that make it tricky to answer clearly, but I think I understand the underlying concern, and the short answer is "no." But your example is impossible, so the answer is "it's impossible." And if it were possible, there'd be no strong reference (nor would there be a need for one), so the question still would be kind of "it's impossible." Even so, let's walk through what's going on here.
First, closure can't reference localString unless it's reassigned somehow in the comment inside doStuff(). closure is assigned at a level where localString is not in scope. Closures can only capture variables that are in scope when they are assigned, not when they're called. But let's go back to the original version of this question, before it was edited. That version did have the case you're describing:
#objc final myClass : NSObject
{
let classPropertyString = "A class property"
func doStuff()
{
let localString = "An object local to this function"
DispatchQueue.global(qos: .userInitiated).async { [classPropertyString] in // (1)
// Do things with 'classPropertyString' and 'localString'
}
// (2)
}
}
There's no problems here. classPropertyString is copied into the closure, avoiding any retain loops. localString is referenced by the closure, and so it's preserved as long as the closure exists.
Because you listed classPropertyString in the capture list, it is evaluated at point (1) and copied into the closure. Because you implicitly captured localString, it is treated as a reference. See Capture Lists in the Swift Programming Language Reference for some excellent examples of exactly how this works in different cases.
In no case (*) will Swift allow the underlying storage for something you're using in a closure to disappear behind your back. That's why the typical concern is excessive retains (memory leaks) rather than dangling references (crashes).
(*) "In no case" here is a lie. There are several ways that Swift will allow it, but almost all of them involve "Unsafe" which is your warning about that. The major exception is unowned, and of course anything involving ! types. And Swift is not typically thread-safe, so you need to be careful about that...
The last comment about thread-safety is a place where the subtle distinctions between implicit and explicit captures can really matter. Consider this case where you modify an implicitly captured value on two queues:
func doStuff() -> String
{
var localString = "An object local to this function"
DispatchQueue.global(qos: .userInitiated).async {
localString = "something else"
callFunction(localString)
}
localString = "even more changes"
return localString
}
What happens in that case? Good grief, never do that. I believe it's undefined behavior and that localString could be anything including corrupted memory, at least in the most general case (it might be defined behavior for calling .async; I'm not sure). But don't do it.
But for your normal cases, there is no reason to explicitly capture local variables. (I sometimes wish Swift had gone the C++ way and said it was required, but it isn't.)
Ok, one more way implicit and explicit are different that might drive home how they work. Consider a stateful closure like this (I build these pretty often):
func incrementor() -> () -> Int {
var n = 0
return {
n += 1
return n
}
}
let inc = incrementor()
inc() // 1
inc() // 2
inc() // 3
let inc2 = incrementor()
inc2() // 1
See how the local variable n is captured by the closure, and can be modified after it goes out of scope. And see how inc2 has its own version of that local variable. Now try that with explicit capture.
func incrementor() -> () -> Int {
var n = 0
return { [n] in // <---- add [n]
n += 1 // Left side of mutating operator isn't mutable: 'n' is an immutable capture
return n
}
}
Explicit captures are copies and they're immutable. Implicit captures are references, and so have the same mutability as the thing they reference.
Let's say I am trying to access a shared variable between two threads. One thread will continuously set the shared variable to either nil or to the reference of an object that can be deallocated.
Class Code
class ConcurrentPrinter {
var value: AnyObject?
}
Thread one
// called 30 times per second
func setter(){
value = shouldSet ? nil : valueArray[0]
// where the value is an instance type
}
Thread two
// also called 30 times per second
func getter() {
if value != nil {
guard let desiredObject = value as? desiredObjectType else {
return
}
}
For some reason, I am getting a Bad_Address error in the guard statement when it tries to cast value into the desiredObjectType. Is this happening because the cast operation gets the address of value and then it gets deallocated before it can finish the cast operation?
Okay, I figured it out. The answer is to place each of the operations on a DispatchQueue and run each of the code using an async request. This ensures that the two pieces of code are running simultaneously
I'm trying to create a retrying mechanism for our network calls. I have created 2 classes. One a retry Class and another a Manager in case I wanted I can cancel all classes.
class Retry {
var url: String
var maxRetries: Int
init (url: String, retryCount: Int){
self.url = url
self.maxRetries = maxRetries
poll()
RetryManager.shared.add(self)
}
private func poll(){
guard retryCount == 0 else{
print("error")
}
getRequest()
}
private func getRequest(){
// make network request
// if no response is received poll again
}
func cancel(){
maxRetries = 0
}
}
Manager class
class RetryManager{
static let sharedInstance = RetryManager()
var retries : [Retry?] = []
private init(){
}
func register(retry: Retry){
retries.append(retry)
}
func remove(retry: Retry){
retry.cancel() // XX; Do I also need this or just removing it is fine?
retries = retries.filter({$0 !== retry})
}
func cancelAll(){
retries.forEach({$0?.cancel()}) // ZZ; Do I also need this? or just removing them is fine?
retries.removeAll()
}
}
My Retry instances are to be used for making network calls.
My major question is about my cancel mechanism. Will doing a RetryManager.shared.cancelAll() suffice for deallocation? Or I need to run cancel or each cancel instance (ie XX, ZZ are also necessary)?
Currently everything works fine, but I'm not sure if I how it would work if I have multiple pointers...would I need to do:
for index..<retries.count{
retries[index] = nil
}
As far as I understand that won't help, it's same as doing retries.removeAll()
I also read In Swift, how do I set every element inside an array to nil? question but was told to open a new question
Not sure if I can answer your question, but I will try with my best understanding :).
Apple's Swift handbook on Automatic Reference Counting (ARC) covers your question very well.
Usually you don't need to have an array of optionals,
var retries = [Retry]()
...
retries.removeAll()
will nicely remove all containing objects and delete the references to these objects. From your context presented above I do not understand why you need to declare an array of optionals. As you know Swift optionals under the hood are just a typed wrapper class Optional<Type>, which doesn't work around the memory allocation problem.
How does array reference objects?
The array will increase the contained objects' reference count by one, that is, a strong reference.
To ensure the objects in the array to be deallocated, one must make their reference count equal zero. Removing them from the array will do the trick, if nothing else is referencing the contained objects.
Beware of the reference cycle though. In your case, you should not hold reference to the retries array in Retry instance. Otherwise even if you set the retries array to nil, the array and its contained objects still have strong reference to each other, meaning their reference counts will never reduce to zero, causing memory leak.