The copyright of this article belongs to the public account [an old code farmer].

  • NSLock

NSLock is a mutex that ensures that only one thread can operate and access the same resource at the same time. NSLock implements the NSLocking protocol. There are two methods lock() and unlock() in the NSLocking protocol, that is, locking and unlocking. All classes that implement the NSLocking protocol have the feature of mutual exclusion

public protocol NSLocking {
    func lock()

    func unlock()
}
Copy the code

NSLock, for example

private var count = 0
private let lock = NSLock()

private func testNSLock() {
            
    DispatchQueue.global().async {
        self.printCount()
    }
          
    DispatchQueue.global().async {
        self.printCount()
    }
}

private func printCount() {
    lock.lock()
    count += 1
    print(count)
    sleep(5)
    lock.unlock()
}
Copy the code

Call printCount, increment count inside printCount and sleep5 seconds. The first thread acquires the lock. The second thread has to wait 5 seconds until the first thread, unlock, can execute the method. 1 is printed first and 2 is printed every 5 seconds.

Another feature of NSLock is that the lock() method cannot be called repeatedly (i.e. lock reentrant). For example, if A lock is used in both methods, and method B is called in method A, A deadlock occurs.

The following code causes a deadlock:

private func func1() {
    lock.lock()      
    func2()      
    lock.unlock()
}
    
private func func2() {
    lock.lock()
    print(count)
    lock.unlock()
}
Copy the code
  • NSRecursiveLock

NSRecursiveLock is called recursive lock. NSRecursiveLock itself also implements THE NSLocking protocol, but the difference is that it can solve the above NSLock reentry problem. Since it is a recursive lock, it can also make recursive calls. The following code will normally print out the strings in both methods

private let recursiveLock = NSRecursiveLock()

private func testRecursiveLock() {
    recursiveLock.lock()
    print("testRecursiveLock")
    testRecursiveLock2()
    recursiveLock.unlock()
}
        
private func testRecursiveLock2() {
    recursiveLock.lock()
    print("testRecursiveLock2")
    recursiveLock.unlock()
}
Copy the code
  • NSCondition

NSCondition is commonly called conditional lock. It also implements the NSLocking protocol, so it also has two methods lock and unlock. If these two methods are called, they have the same effect as NSLock. Signal () notifies the first blocking thread to be released. Broadcast () releases the first block in each thread. Usage:

private let condition = NSCondition() private func testCondition() { DispatchQueue.global().async { self.conditionFunc()  } DispatchQueue.global().async { self.conditionFunc() } DispatchQueue.global().asyncAfter(deadline: .now() + 2, execute: { self.condition.signal() }) DispatchQueue.global().asyncAfter(deadline: .now() + 4, execute: { self.condition.signal() }) } private func conditionFunc() { condition.lock() print("conditionFunc") count += 1 print(count) condition.wait() count += 1 print(count) condition.unlock() }Copy the code

ConditionFunc is called by two threads at the same time. The first thread obtains the lock, prints conditionFunc, and then 1. The second thread is in a wait state.

Condition then call the wait, the first thread into the block, the second thread can enter the execution, and print conditionFunc, 2, followed by condition again call wait, the second thread into the block, two seconds later condition and call a signal, The first thread releases the block, count increments by 1, and prints 3. Two seconds later, condition calls Signal again. The second thread releases, count increments by 1, and prints 4. If singNAL is changed to broadcast, both threads will release blocking.

  • NSConditionLock

NSConditionLock encapsulates NSCondition with conditional detection, and can block and release threads more flexibly based on setting condition values. ConditionLock. Lock (whenCondition: 1) the method is that when meet the conditions of the condition of 1 lock, or block the thread

