Swift - Is setting variable property concurrency or multithreading safe? - ios

As I know setting a variable property when using concurrency or multithreading is not safe but I can't produce a crash with below code.
class Node {
var data = 0
}
var node = Node()
let concurrentQueue = DispatchQueue(label: "queue", attributes: .concurrent)
for i in 0...1000 {
concurrentQueue.async {
node.data = i // Should get crash at this line
}
}
UPDATE1
Thanks #MartinR for pointing out in his comment.
Enable the “Thread Sanitizer” and it'll report an error immediately.
UPDATE2
The code got EXC_BAD_ACCESS KERN_INVALID_ADDRESS crash if changing data to reference type. It doesn't always happen but sometimes it will. For example:
class Data {}
class Node {
var data = Data() // Use reference type instead of value type
}
var node = Node()
let concurrentQueue = DispatchQueue(label: "queue", attributes: .concurrent)
for i in 0...1000 {
concurrentQueue.async {
node.data = Data() // EXC_BAD_ACCESS KERN_INVALID_ADDRESS
}
}
This behavior also happens in Objective-C. Setting object property concurrently will cause crash. But with primitive type, the crash will not happen.
Questions
Does setting value type property concurrently will produce a crash?
If it doesn't produce a crash, what is the difference between setting value type property and setting reference type property?
It's perfect if anyone can also explain why setting reference type property concurrently will produce a crash.

