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]
- Objective-C
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 haswait()
andsignal()
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 semaphoresMutex
- mutual exclusion, mutually exclusive - is a type ofSemaphore
(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 withlock
is thatmutex
also works between processes(not only threads). Also it includesmemory 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
-
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
-
actor MyData { var sharedVariable = "Hello" } //using Task { await self.myData.sharedVariable = "World" }
Multi threading:
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.
© 2022 - 2024 — McMap. All rights reserved.