Conditionlock. unlock(withCondition: 0) conditionlock. unlock(withCondition: 0) conditionlock. unlock(withCondition: 0

private func testConditionLock() { let conditionLock = NSConditionLock(condition: 1) dispatchqueue.global ().async {conditionlock. lock(whenCondition: 1) print(" condition1 ") conditionlock. unlock(withCondition: 1) 0) } DispatchQueue.global().asyncAfter(deadline: .now() + 10, execute: \ {print (" condition values (conditionLock. Condition) ") conditionLock. Lock (whenCondition: Conditionlock. unlock(withCondition: 1) print(conditionlock. condition) conditionlock. unlock(condition: 1) print(conditionlock. condition) conditionlock. condition)Copy the code

First we create an NSConditionLock object and condition is set to 2.

Lock (whenCondition: 1) is then called in the first thread to lock whenCondition equals 1. Coedition is 2, so thread 1 is blocked. 10 seconds later, thread 2 calls to print “condition value 2”, then meets the condition that coedition equals 2, locks it, prints “Task 2”, and calls unlock(withCondition: 1) Unlock and set the coedition value to 1, then thread 1 releases the block, print task 1, then unlock and set the condition value to 0

  • @synchronized

@synchronized is also a mutex and is generally used for singletons, but there is no @synchronized in Swift. Its counterparts in Swift are objc_sync_enter and objc_sync_exit, which are used as follows:

@synchronized (self) {//todo someting} Swift: objc_sync_enter(self) //todo sometingCopy the code
  • A semaphore

A semaphore is something that belongs to the GCD and can block threads based on its value. Semaphore >=0 will not block the current thread, semaphore less than 0 will block the current thread. When the semaphore is reduced by one after calling wait(), the semaphore is increased by one after calling signal().

Var number = 0 let semaphore = DispatchSemaphore(value: 1) DispatchQueue.global().async { semaphare.wait() number += 1 print(number) semaphare.signal() } DispatchQueue.global().async { semaphare.wait() number += 1 print(number) semaphare.signal() }Copy the code

Start two threads at the same time, and when the first thread calls wait(), the semaphore is reduced by one. The semaphore is zero, so the first thread executes normally. Wait () is called when the second thread starts executing, and the semaphore is reduced by one, so the second thread blocks.

When the first thread calls the signal() method, the semaphore is incremented by one and the second thread begins execution.

  • Barrier function

The barrier function is also a common GCD function, which can block the current queue to achieve the purpose of locking, as follows:

let queue = DispatchQueue(label: "1111") queue.async { print("1") } queue.async { print("2") } queue.async(group: Nil, qos:.default, flags:.barrier, execute: {print(" barrier ")}) queue.async {print("3")} queue.async {print("4")}Copy the code

Explanation: If there is no barrier function blocking, the printing order of 1, 2, 3, and 4 is random. After the barrier function is added, the current queue will be blocked, so 3 and 4 can only be printed after 1 and 2 are finished.

The printing sequence is 1, 2, fence, 3, and 4, where 1 and 2, 3 and 4 are printed in random order.

  • pthread_mutex

Pthread_mutex is a C function. It is a mutex and a recursive lock.

Var mutex = pthread_mutex_t(); // Initialize pthread_mutex_init(&mutex,nil); Private func testMutex() {pthread_mutex_lock(&mutex) count += 1 print(count) testMutex() Pthread_mutex_unlock (&mutex)} deinit {pthread_mutex_destroy(&mutex)}Copy the code
  • atomic

Atomic is typically used for keyword descriptions of oc declaration attributes. Stands for atomic operations, which are themselves thread-safe, as opposed to nonatomic, non-atomic operations. Atomic is not thread-safe in certain cases, such as the following code:

@property(atomic, strong)NSMutableArray *array;
Copy the code

Array can be assigned by multiple threads to ensure thread safety, for example: xx.array = XXX;

However, when multiple threads add or remove elements to an array, it remains thread-safe. [xx.array addObject: xxx];

  • Spinlocks (OSSpinLock)

The difference between a spin lock and a mutex is that when a thread tries to acquire the lock but fails to acquire it, the mutex puts the thread into a sleep state. When the lock is released, the thread is awakened to acquire the lock at the same time. To continue the mission. Spin-locks, on the other hand, do not go to sleep, but keep looping to see if they are available.

Since a spin lock is CPU intensive to wait for, it is efficient and can be executed as soon as the lock is released without needing to wake up. So it’s good for lightweight task locking for short periods of time. In iOS development, Apple provided a spin lock called OSSpinLock, but OSSpinLock has been deprecated since iOS10.0, so the usage is not covered.

  • os_unfair_lock

Os_unfair_lock is intended to replace OSSpinLock after iOS 10.0. Unlike OSSpinLock, os_UNFAIR_Lock attempts to acquire threads that sleep and are awakened by the kernel when unlocked.

Os_unfair_lock is literally an unfair lock, meaning that the thread that released the lock may immediately acquire it again, while the thread that was waiting for the lock may not be able to attempt the lock once it wakes up. Usage:

private var unfairLock = os_unfair_lock()

func testUnfairLock() {
    os_unfair_lock_lock(&unfairLock)
    //todo someting
    os_unfair_lock_unlock(&unfairLock)
}
Copy the code

This article is first published in the public number [an old code farming], pay attention to the public number free access to learning video

Introduction to various thread locks in iOS