First off
Class value are stored in heap memory while struct/enum value are stored in stack memory and compiler will try to allocate those memory at compile time (according to my first ref and many online answer). You can check using this code:
class MemTest {}
class Node {
var data = MemTest()
}
let node = Node()
let concurrentQueue = DispatchQueue(label: "queue", attributes: .concurrent)
for index in 0...100000 {
concurrentQueue.async {
node.data = MemTest()
withUnsafePointer(to: &node.data) {
print("Node data # \($0)")
}
withUnsafePointer(to: node.data) {
print("Node data value no. \(index) # \($0)")
}
}
How to: run 2 time and check memory address for value changed time 500, switch MemTest between class and struct will show the difference. Struct will show the same while class will show different address between each time.
So changing value type is just like changing address but the the memory blocks will not be restored while changing reference type is not just changing address but also restore and allocate new memory blocks which will cause the program to break.
Secondly
But if running first code block of #trungduc with withUnsafePointer, it will show us that the index var of for loop is allocated on the go and in the heap memory, so why is that?
As mentioned before, compiler will only try to allocate the mem if the value type is calculable at compile time. If the they are not calculable, the value will be allocated in heap memory and stay there till the end of scope (according to my second ref). So may be the explanation here is the system will restored the allocated stack after everything is done - end of scope (This I'm not very sure). So in this case the code will not produce a crash as we know.
My conclusion, the reference type variable's mem will be allocated and restored with no restriction in this case whereas value type variable's mem will be allocated and removed only after the system enter and exit a the scope which contains said variable
Thanks
#Gokhan Topcu for Check section "Memory Management"
#Bruno Rocha for Check section "Heap Allocated Value Types"
Some words
My answer is not very solid and might have lots of grammar and spelling error. All update are appreciated. Thanks in advance
Update
For the part I'm not very sure:
variable index was copied to a new memory address with operator = so it doesn't matter where the scope end, stack will be released after for loop
After some digging, in #trungduc's code, with reference type variable, it will do 3 things:
Allocate new memory for class Data
Reduce reference to old Data stored in node.data, even free old Data if it's no longer referenced
Point node.data to new Data
While for value type it will do 1 thing only:
Point node.data to Integer in stack memory
The major difference is in step 2 where there is a chance the old Data memory is restored
There are possibilities where this scenario will happen with reference type
________Task 1________|________Task 2________
Allocate new Data #1 |
|Allocate new Data #2
Load pointer to old |
Data |
Reduce reference count|
to old Data |
|Load pointer to old
|Data
Free old Data |
|Reduce reference count
|to old Data (!)
|Free old Data (!)
Reference new Data #1 |
|Reference new Data #2
while with value type, this will happen
________Task 1________|________Task 2________
Reference to Integer 1|
|Reference to Integer 2
In the first case we will have various alternative scenarios but in most case, we get a segmentation fault because thread 2 tries to dereference that pointer after thread 1 free it. There might be other issues like memory leaking as we notice, thread 2 might not reduce reference count to Data #1 correctly
Whereas in second case, it's just changing the pointer.
Note
In second case, it will never cause crash on Intel CPU but not guaranteed on other CPUs as well because many CPUs do not promise that doing this will not cause a crash.

Non-atomic doesn't mean that app will crash if multiple threads are using the shared resource.
Atomic Properties
Defining a property as atomic will guarantee that a valid value will be returned. Notice that valid does not always mean correct.
This also does not mean that atomic properties are thread safe. Different threads can attempt to write and read a the same time. One of two values will be returned — the value before the change or the value of the change
Non-Atomic Properties
Non atomic properties has no guarantee regarding the returned value. It can be the correct value, a partially written value or even some garbage value.
It simply means that the final value will not be consistent. You won't know which thread will update the value last.
You can refer the link for more clarification on this.

Related

Understanding Swift thread safety

I have encountered a data race in my app using Xcode's Thread Sanitizer and I have a question on how to address it.
I have a var defined as:
var myDict = [Double : [Date:[String:Any]]]()
I have a thread setup where I call a setup() function:
let queue = DispatchQueue(label: "my-queue", qos: .utility)
queue.async {
self.setup {
}
}
My setup() function essentially loops through tons of data and populates myDict. This can take a while, which is why we need to do it asynchronously.
On the main thread, my UI accesses myDict to display its data. In a cellForRow: method:
if !myDict.keys.contains(someObject) {
//Do something
}
And that is where I get my data race alert and the subsequent crash.
Exception NSException * "-[_NSCoreDataTaggedObjectID objectForKey:]:
unrecognized selector sent to instance
0x8000000000000000" 0x0000000283df6a60
Please kindly help me understand how to access a variable in a thread safe manner in Swift. I feel like I'm possibly half way there with setting, but I'm confused on how to approach getting on the main thread.
One way to access it asynchronously:
typealias Dict = [Double : [Date:[String:Any]]]
var myDict = Dict()
func getMyDict(f: #escaping (Dict) -> ()) {
queue.async {
DispatchQueue.main.async {
f(myDict)
}
}
}
getMyDict { dict in
assert(Thread.isMainThread)
}
Making the assumption, that queue possibly schedules long lasting closures.
How it works?
You can only access myDict from within queue. In the above function, myDict will be accessed on this queue, and a copy of it gets imported to the main queue. While you are showing the copy of myDict in a UI, you can simultaneously mutate the original myDict. "Copy on write" semantics on Dictionary ensures that copies are cheap.
You can call getMyDict from any thread and it will always call the closure on the main thread (in this implementation).
Caveat:
getMyDict is an async function. Which shouldn't be a caveat at all nowadays, but I just want to emphasise this ;)
Alternatives:
Swift Combine. Make myDict a published Value from some Publisher which implements your logic.
later, you may also consider to use async & await when it is available.
Preface: This will be a pretty long non-answer. I don't actually know what's wrong with your code, but I can share the things I do know that can help you troubleshoot it, and learn some interesting things along the way.
Understanding the error
Exception NSException * "-[_NSCoreDataTaggedObjectID objectForKey:]: unrecognized selector sent to instance 0x8000000000000000"
An Objective C exception was thrown (and not caught).
The exception happened when attempting to invoke -[_NSCoreDataTaggedObjectID objectForKey:]. This is a conventional way to refer to an Objective C method in writing. In this case, it's:
An instance method (hence the -, rather than a + that would be used for class methods)
On the class _NSCoreDataTaggedObjectID (more on this later)
On the method named objectForKey:
The object receiving this method invocation is the one with address 0x8000000000000000.
This is a pretty weird address. Something is up.
Another hint is the strange class name of _NSCoreDataTaggedObjectID. There's a few observations we can make about it:
The prefixed _NS suggests that it's an internal implementation detail of CoreData.
We google the name to find class dumps of the CoreData framework, which show us that:
_NSCoreDataTaggedObjectID subclasses _NSScalarObjectID
Which subclasses _NSCoreManagedObjectID
Which subclasses NSManagedObjectID
NSManagedObjectID is a public API, which has its own first-party documentation.
It has the word "tagged" in its name, which has a special meaning in the Objective C world.
Some back story
Objective C used message passing as its sole mechanism for method dispatch (unlike Swift which usually prefers static and v-table dispatch, depending on the context). Every method call you wrote was essentially syntactic sugar overtop of objc_msgSend (and its variants), passing to it the receiver object, the selector (the "name" of the method being invoked) and the arguments. This was a special function that would do the job of checking the class of the receiver object, and looking through that classes' hierarchy until it found a method implementation for the desired selector.
This was great, because it allows you to do a lot of cool runtime dynamic behaviour. For example, menu bar items on a macOS app would just define the method name they invoke. Clicking on them would "send that message" to the responder chain, which would invoke that method on the first object that had an implementation for it (the lingo is "the first object that answers to that message").
This works really well, but has several trade-offs. One of them was that everything had to be an object. And by object, we mean a heap-allocated memory region, whose first several words of memory stored meta-data for the object. This meta-data would contain a pointer to the class of the object, which was necessary for doing the method-loopup process in objc_msgSend as I just described.
The issue is, that for small objects, (particularly NSNumber values, small strings, empty arrays, etc.) the overhead of these several words of object meta-data might be several times bigger than the actual object data you're interested in. E.g. even though NSNumber(value: true /* or false */) stores a single bit of "useful" data, on 64 bit systems there would be 128 bits of object overhead. Add to that all the malloc/free and retain/release overhead associated with dealing with large numbers of tiny object, and you got a real performance issue.
"Tagged pointers" were a solution to this problem. The idea is that for small enough values of particular privileged classes, we won't allocate heap memory for their objects. Instead, we'll store their objects' data directly in their pointer representation. Of course, we would need a way to know if a given pointer is a real pointer (that points to a real heap-allocated object), or a "fake pointer" that encodes data inline.
The key realization that malloc only ever returns memory aligned to 16-byte boundaries. This means that 4 bits of every memory address were always 0 (if they weren't, then it wouldn't have been 16-byte aligned). These "unused" 4 bits could be employed to discriminate real pointers from tagged pointers. Exactly which bits are used and how differs between process architectures and runtime versions, but the general idea is the same.
If a pointer value had 0000 for those 4 bits then the system would know it's a real object pointer that points to a real heap-allocated object. All other possible values of those 4-bit values could be used to signal what kind of data is stored in the remaining bits. The Objective C runtime is actually opensource, so you can actually see the tagged pointer classes and their tags:
{
// 60-bit payloads
OBJC_TAG_NSAtom = 0,
OBJC_TAG_1 = 1,
OBJC_TAG_NSString = 2,
OBJC_TAG_NSNumber = 3,
OBJC_TAG_NSIndexPath = 4,
OBJC_TAG_NSManagedObjectID = 5,
OBJC_TAG_NSDate = 6,
// 60-bit reserved
OBJC_TAG_RESERVED_7 = 7,
// 52-bit payloads
OBJC_TAG_Photos_1 = 8,
OBJC_TAG_Photos_2 = 9,
OBJC_TAG_Photos_3 = 10,
OBJC_TAG_Photos_4 = 11,
OBJC_TAG_XPC_1 = 12,
OBJC_TAG_XPC_2 = 13,
OBJC_TAG_XPC_3 = 14,
OBJC_TAG_XPC_4 = 15,
OBJC_TAG_NSColor = 16,
OBJC_TAG_UIColor = 17,
OBJC_TAG_CGColor = 18,
OBJC_TAG_NSIndexSet = 19,
OBJC_TAG_NSMethodSignature = 20,
OBJC_TAG_UTTypeRecord = 21,
// When using the split tagged pointer representation
// (OBJC_SPLIT_TAGGED_POINTERS), this is the first tag where
// the tag and payload are unobfuscated. All tags from here to
// OBJC_TAG_Last52BitPayload are unobfuscated. The shared cache
// builder is able to construct these as long as the low bit is
// not set (i.e. even-numbered tags).
OBJC_TAG_FirstUnobfuscatedSplitTag = 136, // 128 + 8, first ext tag with high bit set
OBJC_TAG_Constant_CFString = 136,
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
You can see, strings, index paths, dates, and other similar "small and numerous" classes all have reserved pointer tag values. For each of these "normal classes" (NSString, NSDate, NSNumber, etc.), there's a special internal subclass which implements all the same public API, but using a tagged pointer instead of a regular object.
As you can see, there's a value for OBJC_TAG_NSManagedObjectID. It turns out, that NSManagedObjectID objects were numerous and small enough that they would benefit greatly for this tagged-pointer representation. After all, the value of NSManagedObjectID might be a single integer, much like NSNumber, which would be wasteful to heap-allocate.
If you'd like to learn more about tagged pointers, I'd recommend Mike Ash's writings, such as https://www.mikeash.com/pyblog/friday-qa-2012-07-27-lets-build-tagged-pointers.html
There was also a recent WWDC talk on the subject: WWDC 2020 - Advancements in the Objective-C runtime
The strange address
So in the previous section we found out that _NSCoreDataTaggedObjectID is the tagged-pointer subclass of NSManagedObjectID. Now we can notice something else that's strange, the pointer value we saw had a lot of zeros: 0x8000000000000000. So what we're dealing with is probably some kind of uninitialized-state of an object.
Conclusion
The call stack can shed further light on where this happens exactly, but what we know is that somewhere in your program, the objectForKey: method is being invoked on an uninitialized value of NSManagedObjectID.
You're probably accessing a value too-early, before it's properly initialized.
To work around this you can take one of several approaches:
A future ideal world, use would just use the structured concurrency of Swift 5.5 (once that's available on enough devices) and async/await to push the work on the background and await the result.
Use a completion handler to invoke your value-consuming code only after the value is ready. This is most immediately-easy, but will blow up your code base with completion handler boilerplate and bugs.
Use a concurrency abstraction library, like Combine, RxSwift, or PromiseKit. This will be a bit more work to set up, but usually leads to much clearer/safer code than throwing completion handlers in everywhere.
The basic pattern to achieve thread safety is to never mutate/access the same property from multiple threads at the same time. The simplest solution is to just never let any background queue interact with your property directly. So, create a local variable that the background queue will use, and then dispatch the updating of the property to the main queue.
Personally, I wouldn't have setup interact with myDict at all, but rather return the result via the completion handler, e.g.
// properties
var myDict = ...
private let queue = DispatchQueue(label: "my-queue", qos: .utility) // some background queue on which we'll recalculate what will eventually be used to update `myProperty`
// method doesn't reference `myDict` at all, but uses local var only
func setup(completion: #escaping (Foo) -> Void) {
queue.async {
var results = ... // some local variable that we'll use as we're building up our results
// do time-consuming population of `results` here;
// do not touch `myDict` here, though;
// when all done, dispatch update of `myDict` back to the main queue
DispatchQueue.main.async { // dispatch update of property back to the main queue
completion(results)
}
}
}
Then the routine that calls setup can update the property (and trigger necessary UI update, too).
setup { results in
self.myDict = results
// also trigger UI update here, too
}
(Note, your closure parameter type (Foo in my example) would be whatever type myDict is. Maybe a typealias as advised elsewhere, or better, use custom types rather than dictionary within dictionary within dictionary. Use whatever type you’d prefer.)
By the way, your question’s title and preamble talks about TSAN and thread safety, but you then share a “unrecognized selector” exception, which is a completely different issue. So, you may well have two completely separate issues going on. A TSAN data race error would have produced a very different message. (Something like the error I show here.) Now, if setup is mutating myDict from a background thread, that undoubtedly will lead to thread-safety problems, but your reported exception suggests there might also be some other problem, too...

Safety of using an empty reference instance across multiple threads

Background
I have a class Data that stores multiple input parameters and a single output value.
The output value is recalculated whenever one of the input parameters is mutated.
The calculation takes a non-trivial amount of time so it is performed asynchronously.
If one of the input parameters changes during recalculation, the current calculation is cancelled, and a new one is begun.
The cancellation logic is implemented via a serialized queue of calculation operations and a key (reference instance) (Data.key). Data.key is set to a new reference instance every time a new recalculation is added to the queue. Also only a single recalculation can occur at a time — due to the queue. Any executing recalculation constantly checks if it was the most recently initiated calculation by holding a reference to both the key that what was created with it when it was initiated and the currently existing key. If they are different, then a new recalculation has been queued since it began, and it will terminate.
This will trigger the next recalculation in the queue to begin, repeating the process.
The basis for my question
The reassignment of Data.key is done on the main thread.
The current calculation constantly checks to see if its key is the same as the current one. This means another thread is constantly accessing Data.key.
Question(s)
Is it safe for me to leave Data.key vulnerable to being read/written to at the same time?
Is it even possible for a property to be read and written to simultaneously?
Yes Data.Key vulnerable to being read/written to at the same time.
Here is example were i'm write key from main thread and read from MySerialQueue.
If you run that code, sometimes it would crash.
Crash happens because of dereference of pointer that point to memory released during writing by main queue.
Xcode have feature called ThreadSanitizer, it would help to catch such problems.
Discussion About Race condition
func experiment() {
var key = MyClass()
var key2 = MyClass()
class MyClass {}
func writer() {
for _ in 0..<1000000 {
key = MyClass()
}
}
func reader() {
for _ in 0..<1000000 {
if key === key2 {}
}
}
DispatchQueue.init(label: "MySerialQueue").async {
print("reader begin")
reader()
print("reader end")
}
DispatchQueue.main.async {
print("writer begin")
writer()
print("writer end")
}
}
Q:
Is it safe for me to leave Data.key vulnerable to being read/written to at the same time?
A:
No
Q:
Is it even possible for a property to be read and written to simultaneously?
A:
Yes, create a separate queue for the Data.Key that only through which you access it. As long as any operation (get/set) is restricted within this queue you can read or write from anywhere with thread safety.

Any way to detect `UnsafeMutablePointer` has been deallocated?

I got a custom observer which is checking if changes have been made to objects it is observing. The object holds reference to the original property (pointer) in an UnsafeMutablePointer ... the problem is that all works jolly good unless the original value deallocates and I start getting random values from whatever now occupies that bit of memory ...
Is there any way to detect the original object is gone so I can invalidate my observer?
Edit: To make clear what I am trying to achieve I have added my current implementation (which kinda works for local class variables):
class Observer<Observed> where Observed: Equatable {
typealias Binder = ((Observed) -> ())
typealias Storage = UnsafeMutablePointer<Observed>
private var storage: Storage
private var bind: Binder
private var running: Bool
init(_ value: Storage, binder: #escaping Binder) {
storage = value
bind = binder
running = true
var val: Observed = value.pointee
DispatchQueue.global().async {
while self.running {
if val != value.pointee {
self.bind(value.pointee)
val = value.pointee
}
}
}
}
var value: Observed {
return storage.pointee
}
deinit {
running = false
}
}
No; it is your responsibility to ensure that an unsafe pointer cannot outlive its referenced object. ("Important memory details are your problem" is what Swift generally means by the "Unsafe" designation.)
Generally the correct tool here is a weak reference rather than a pointer.
Your observation code is incorrect in numerous ways. The basic approach of a tight loop that reads the value and compares it to an existing value is going to drain battery very quickly even if it had no other problems. It's also invalid because your pointee isn't thread-safe. You can't read it on a random thread and be promised a coherent value.
Swift doesn't promise that value types stay at the same place in memory, so even if the value still "exists," your pointer is not promised to point to it. It doesn't even promise that value types have a heap (i.e. persistent) memory location; they may be stored on the stack or even in registers. When you take a pointer to it, it may create a temporary copy in order to provide a memory location, but it's not promised to be the same memory as other references to it. That's the whole point of a value type. It can be freely copied and moved around (and even entirely optimized away in some cases). If you need to be able to refer to the instance, then you need a reference type.

Crash while archiving data by NSKeyedArchiver

My app uses the following function to serialise a set of custom objects (only relevant code is shown):
class func serializeShoppingLocations(buyLocations: Set<ShoppingLocation>) -> Data {
#if os(iOS)
NSKeyedArchiver.setClassName("ShoppingLocation", for: ShoppingListIOS.ShoppingLocation.self)
#elseif os(watchOS)
NSKeyedArchiver.setClassName("ShoppingLocation", for: ShoppingListWatch.ShoppingLocation.self)
#endif
let data = NSKeyedArchiver.archivedData(withRootObject: buyLocations)
return data
}
The conditional compilation for the iOS and watchOS targets is required in order to allow both targets to dearchive the serialised data later, even if they have been archived by the other target.
ShoppingLocation adopts the NSCoding protocol, and inherits from NSObject.
This code crashes at the let data = … instruction with EXC_BAD_ACCESS (code=1, address=0x0).
I know that the error probably indicates that a non-existent object is referred. However, the variable buyLocations is existent, and I can print the value in the debugger, including all values of the objects in the set.
The stack trace is:
The stack trace indicates that the archiver starts to archive the NSSet, and tries to archive one of the elements of the set, but setObjectForKey in the internal mutable dictionary fails.
What could be the reason?
EDIT:
During further testing I realised that the crash happens always when at least 2 threads of the app are active.
Particularly, I found a situation where one thread crashed (red in Xcode) at an instruction different from the one mentioned above:
let dataBlob = NSKeyedArchiver.archivedData(withRootObject: shoppingItem.buyLocations)
and the other thread stopped at the instruction mentioned above (green in Xcode):
let data = NSKeyedArchiver.archivedData(withRootObject: buyLocations)
Might it be that NSKeyedArchiver is not thread-safe?
My problem is indeed, as suspected, caused by unsynchronised accesses by multiple threads:
An NSArchiver walks through an object graph, starting at the root object, and archives on the way every object it encounters.
In order to create a correct archive, it is therefore required that the object graph is not mutated until the archiver terminates.
In a single-threaded application, this is guaranteed automatically.
In a multi-threaded application where more than 1 thread can mutate the object graph, the programmer is responsible to ensure this, because the archiver has no way to prevent other threads to mutate the root object. (If, e.g., the archiver would first make a deep copy of the object graph, and then archive the copy, the object graph could be mutated during the deep copy).
It is therefore required to serialise all get and set accesses to the object graph, e.g. by execute them in a serial access queue.

What does it mean for something to be thread safe in iOS?

I often come across the key terms "thread safe" and wonder what it means. For example, in Firebase or Realm, some objects are considered "Thread Safe". What exactly does it mean for something to be thread safe?
Thread Unsafe
- If any object is allowed to modify by more than one thread at the same time.
Thread Safe
- If any object is not allowed to modify by more than one thread at the same time.
Generally, immutable objects are thread-safe.
An object is said to be thread safe if more than one thread can call methods or access the object's member data without any issues; an "issue" broadly being defined as being a departure from the behaviour when only accessed from only one thread.
For example an object that contains the code i = i + 1 for a regular integer i would not be thread safe, since two threads might encounter that statement and one thread might read the original value of i, increment it, then write back that single incremented value; all at the same time as another thread. In that way, i would be incremented only once, where it ought to have been incremented twice.
After searching for the answer, I got the following from this website:
Thread safe code can be safely called from multiple threads or concurrent tasks without causing any problems (data corruption, crashing, etc). Code that is not thread safe must only be run in one context at a time. An example of thread safe code is let a = ["thread-safe"]. This array is read-only and you can use it from multiple threads at the same time without issue. On the other hand, an array declared with var a = ["thread-unsafe"] is mutable and can be modified. That means it’s not thread-safe since several threads can access and modify the array at the same time with unpredictable results. Variables and data structures that are mutable and not inherently thread-safe should only be accessed from one thread at a time.
iOS Thread safe
[Atomicity, Visibility, Ordering]
[General lock, mutex, semaphore]
Thread safe means that your program works as expected. It is about multithreading envirompment, where we have a problem with shared resource with problems of Data races and Race Condition[About].
Apple provides us by Synchronization Tools:
Atomicity
Atomic Operations - lock free mechanism which is based on hardware instructions - for example Compare-And-Swap(CAS)[More]...
Objective-C OSAtomic, atomic property attribute[About]
[Swift Atomic Operations]
Visibility
Volatile Variable - read value from memory(no cache)
Objective-C volatile
Ordering
Memory Barriers - guarantees up-to date data[About]
Objective-C OSMemoryBarrier
Find problem in your code
Thread Sanitizer - uses self.recordAndCheckWrite(var) inside to figure out when(timestamp) and who(thread) access to variable
Synchronisation
Locks - thread can get a lock and nobody else access to the resource. NSLock.
Semaphore consists of Threads queue, Counter value and has wait() and signal() api. Semaphore allows a number of threads(Counter value) work with resource at a given moment. DispatchSemaphore, POSIX Semaphore - semaphore_t. App Group allows share POSIX semaphores
Mutex - mutual exclusion, mutually exclusive - is a type of Semaphore(allows several threads) where Thread can acquire it and is able to work with block as a single encroacher, all others thread will be blocked until release. The main different with lock is that mutex also works between processes(not only threads). Also it includes memory barrier.
var lock = os_unfair_lock_s()
os_unfair_lock_lock(&lock)
//critical section
os_unfair_lock_unlock(&lock)
NSLock -POSIX Mutex Lock - pthread_mutex_t, Objective-C #synchronized.
let lock = NSLock()
lock.lock()
//critical section
lock.unlock()
Recursive lock - Lock Reentrance - thread can acquire a lock several times. NSRecursiveLock
Spin lock - waiting thread checks if it can get a lock repeatedly based on polling mechanism. It is useful for small operation. In this case thread is not blocked and expensive operations like context switch is not nedded
[GCD]
Common approach is using custom serial queue with async call - where all access to memory will be done one by one:
serial read and write access
private let queue = DispatchQueue(label: "com.company")
self.queue.async {
//read and write access to shared resource
}
concurrent read and serial write access. when write is oocured - all previous read access finished -> write -> all other reads
private let queue = DispatchQueue(label: "com.company", attributes: .concurrent)
//read
self.queue.sync {
//read
}
//write
self.queue.sync(flags: .barrier) {
//write
}
Operations
[Actors]
actor MyData {
var sharedVariable = "Hello"
}
//using
Task {
await self.myData.sharedVariable = "World"
}
Multi threading:
[Concurrency vs Parallelism]
[Sync vs Async]
[Mutable vs Immutable] [let vs var]
[Swift thread safe Singleton]
[Swift Mutable/Immutable collection]
pthread - POSIX[About] thread
NSThead
To give a simple example. If something is shared across multiple threads without any issues like crash, it is thread-safe. For example, if you have a constant (let value = ["Facebook"]) and it is shared across multiple threads, it is thread safe because it is read-only and cannot be modified. Whereas, if you have a variable (var value = ["Facebook"]), it may cause potential crash or data loss when shared with multiple threads because it's data can be modified.

Resources