How can I make a thread-safe app using NSLock?
Intro
In iOS programming, There are several techniques for creating a thread-safe app. Among them, I’ll talk about how to make a thread-safe app using NSLock.
What is the thread-safe?
In multi-thread programming, it generally means that no matter how many functions, variables, or objects are accessed from multiple threads at the same time, there is no problem in the execution of the program.
More precisely, when one function is called from one thread and is running, it is defined that the performance result of the function in each thread comes out correctly even if another thread calls the function and runs together at the same time.
Atomic operation
There are several rules for creating a thread-safe program, but among them, we will only look at the atomic operation. Because NSLock is associated with atomic operation.
When accessing shared resources, mutual exclusion may be implemented by using atomic operations or by using atomically defined approaches. In Swift, NSLock enables atomic operation.
If you want to learn about more rules for creating a thread-safe program, check this link below.
What is NSLock? and how can I use NSLock?
NSLock is a mechanism that you can use to protect critical sections of code from being accessed by multiple threads simultaneously. Here’s sample code of NSLock.
import Foundation
var money = 1000
DispatchQueue.global().async {
spend()
}
DispatchQueue.global().async {
spend()
}
func spend() {
money = money - 100
print(money)
}
In this code, Two Threads execute a method called spend() at the same time, and the wrong result is output. We need to fix this code using NSLock.
import Foundation
var money = 1000
let lock = NSLock()
DispatchQueue.global().async {
spend()
}
DispatchQueue.global().async {
spend()
}
func spend() {
lock.lock()
defer {lock.unlock()}
money = money - 100
print(money)
}
In this code, When spend() executed, NSLock locked critical section of code. So the second spend() executed must wait until the execution of the first spend() is finished.
When you use NSLock in your code, you should unlock the lock. If lock isn’t unlocked, It may cause deadlock. Also, Unlocking a lock from a different thread can result in undefined behavior. The most appropriate way to perform unlock is to use defer().
defer() lets us set up some work to be performed when the current scope exits. So unlock using defer is a effective way to unlock the lock.
In addition, Apple offers various other types of NSLock.
- NSRecursiveLock
NSRecursiveLock defines a lock that may be acquired multiple times by the same thread without causing a deadlock, a situation where a thread is permanently blocked waiting for itself to relinquish a lock. While the locking thread has one or more locks, all other threads are prevented from accessing the code protected by the lock.
you can check more information in this link.
2. NSConditionLock
Using an NSConditionLock object, you can ensure that a thread can acquire a lock only if a certain condition is met. Once it has acquired the lock and executed the critical section of code, the thread can relinquish the lock and set the associated condition to something new. The conditions themselves are arbitrary: you define them as needed for your application.
you can check more information in this link.
Conclusion
Multi-threading helps improve performance, but misimplementation can also cause deadlock and it can result in undefined behavior. So when you want to use NSLock, you should unlock the lock and be careful to use it correctly.