Locks are a common synchronization tool. At the same time A piece of code can only be allowed to be A finite number of threads access, such as A thread A before entering the need to protect the code to add simple mutex, another thread B will not be able to access the protected code, only waiting for the previous thread A unlock after execution of the protected code, B threads can access to the protected code. This article summarizes the locks used in iOS development, including Spinlock_t, OS_UNfair_LOCK, pthread_mutex_t, NSLock, NSRecursiveLock, NSCondition, NSConditionLock, @synchronized, dispatch_sema Phore, pthread_rwlock_t.

spinlock_t

Spin-lock, there are only three ways to lock, unlock and try to lock. Unlike NSLock, the thread is first polled when a lock request fails, but a second later the thread enters the waiting state, waiting to be awakened. OSSpinLock, on the other hand, polls all the time. Waiting consumes a lot of CPU resources and is not suitable for long tasks. To use OSSpinLock, you need to first introduce #import <libkern/ osatomic.h >. See/usr/include/libkern OSSpinLockDeprecated. H name at the back of the Deprecated strong hint us OSSpinLock has been Deprecated. OSSPINLOCK_DEPRECATED_REPLACE_WITH(OS_unfair_LOCK) OSSpinLock has thread-safety issues, which can cause priority inversion issues, and should not be used under any circumstances at this time. We can use Apple’s OS_Unfair_Lock released after iOS 10.0 (as an alternative to OSSpinLock). We’ll learn more about OS_UNfair_LOCK in the next section.

The OSSpinLock API is simple to use

The OSSpinLock API is simple. Let’s start with an example of how to use it.

#import <libkern/OSAtomic. H > // Import OSSpinLock @interface ViewController () @property (nonatomic, assign) NSInteger sum; @property (nonatomic, assign) OSSpinLock lock; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.lock = OS_SPINLOCK_INIT; // Initialize the lock. Dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); Self. sum = 0; self.sum = 0; // dispatch_async(globalQueue, ^{// async task 1 OSSpinLockLock(&_lock); For (unsigned int I = 0; i < 10000; ++i) { self.sum++; } NSLog (@ "⏰ ⏰ ⏰ % ld", the self. The sum); OSSpinLockUnlock(&_lock); / / unlock}); Dispatch_async (globalQueue, ^{// Async task 2 OSSpinLockLock(&_lock); For (unsigned int I = 0; i < 10000; ++i) { self.sum++; } NSLog (@ "⚽ ️ ⚽ ️ ⚽ ️ % ld", the self. The sum); OSSpinLockUnlock(&_lock); / / unlock}); } @end // Print 🖨️ : ⏰⏰⏰ 10000 most most most 20000 // While most 10000 most most 20000 // While most 10000 most 10000 // While most 10000 most 10000 Another is both reached more than 10000 / / case 1: ⏰ ⏰ ⏰ 9064 ⚽ ️ ⚽ ️ ⚽ ️ / 13708 / case 2: ⏰ ⏰ ⏰ 11326 ⚽ ️ ⚽ ️ ⚽ ️ / 9933 / case 3: ⏰ ⏰ ⏰ 10906 ⚽ ️ ⚽ ️ ⚽ ️ 11903...Copy the code

The sum property works the same if it’s atomic or nonatomic. Atomic is atomic, but it’s not thread-safe. Its atomicity is limited to locking the setters and getters of the variables it decorates. Sum ++, self.sum = self.sum+ 1, and so on. Atomic does not guarantee thread-safety.

The number printed without a lock has some interesting points, which are analyzed here :(assume that the two dispatch_async tasks in the global concurrent queue have both new threads opened and the two lines are named “⏰ thread” and “⚽️ thread” respectively)

  1. It is certain that neither ⏰ nor ⚽️ threads will print20000.
  2. Both the ⏰ and ⚽️ threads have been printed10000The above.
  3. ⏰ thread or ⚽️ thread one of which is printed in10000One of the above is in10000The following.

Since the ⏰ and ⚽️ threads are running concurrently, there is no such thing as a thread that increments sum to 10000 and then another thread that increments sum to 20000. Only if the lock or self. Sum increments are executed in a serial queue. In case 2, we may be able to understand that both prints are above 10000, which can be analyzed as a point in time when the ⏰ thread continues to increment, and then the ⚽️ thread executes the loop after this point in time when sum is already greater than its value from the last loop. Then the sum of the ⏰ and ⚽️ threads continues to loop with a value greater than the value of the last loop, and the last two threads print sum with a value greater than 10000. In case 3, it is difficult to understand why one of them can be less than 10000. Is it possible that one of the threads is executing fast or slow? And if it was shrunk once, wouldn’t that cause both threads to end up printing sum less than 10000? Is it possible that self.sum was read from a register or memory? The volatile keyword came to mind. (For the time being, the analysis will stop here.)

OSSpinLockDeprecated. H File content

Let’s take a look directly at the code in osSpinLockDeprecated.h. Each line of OSSpinLock related code in the above example code will have a warning ⚠️⚠️ ‘OSSpinLock’ is deprecated: First deprecated in iOS 10.0-use OS_unfair_lock () from < OS /lock.h> instead. As OSSPINLOCK_DEPRECATED indicates below, we are advised not to use OSSpinLock on all four systems.

#ifndef OSSPINLOCK_DEPRECATED

#define OSSPINLOCK_DEPRECATED 1
#define OSSPINLOCK_DEPRECATED_MSG(_r) "Use " #_r "() from <os/lock.h> instead"
#defineOSSPINLOCK_DEPRECATED_REPLACE_WITH (_r) \ __OS_AVAILABILITY_MSG (macosx, deprecated = 10.12, OSSPINLOCK_DEPRECATED_MSG (_r)) \ __OS_AVAILABILITY_MSG (ios, deprecated = 10.0, OSSPINLOCK_DEPRECATED_MSG (_r)) \ __OS_AVAILABILITY_MSG (tvos, deprecated = 10.0, OSSPINLOCK_DEPRECATED_MSG(_r)) \ __OS_AVAILABILITY_MSG(watchos, deprecated=3.0, OSSPINLOCK_DEPRECATED_MSG(_r)) \ __OS_AVAILABILITY_MSG(watchos, deprecated=3.0, OSSPINLOCK_DEPRECATED_MSG(_r))
    
#else

#undef OSSPINLOCK_DEPRECATED
#define OSSPINLOCK_DEPRECATED 0
#define OSSPINLOCK_DEPRECATED_REPLACE_WITH(_r)

#endif
Copy the code

Here are the different implementations of the OSSpinLock API:

  1. #if ! (defined(OSSPINLOCK_USE_INLINED) && OSSPINLOCK_USE_INLINED)The original API when not using inlining is true:
  • #define OS_SPINLOCK_INIT 0Initialization.
/ *! The default value for an OSSpinLock. The default value for OSSpinLock is 0 (unlocked) is that unlocked is zero, locked is nonzero. The convention is: unlocked is zero, locked is non-zero */
#define    OS_SPINLOCK_INIT    0
Copy the code
  • OSSpinLockData type.
/ *! @abstract Data type for a spinlock. The spinlock data type is int32_t @discussion You should always initialize a spinlock to {@link OS_SPINLOCK_INIT} before using it. Before using a spinlock, we should always initialize it to OS_SPINLOCK_INIT. (It is actually assigned the number 0) */
typedef int32_t OSSpinLock OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock);
Copy the code
  • OSSpinLockTryTry to lock,boolThe return value of the lock type indicates whether the lock succeeded. Failure does not block the thread.
/ *! @abstract Locks a spinlock if it would not block. If a spinlock is not locked, it is locked. @result Returns false if the lock was already held by another thread, true if it took the lock successfully. Return false if the lock is already held by another thread, or true otherwise to indicate success. * /
OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock_trylock)
__OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_2_0)
bool    OSSpinLockTry( volatile OSSpinLock *__lock );
Copy the code
  • OSSpinLockLockLock.
/ *! @discussion Although the lock operates spins, it employs various strategies to back off if the lock is held. Although the lock operation rotates (it waits when the lock fails and blocks until the lock is acquired), it employs various policies to enable the rotation to be turned off if the lock is successful. */ OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock_lock) __OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_2_0) void OSSpinLockLock( volatile OSSpinLock *__lock );Copy the code
  • OSSpinLockUnlockUnlocked.
/ *! @abstract Unlocks a spinlock */ OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock_unlock) __OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_2_0) void OSSpinLockUnlock( volatile OSSpinLock *__lock );Copy the code
  1. OSSPINLOCK_USE_INLINEDInline is used for true, and an inline implementation is usedos_unfair_lock_tInstead ofOSSpinLock.

Inline Implementations of the legacy OSSpinLock interfaces in terms of the primitives. Direct use of NOTE: The primitives are the primary ones. the locked value of os_unfair_lock is implementation defined and subject to change, code that relies on the specific locked value used by the legacy OSSpinLock interface WILL break when using these inline implementations in terms of os_unfair_lock.

In terms of the original interface in < OS /lock.h>, this is an inline implementation of the original OSSpinLock interface. It’s better to use these primitives directly. NOTE: The lock value in os_UNfair_LOCK is implementation-defined and subject to change. When these inline implementations are used, os_UNFAIR_LOCK is interrupted by code that relies on the specific lock values used by the older VERSIONS of the OSSpinLock interface.

Adding OSSPINLOCK_INLINE in front of a function tells the compiler to do its best to ensure that the modified function is implemented INLINE.

  #if __has_attribute(always_inline) // Try your best to keep functions inline
  #define OSSPINLOCK_INLINE static __inline
  #else
  #define OSSPINLOCK_INLINE static __inline __attribute__((__always_inline__))
  #endif

  #define OS_SPINLOCK_INIT 0 // Initialize to 0
  typedef int32_t OSSpinLock; // The type is still int32_t

  #if  __has_extension(c_static_assert)
  // If OSSpinLock and OS_UNfair_LOCK have different memory lengths, that is, incompatible types, we cannot guarantee that both OSSpinLock and OS_UNfair_LOCK can be correctly converted.
  _Static_assert(sizeof(OSSpinLock) == sizeof(os_unfair_lock), "Incompatible os_unfair_lock type"); 
  #endif
Copy the code
  • os_unfair_lockLock.
  OSSPINLOCK_INLINE
  void
  OSSpinLockLock(volatile OSSpinLock *__lock)
  {
      // Convert to os_unfair_lock_t.
      os_unfair_lock_t lock = (os_unfair_lock_t)__lock;
      return os_unfair_lock_lock(lock);
  }
Copy the code
  • os_unfair_lockTry locking.
OSSPINLOCK_INLINE
bool
OSSpinLockTry(volatile OSSpinLock *__lock)
{
    // Convert to os_unfair_lock_t.
    os_unfair_lock_t lock = (os_unfair_lock_t)__lock;
    return os_unfair_lock_trylock(lock);
}
Copy the code
  • os_unfair_lockUnlocked.
OSSPINLOCK_INLINE
void
OSSpinLockUnlock(volatile OSSpinLock *__lock)
{
    // Convert to os_unfair_lock_t.
    os_unfair_lock_t lock = (os_unfair_lock_t)__lock;
    return os_unfair_lock_unlock(lock);
}
Copy the code

#undef OSSPINLOCK_INLINE Unlocks the macro definition above.

  1. The last case.
#define OS_SPINLOCK_INIT 0 / / initialization
typedef int32_t OSSpinLock OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock); / / type int32_t
typedef volatile OSSpinLock *_os_nospin_lock_t
        OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock_t); / / named _os_nospin_lock_t

OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock_lock)
OS_NOSPIN_LOCK_AVAILABILITY
void _os_nospin_lock_lock(_os_nospin_lock_t lock); / / lock
#undef OSSpinLockLock // Unlocks the macro definition of the original API above
#define OSSpinLockLock(lock) _os_nospin_lock_lock(lock)

OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock_trylock)
OS_NOSPIN_LOCK_AVAILABILITY
bool _os_nospin_lock_trylock(_os_nospin_lock_t lock); // Try to lock
#undef OSSpinLockTry // Unlocks the above macro definition of the original API to determine whether a lock can be imposed
#define OSSpinLockTry(lock) _os_nospin_lock_trylock(lock)

OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock_unlock)
OS_NOSPIN_LOCK_AVAILABILITY
void _os_nospin_lock_unlock(_os_nospin_lock_t lock); / / unlock
#undef OSSpinLockUnlock // Unlocks the macro definition of the original API above
#define OSSpinLockUnlock(lock) _os_nospin_lock_unlock(lock)
Copy the code

At this point OSSpinLockDeprecated. H file code end, overall only four.

  1. OS_SPINLOCK_INITInitialization.
  2. OSSpinLockTry()Attempt to lock, return false if the lock is already held by another thread, return true otherwise, and the current thread will not be blocked even if the lock fails.
  3. OSSpinLockLock()Lock, failed to lock will wait, will block the current thread.
  4. OSSpinLockUnlockUnlocked.

OSSpinLock security issues

An OSSpinLock is not a thread-safe lock. Threads waiting for an OSSpinLock are in a busy-wait state, occupying CPU resources. (Like a while(1) loop, it queries the state of the lock continuously. Note that the runloop mechanism is also blocking, but the Runloop is like hibernation blocking, which does not cost CPU resources. This busy mechanism makes it more efficient than other locks. After all, there’s no wake-sleep or anything like that, so things can be done faster. Spinlocks are currently deprecated and can cause priority inversion.

For example, if the priority of A is higher than that of B, the priority of A is higher than that of B. If the priority of A is higher than that of B, the priority of A is higher than that of B. If the priority of B is higher than that of B, the priority of A is higher than that of B. Since A has A high priority, it will always occupy CPU resources without giving up time slices. In this way, B cannot obtain CPU resources to execute the task, resulting in failure to complete the task.

In the new version of iOS, the system maintains five different thread priorities /QoS: background, Utility, default, user-initiated, and user-interactive. A higher-priority thread always executes before a lower-priority thread, and a thread is not disturbed by a lower-priority thread. This thread scheduling algorithm creates a potential priority reversal problem that breaks spin lock.

Specifically, if a low-priority thread acquis a lock and accesses a shared resource, and a high-priority thread tries to acquire the lock, it will be in a busy state such as spin lock and consume a lot of CPU. In this case, low-priority threads cannot compete with high-priority threads for CPU time, causing tasks to be delayed and locks to be released. This wasn’t just a theoretical problem, libobjc had encountered this problem many times before, and apple engineers disabled OSSpinLock. Greg Parker, an Apple engineer, mentioned that one solution to this problem is to use truly unbounded backoff algorithm, which can avoid the livelock problem, but if the system load is high, it may still block the high-priority threads for tens of seconds. Another option is to use the Handoff Lock algorithm, which libobJC is currently using. The holder of the lock saves the thread ID inside the lock, and the wait for the lock temporarily contributes its priority to avoid the priority reversal problem. In theory this pattern can cause problems in more complex multi-lock conditions, but in practice everything is fine so far. Libobjc uses the Mach kernel thread_switch() and passes a Mach thread port to avoid priority inversion. It also uses a private parameter option, so developers cannot implement the lock themselves. OSSpinLock, on the other hand, cannot be changed due to binary compatibility issues. The bottom line is that all types of spin-locks in iOS cannot be used unless the developer can ensure that all threads accessing the lock are of the same priority. – OSSpinLock No longer Safe

os_unfair_lock

Os_unfair_lock is designed to replace OSSpinLock, which has been supported since iOS 10. Unlike OSSpinLock, the threads waiting for THE OS_UNfair_lock are hibernated (similar to Runloop). I’m not busy-wait.

Os_unfair_lock probe,

The first member variable in the struct SideTable definition is spinlock_t slock; Here we expand on spinlock_t.

struct SideTable {
    spinlock_tslock; . };Copy the code

Spinlock_t is actually a template class that uses a using declaration.

#if DEBUG
#   define LOCKDEBUG 1
#else
#   define LOCKDEBUG 0
#endif

template <bool Debug> class mutex_tt;
using spinlock_t = mutex_tt<LOCKDEBUG>;
Copy the code

So spinlock_t is actually a mutex, contrary to the name spinlock, which used to be OSSpinLock and was abandoned due to security issues due to priority inversion.

template <bool Debug>
class mutex_tt : nocopy_t { // Inherited from nocopy_t
    os_unfair_lock mLock;
 public:
    constexpr mutex_tt(a) : mLock(OS_UNFAIR_LOCK_INIT) {
        lockdebug_remember_mutex(this); }... };Copy the code

Nocopy_t, as the name suggests, removes the copy constructor and assignment operator generated by the compiler by default, while the constructor and destructor are still generated by the compiler.

// Mix-in for classes that must not be copied.
// The constructor and destructor are generated by the compiler by default, and the copy constructor and assignment operator are removed.
class nocopy_t {
  private:
    nocopy_t(const nocopy_t&) = delete;
    const nocopy_t& operator= (const nocopy_t&) = delete;
  protected:
    constexpr nocopy_t(a) = default;
    ~nocopy_t() = default;
};
Copy the code

The first member variable of the mute_tt class is os_unfair_lock mLock.

Os_unfair_lock feature

You can see the definition of OS_unfair_lock in usr/include/os/lock.h. To use OS_unfair_lock, you must first introduce #import < OS /lock.h>.

Os_unfair_lock specifies a simple API

The OS_UNfair_LOCK API is simple. Let’s start with an example of how to use it.

#import "ViewController.h"
#import <os/lock.h> // os_unfair_lock

@interface ViewController (a)
@property (nonatomic, assign) NSInteger sum;
@property (nonatomic, assign) os_unfair_lock unfairL;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    _sum = 100; // We only assign to the automatically generated _sum variable. We don't call the setters for sum
    self->_sum = 1000; // We only assign to the automatically generated _sum variable. We don't call the setters for sum
    
    // Make sure to distinguish between self. And C/C++. Is different. The. In OC calls getter/setter functions.
    // self-> XXX self-> XXX self-> XXX self-> XXX self-> XXX
    // in OC, it should be self->_xxx, _xxx is the corresponding member variable which is automatically generated by the XXX attribute
    // self is a pointer to a structure, so access to the pointer member variable must be self->_xxx, not self-> XXX
    
    "Self. unfairL = XXX" is equivalent to calling the setter for unfairL
    / / that [the self setUnfairL: OS_UNFAIR_LOCK_INIT];
    
    // use "XXX = self.unfaiL" or "self.unfairL",
    // This is equivalent to calling the getter function of unfairL and reading its value
    // Equivalent to calling the getter function: [self unfairL]
    
    // uint32_t _os_unfair_lock_opaque; // uint32_t _os_unfair_lock_opaque; // uint32_t _os_unfair_lock_opaque; } os_unfair_lock, *os_unfair_lock_t; * /
     
    self.unfairL = OS_UNFAIR_LOCK_INIT; / / initialization
    dispatch_queue_t globalQueue_DEFAULT = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    self.sum = 0;
    
    __weak typeof(self) _self = self;
    dispatch_async(globalQueue_DEFAULT, ^{
        __strong typeof(_self) self = _self;

        // Instead of using &self.unfairL,
        // This is equivalent to &[self unfairL]
        // You can't take the address like this
        / / & [self unfairL].
        Cannot take the address of an rvalue of type 'os_unfair_lock'
        // Failed to obtain the address of the rvalue of type "OS_UNfair_LOCK"
        // &self.unfairL;
        Address of property expression requested
        // Only &self->_unfairL can be used
        // Get the member variable _unfairL, and then get the address
        
        os_unfair_lock_lock(&self->_unfairL); / / lock
        // os_unfair_lock_lock(&self->_unfairL); // Repeat locking will crash directly
        
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        os_unfair_lock_unlock(&self->_unfairL); / / unlock
        NSLog(@"⏰ ⏰ ⏰ % ld." ", self.sum);
    });

    dispatch_async(globalQueue_DEFAULT, ^{
        __strong typeof(_self) self = _self;
        os_unfair_lock_lock(&self->_unfairL); / / lock
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        os_unfair_lock_unlock(&self->_unfairL); / / unlock
        NSLog(@"⚽ ️ ⚽ ️ ⚽ ️ % ld." ", self.sum);
    });
}
@end

/ / print:⚽ ️ ⚽ ️ ⚽ ️10000⏰ ⏰ ⏰20000
Copy the code

H File contents

The first is a macro definition that tells us when os_unfair_lock will appear. You can see that OS_Unfair_LOCK was first introduced in iOS 10.0.

#define OS_LOCK_API_VERSION 20160309
#defineOS_UNFAIR_LOCK_AVAILABILITY \ __API_AVAILABLE(MacOS (10.12), ios(10.0), TVOs (10.0), WATchos (3.0))
Copy the code

/ *!

  • @typedef os_unfair_lock
  • @abstract
  • Low-level lock that allows waiters to block efficiently on contention.
  • In general, higher level synchronization primitives such as those provided by
  • the pthread or dispatch subsystems should be preferred.
  • The values stored in the lock should be considered opaque and implementation
  • defined, they contain thread ownership information that the system may use
  • to attempt to resolve priority inversions.
  • This lock must be unlocked from the same thread that locked it, attempts to
  • unlock from a different thread will cause an assertion aborting the process.
  • This lock must not be accessed from multiple processes or threads via shared
  • or multiply-mapped memory, the lock implementation relies on the address of
  • the lock value and owning process.
  • Must be initialized with OS_UNFAIR_LOCK_INIT
  • @discussion
  • Replacement for the deprecated OSSpinLock. Does not spin on contention but
  • waits in the kernel to be woken up by an unlock.
  • As with OSSpinLock there is no attempt at fairness or lock ordering, e.g. an
  • unlocker can potentially immediately reacquire the lock before a woken up
  • waiter gets an opportunity to attempt to acquire the lock. This may be
  • advantageous for performance reasons, but also makes starvation of waiters a
  • possibility.

* /

To summarize the content of the above summary, it probably includes the following four points:

  1. os_unfair_lockIt’s a low level lock. Some high level locks should be our first choice in daily development.
  2. The same thread must be used to unlock the lock, and trying to unlock it using a different thread will result in an assertion to abort the process.
  3. The lock contains thread ownership information to solve the priority inversion problem.
  4. This lock cannot be accessed from multiple processes or threads through shared or multimapped memory, and the implementation of the lock depends on the address of the lock value and the owning process.
  5. You must use theOS_UNFAIR_LOCK_INITInitialize.

Os_unfair_lock_s is an os_unfair_lock_s structure with a typedef. Os_unfair_lock is an OS_unfair_lock_s structure. Os_unfair_lock_t is a pointer to OS_unfair_lock_s. Inside the structure is a uint32_T _OS_unfair_LOCK_opaque member variable.

OS_UNFAIR_LOCK_AVAILABILITY
typedef struct os_unfair_lock_s {
    uint32_t _os_unfair_lock_opaque;
} os_unfair_lock, *os_unfair_lock_t;
Copy the code

Initialize (os_unfair_lock){0} differently for different platforms or C++ versions.

  1. (os_unfair_lock){0}
  2. os_unfair_lock{}
  3. os_unfair_lock()
  4. {0}
#ifndef OS_UNFAIR_LOCK_INIT
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
#define OS_UNFAIR_LOCK_INIT ((os_unfair_lock){0}) / / ⬅ ️
#elif defined(__cplusplus) && __cplusplus >= 201103L
#define OS_UNFAIR_LOCK_INIT (os_unfair_lock{}) / / ⬅ ️
#elif defined(__cplusplus)
#define OS_UNFAIR_LOCK_INIT (os_unfair_lock()) / / ⬅ ️
#else
#define OS_UNFAIR_LOCK_INIT {0} / / ⬅ ️
#endif
#endif // OS_UNFAIR_LOCK_INIT
Copy the code
  • os_unfair_lock_lockLock.
/ *! * @function OS_UNfair_LOCK_lock * * @abstract * Locks an OS_unfair_lock. // Os_Unfair_lock * * @param lock * // The parameter is a Pointer to OS_unfair_lock */
OS_UNFAIR_LOCK_AVAILABILITY
OS_EXPORT OS_NOTHROW OS_NONNULL_ALL
void os_unfair_lock_lock(os_unfair_lock_t lock);
Copy the code
  • os_unfair_lock_trylockTry locking.
/ *! * @function OS_UNfair_LOCK_tryLOCK * * @abstract * Locks an OS_unfair_lock if it is not already locked Os_unfair_lock if it has not been previously locked. * * @discussion * It is invalid to surround this function with a retry loop, if this function * returns false, the program must be able to proceed without having acquired * the lock, or it must call os_unfair_lock_lock() directly (a retry loop around * os_unfair_lock_trylock() amounts to an inefficient  implementation of * os_unfair_lock_lock() that hides the lock waiter from the system and prevents * resolution of * If this function returns false, enclosing the function with a retry loop is invalid. The program must be able to handle the case where no lock is acquired to keep the program running. * Or you must call OS_unfair_LOCK_lock () directly (OS_unfair_LOCK_lock blocks the thread until the lock is acquired). * (the retry loop around OS_unfair_lock_trylock () is equal to an inefficient implementation of OS_unfair_lock_lock (), * * @param lock * Pointer to an OS_UNfair_LOCK. * The parameter is a Pointer to os_UNfair_LOCK. * * @result * Returns true if the lock was succesfully locked and false if the lock was already locked. Return false if it has been previously locked. * * /
OS_UNFAIR_LOCK_AVAILABILITY
OS_EXPORT OS_NOTHROW OS_WARN_RESULT OS_NONNULL_ALL
bool os_unfair_lock_trylock(os_unfair_lock_t lock);
Copy the code
  • os_unfair_lock_unlockUnlocked.
/ *! * @function OS_unfair_LOCK_UNLOCK * * @abstract * Unlocks AN OS_unfair_lock. // Unlock * * @param lock * Pointer to an os_unfair_lock. */
OS_UNFAIR_LOCK_AVAILABILITY
OS_EXPORT OS_NOTHROW OS_NONNULL_ALL
void os_unfair_lock_unlock(os_unfair_lock_t lock);
Copy the code
  • os_unfair_lock_assert_ownerCheck whether the current thread isos_unfair_lockOwner, otherwise the assertion is triggered.
/ *! * @function os_unfair_lock_assert_owner * * @abstract * Asserts that the calling thread is the current owner of the specified unfair lock. * * @discussion * If the lock is currently owned by the calling thread, This function returns. * If the lock is currently owned by the calling thread, this function returns. * * If the lock is unlocked or owned by a different thread, This function implies and terminates the process. * If the lock is unlocked or is owned by another thread, the assertion is performed. * * @param lock * Pointer to an os_unfair_lock. */
OS_UNFAIR_LOCK_AVAILABILITY
OS_EXPORT OS_NOTHROW OS_NONNULL_ALL
void os_unfair_lock_assert_owner(os_unfair_lock_t lock);
Copy the code
  • os_unfair_lock_assert_not_ownerContrary to above, if the current thread is specifiedos_unfair_lockThe owner of the.
/ *! * @function os_unfair_lock_assert_not_owner * * @abstract * Asserts that the calling thread is not the current owner of the specified unfair lock. * * @discussion * If the lock is unlocked or owned by a different thread, this function returns. * * If the lock is currently owned by the current thread, this function assertsand terminates the process. * * @param lock * Pointer to an os_unfair_lock. */
OS_UNFAIR_LOCK_AVAILABILITY
OS_EXPORT OS_NOTHROW OS_NONNULL_ALL
void os_unfair_lock_assert_not_owner(os_unfair_lock_t lock);
Copy the code
  • testos_unfair_lock_assert_owneros_unfair_lock_assert_not_owner.
dispatch_async(globalQueue_DEFAULT, ^{
    os_unfair_lock_assert_owner(&self->_unfairL);
});
os_unfair_lock_assert_not_owner(&self->_unfairL);
Copy the code

pthread_mutex_t

Pthread_mutex_t is a multithreaded mutex in C language. It is a cross-platform lock. Threads waiting for the lock are in hibernation state. When using recursive locking, it is safe to allow multiple threads to access a shared resource by allowing the same thread to repeatedly lock while the other thread waits. Pthread_mutex_t is used first by importing the header file #import .

PTHREAD_MUTEX_NORMAL // By default, that is, the common type. When a thread locks the lock, other threads that request the lock form a queue and acquire the lock on a first-in, first-out basis.
PTHREAD_MUTEX_ERRORCHECK If the same thread requests the same lock, EDEADLK is returned. Otherwise, the action is the same as the normal lock type. This ensures that nested deadlock does not occur when multiple locks are not allowed
PTHREAD_MUTEX_RECURSIVE // A recursive lock allows the same thread to successfully obtain the same lock multiple times and unlock it multiple times.
PTHREAD_MUTEX_DEFAULT // Adapt lock, the simplest type of lock, only wait to unlock after the re-competition, no wait queue.
Copy the code

Pthread_mutex_trylock is different from trylock. Trylock returns YES and NO. Pthread_mutex_trylock returns 0 on success and an error code on failure.

Pthread_mutex_t is simple to use

Pthread_mutex_t is initialized with different pthread_mutexattr_t to obtain different types of locks.

Mutex (PTHREAD_MUTEX_DEFAULT or PTHREAD_MUTEX_NORMAL)

#import "ViewController.h"
#import <pthread.h> // pthread_mutex_t

@interface ViewController (a)

@property (nonatomic, assign) NSInteger sum;
@property (nonatomic, assign) pthread_mutex_t lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.sum = 0;
    dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 1. Mutex. The default state is mutex
    // Initialize the properties
    pthread_mutexattr_t att;
    pthread_mutexattr_init(&att);
    
    // Set the property that describes the lock type
    pthread_mutexattr_settype(&att, PTHREAD_MUTEX_DEFAULT);
    
    // Initialize the lock
    pthread_mutex_init(&self->_lock, &att);
    // Destroy the property
    pthread_mutexattr_destroy(&att);

    __weak typeof(self) _self = self;
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if(! self)return;
    
        pthread_mutex_lock(&self->_lock);
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        pthread_mutex_unlock(&self->_lock);
        
        NSLog(@"😵 😵 😵 % ld." ", (long)self.sum);
    });
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if(! self)return;
        
        pthread_mutex_lock(&self->_lock);
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        pthread_mutex_unlock(&self->_lock);

        NSLog(@"👿 👿 👿 % ld." ", (long)self.sum);
    });
}

#pragma mark - dealloc

- (void)dealloc {
    NSLog(@"🧑‍🎤🧑‍🎤🧑‍🎤 dealloc simultaneously releases 🔒...");
    / / destruction of the lock
    pthread_mutex_destroy(&self->_lock);
}

@end

// Print 🖨️ :😵 😵 😵10000👿 👿 👿20000🧑🎤🧑🎤🧑 deallocCopy the code

Recursive locking (PTHREAD_MUTEX_RECURSIVE)

#import "ViewController.h"
#import <pthread.h> // pthread_mutex_t

static int count = 3;
@interface ViewController (a)

@property (nonatomic, assign) NSInteger sum;
@property (nonatomic, assign) pthread_mutex_t recursivelock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.sum = 0;
    dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 2. Recursive locking (PTHREAD_MUTEX_RECURSIVE)
    pthread_mutexattr_t recursiveAtt;
    pthread_mutexattr_init(&recursiveAtt);
    
    // Set the property that describes the lock type
    pthread_mutexattr_settype(&recursiveAtt, PTHREAD_MUTEX_RECURSIVE);
    
    // Initialize the lock
    pthread_mutex_init(&self->_recursivelock, &recursiveAtt);
    // Destroy the property
    pthread_mutexattr_destroy(&recursiveAtt);
    
    __weak typeof(self) _self = self;
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if(! self)return;
        
        pthread_mutex_lock(&self->_recursivelock);
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        pthread_mutex_unlock(&self->_recursivelock);

        NSLog(@"😵 😵 😵 % ld." ", (long)self.sum);
    });
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if(! self)return;
        
        // Recursive lock validation
        [self recursiveAction];
    });
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if(! self)return;
        
        pthread_mutex_lock(&self->_recursivelock);
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        pthread_mutex_lock(&self->_recursivelock);
        
        NSLog(@"👿 👿 👿 % ld." ", (long)self.sum);
    });
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if(! self)return;
        
        // Recursive lock validation
        [self recursiveAction];
    });
}

#pragma mark - Private Methods
- (void)recursiveAction {
    pthread_mutex_lock(&self->_recursivelock);
    
    NSLog(@"😓 😓 😓 count = % d", count);
    if (count > 0) {
        count--;
        [self recursiveAction];
    }

    // else {// If the thread is single, there is no problem with adding a recursive exit
    // return;
    // }
    
    pthread_mutex_unlock(&self->_recursivelock);
    count = 3;
}

#pragma mark - dealloc
- (void)dealloc {
    NSLog(@"🧑‍🎤🧑‍🎤🧑‍🎤 dealloc simultaneously releases 🔒...");
    pthread_mutex_destroy(&self->_recursivelock);
}

@end

/ / print 🖨 ️ :😵 😵 😵10000😓 😓 😓 count =3😓 😓 😓 count =2😓 😓 😓 count =1😓 😓 😓 count =0👿 👿 👿20000😓 😓 😓 count =3😓 😓 😓 count =2😓 😓 😓 count =1😓 😓 😓 count =0🧑🎤🧑🎤🧑 deallocCopy the code

Conditions for the lock

First set the following scenario, two threads A and B, A thread to perform the deletion of array elements, B thread to perform the addition of array elements, because we do not know which thread will execute first, so we need to lock the implementation, only after the addition can perform the deletion operation, adding conditions for the mutex can achieve. Thread dependency can be achieved in this way.

#import "ViewController.h"

#import <pthread.h> // pthread_mutex_t

@interface ViewController (a)

@property (nonatomic, strong) NSMutableArray *dataArr;
@property (nonatomic, assign) pthread_mutex_t lock;
@property (nonatomic, assign) pthread_cond_t condition;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // Initialize the array
    self.dataArr = [NSMutableArray array];
    
    // Initialize the lock
    pthread_mutexattr_t att;
    pthread_mutexattr_init(&att);
    pthread_mutexattr_settype(&att, PTHREAD_MUTEX_DEFAULT);
    pthread_mutex_init(&self->_lock, &att);
    pthread_mutexattr_destroy(&att);

    // Initialize conditions
    pthread_cond_init(&self->_condition, NULL);
    
    dispatch_queue_t global_DEFAULT = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t global_HIGH = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

    __weak typeof(self) _self = self;
    
    dispatch_async(global_HIGH, ^{
        __strong typeof(_self) self = _self;
        if(! self)return;
        
        pthread_mutex_lock(&self->_lock);
        NSLog(@"🧑 ‍ 💻 🧑 ‍ 💻 🧑 ‍ 💻 delete begin");
        
        if (self.dataArr.count < 1) {
            pthread_cond_wait(&self->_condition, &self->_lock);
        }
        
        [self.dataArr removeLastObject];
        NSLog(@"Array performs deletion operation");
        pthread_mutex_unlock(&self->_lock);
    });
    
    dispatch_async(global_DEFAULT, ^{
        __strong typeof(_self) self = _self;
        if(! self)return;
        
        pthread_mutex_lock(&self->_lock);
        NSLog(@"🧑 ‍ 💻 🧑 ‍ 💻 🧑 ‍ 💻 add begin");
        
        [self.dataArr addObject:@"CHM"];
        pthread_cond_signal(&self->_condition);
        
        NSLog(@"Array performs add element operation");
        pthread_mutex_unlock(&self->_lock);
    });

    NSThread *deThread = [[NSThread alloc] initWithTarget:self selector:@selector(deleteObj) object:nil];
    [deThread start];

    // Sleep for 1 second to make sure that the thread that deleted the element gets the lock first
    sleep(1);

    NSThread *addThread = [[NSThread alloc] initWithTarget:self selector:@selector(addObj) object:nil];
    [addThread start];
}

#pragma mark - Private Methods

- (void)deleteObj {
    pthread_mutex_lock(&self->_lock);

    NSLog(@"🧑 ‍ 💻 🧑 ‍ 💻 🧑 ‍ 💻 delete begin");
    // Add a judgment and a condition if no data is available
    
    if (self.dataArr.count < 1) {
        // Add a condition, if the array is empty, then add the waiting thread to sleep and release the lock. This will release the lock, so that the following addObj thread can acquire the lock
        // When a signal is received, the lock is reimposed and the execution continues downward
        pthread_cond_wait(&self->_condition, &self->_lock);
    }
    
    [self.dataArr removeLastObject];
    NSLog(@"Array performs deletion operation");

    pthread_mutex_unlock(&self->_lock);
}

- (void)addObj {
    pthread_mutex_lock(&self->_lock);

    NSLog(@"🧑 ‍ 💻 🧑 ‍ 💻 🧑 ‍ 💻 add begin");
    [self.dataArr addObject:@"HTI"];
    
    // Send a signal indicating that the element has been added
    pthread_cond_signal(&self->_condition);
    
    NSLog(@"Array performs add element operation");
    pthread_mutex_unlock(&self->_lock);
}

#pragma mark - dealloc

- (void)dealloc {
    NSLog(@"🧑‍🎤🧑‍🎤🧑‍🎤 dealloc simultaneously releases 🔒...");
    
    pthread_mutex_destroy(&self->_lock);
    pthread_cond_destroy(&self->_condition);
}

@end

/ / print 🖨 ️ :🧑 💻 🧑 💻 🧑 💻deleteThe begin 🧑 💻 🧑 💻 🧑 💻 add the begin array add elements to delete an array element operation execution 🧑 🎤 🧑 🎤 🧑 🎤 dealloc releasing 🔒...Copy the code

NSLock

It is derived from NSObject and follows the NSLocking protocol. The lock method locks the lock, the unlock method unlocks the lock, and the tryLock method tries to lock the lock. If true is returned, the lock succeeded, false is returned, the lock failed. Remember that the returned BOOL represents the success or failure of the locking action, not the ability to lock, and even a failure to lock will not block the current thread. LockBeforeDate: Attempts to lock before the specified Date. If the lock cannot be locked before the specified Date, NO is returned and the current thread is blocked. It can be used to estimate how long it will take for the last critical section to complete, and then lock another section after that time.

  1. Based on themutexBasic lock encapsulation, more object oriented, threads waiting for a lock are hibernated.
  2. Comply with theNSLockingAgreement,NSLockingThere are only two methods in the protocol-(void)lock-(void)unlock.
  3. Methods you might use:
  4. Initialization and otherOCObject, directlyallocinitOperation.
  5. -(void)lock;Lock.
  6. -(void)unlock;Unlocked.
  7. -(BOOL)tryLock;Try locking.
  8. -(BOOL)lockBeforeDate:(NSDate *)limit;Wait for a lock to be added before a certain point in time.
  9. Call consecutively in the main line[self.lock lock]Causes a deadlock on the main thread.
  10. No fetch on main threadLockIn the case of and in the acquisitionLockIn the case of two consecutive [self.lock unlock]Nothing happens. (Other locks may be unlocked consecutivelycrash, has not yet come and test)
  11. Continuous in child threads[self.lock lock]Will result in a deadlock while other sublines acquireself.lockThey will wait forever.
  12. At the same time, child thread deadlock causesViewControllerDon’t release.

NSLock use

@interface ViewController (a)

@property (nonatomic, assign) NSInteger sum;
@property (nonatomic, strong) NSLock *lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.sum = 0;
    self.lock = [[NSLock alloc] init];
    dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    __weak typeof(self) _self = self;
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if(! self)return;
        
        // If the lock fails here, the current thread is blocked, and the following code does not execute.
        // Until the lock is released by another thread and it can be locked, the following code will not be executed
        [self.lock lock];
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        [self.lock unlock];
        
        NSLog(@"👿 👿 👿 % ld." ", (long)self.sum);
    });
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if(! self)return;
        
        // If the lock fails here, the current thread is blocked, and the following code does not execute.
        // Until the lock is released by another thread and it can be locked, the following code will not be executed
        [self.lock lock];
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        [self.lock unlock];
        
        NSLog(@"😵 😵 😵 % ld." ", (long)self.sum);
    });
}

#pragma mark - dealloc
- (void)dealloc {
    NSLog(@"🧑‍🎤🧑‍🎤🧑‍🎤 dealloc simultaneously releases 🔒...");
}

@end

// Print the result:😵 😵 😵20000👿 👿 👿10000🧑🎤🧑🎤🧑 dealloc__weak typeof(self) _self = self;
/ / thread 1
dispatch_async(global_queue, ^{
    __strong typeof(_self) self = _self;
    if(! self)return;

    [self.lock lock];
    for (unsigned int i = 0; i < 10000; ++i) {
        self.sum++;
    }
    sleep(3);
    [self.lock unlock];
    NSLog(@"👿 👿 👿 % ld." ", (long)self.sum);
});

/ / thread 2
dispatch_async(global_queue, ^{
    __strong typeof(_self) self = _self;
    if(! self)return;
    sleep(1); // Make sure thread 1 gets the lock first
    
    // If 1 is used here, the lock cannot be acquired at this point in time
    // If a number greater than 2 is used, the lock is obtained
    // The if function blocks the current thread
    if ([self.lock lockBeforeDate: [NSDate dateWithTimeIntervalSinceNow:1]]) {
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        [self.lock unlock];
    } else {
        NSLog(@"LockBeforeDate fails, does it come directly here, does it not block the current thread?");
    }
    
    NSLog(@"😵 😵 😵 % ld." ", (long)self.sum);
});
Copy the code

[the self lock lockBeforeDate: [NSDate dateWithTimeIntervalSinceNow: 1]], lockBeforeDate: The lock () method will attempt to lock thread 2 until the Date is specified, and this process will block thread 2. If the lock cannot be locked until the Date is specified, return false, and if it can be locked before the Date is specified, return true. _priv and name, check each stage, _priv is always NULL. The name is used for identification and is used as the name of the lock when output. If there are three threads, then a lock is placed on one thread, and the rest of the threads requesting the lock form a waiting queue. This result can be tested by changing the priority of the threads.

NSRecursiveLock

NSRecursiveLock is a recursive lock and differs from NSLock in that it can be repeated in the same thread without causing a deadlock. NSRecursiveLock records the number of locks and unlocks. When the number of locks and unlocks is equal, this thread releases the lock and other threads can successfully lock.

  1. withNSLockAgain, it’s based onmutexThe encapsulation is based onmutexThe encapsulation of a recursive lock, so this is a recursive lock.
  2. Comply with theNSLockingAgreement,NSLockingThere are only two methods in the protocol-(void)lock-(void)unlock.
  3. Methods you might use:
  4. It inherits from NSObject, so initialization is just like any other OC object, alloc and init.
  5. -(void)lock;lock
  6. -(void)unlock;unlock
  7. -(BOOL)tryLock;Try to lock
  8. -(BOOL)lockBeforeDate:(NSDate *)limit;Wait for a lock to be added before a certain point in time.
  9. A recursive lock can be called consecutively on the same threadlockDoes not directly cause a blocking deadlock, but must still be executed an equal number of timesunlock. Otherwise, the asynchronous thread acquires the recursive lock again, resulting in a blocking deadlock for the asynchronous thread.
  10. Recursive lock allows the same thread to lock multiple times, different threads to enter the lock entry will be in a waiting state, need to wait for the previous thread to unlock complete before entering the lock state.

NSRecursiveLock use

It actually implements the recursive locking scenario that pthread_mutex_t and PTHREAD_MUTEX_RECURSIVE do above, except that the NSRecursiveLock API is more streamlined and easier to use.

#import "ViewController.h"

static int count = 3;

@interface ViewController (a)

@property (nonatomic, assign) NSInteger sum;
@property (nonatomic, strong) NSLock *lock;
@property (nonatomic, strong) NSRecursiveLock *recursiveLock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.sum = 0;
    self.recursiveLock = [[NSRecursiveLock alloc] init];
    dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    __weak typeof(self) _self = self;
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if(! self)return;
    
        [self.recursiveLock lock];
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        [self.recursiveLock unlock];
        
        NSLog(@"👿 👿 👿 % ld." ", (long)self.sum);
    });
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if(! self)return;
        
        [self recursiveAction];
    });
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if(! self)return;
        
        [self.recursiveLock lock];
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        [self.recursiveLock unlock];
        
        NSLog(@"😵 😵 😵 % ld." ", (long)self.sum);
    });
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if(! self)return;
        
        [self recursiveAction];
    });
}

#pragma mark - Private Methods

- (void)recursiveAction {
    [self.recursiveLock lock];
    NSLog(@"😓 😓 😓 count = % d", count);
    if (count > 0) {
        count--;
        [self recursiveAction];
    }

    // else {// If the thread is single, there is no problem with adding a recursive exit
    // return;
    // }

    [self.recursiveLock unlock];
    count = 3;
}

#pragma mark - dealloc
- (void)dealloc {
    NSLog(@"🧑‍🎤🧑‍🎤🧑‍🎤 dealloc simultaneously releases 🔒...");
}

@end
// Print the result:😓 😓 😓 count =3👿 👿 👿10000😓 😓 😓 count =2😓 😓 😓 count =1😓 😓 😓 count =0😵 😵 😵20000😓 😓 😓 count =3😓 😓 😓 count =2😓 😓 😓 count =1😓 😓 😓 count =0
Copy the code
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {static void (^RecursieveBlock)(int);
  
  RecursiveBlock = ^(int value) {
      [lock lock];
      if (value > 0) {
          NSLog(@"value: %d", value);
          
          RecursiveBlock(value - 1);
      }
      [lock unlock];
  };
  
  RecursiveBlock(2);
});
Copy the code

In the example above, if NSLock is used, the lock will be locked first, but when the lock is not executed, it will enter the next layer of recursion and request the lock again, blocking the thread. The thread will be blocked, and naturally the subsequent unlock code will never be executed, resulting in a deadlock. NSRecursiveLock is designed to solve this problem.

NSCondition

The object of the NSCondition actually acts as a lock and a thread checker. After the lock is locked, other threads can also lock it, and then decide whether to continue running the thread based on the condition that the thread is in the waiting state. Instead, it goes directly into the waiting state, and when the lock in another thread executes the signal or broadcast method, the thread wakes up and continues to run the following method. That is, the model using NSCondition is as follows: 1. Lock the condition object. 2. Test whether the next task can be safely performed. If the Boolean value is false, the wait or waitUntilDate: methods of the conditional object are called to block the thread. Return from these methods, and go to Step 2 to retest your Boolean values. Continue to wait for the signal and retest until it is safe to perform the next task. The waitUntilDate method has a wait time limit. When the specified time is up, it returns NO and continues with the next task. While waiting for a signal, the thread executes the signal sent by lock signal. The difference between “signal” and “broadcast” is that “signal” is only a semaphore that can wake up only one waiting thread, and “broadcast” can wake up all waiting threads. If there are no waiting threads, both methods have no effect.

  1. Based on themutexLock andcontConditional encapsulation, so it’s a mutex and comes with a condition that the thread waiting for the lock sleeps.
  2. Comply with theNSLockingAgreement,NSLockingThere are only two methods in the protocol-(void)lock-(void)unlock.
  3. Methods that might be used
  4. Initialization is done directly, just like any other OC objectallocinitOperation.
  5. -(void)lock;lock
  6. -(void)unlock;unlock
  7. -(BOOL)tryLock;Try to lock
  8. -(BOOL)lockBeforeDate:(NSDate *)limit;Wait for a lock to be added before a certain point in time
  9. -(void)wait;Wait conditions (release the lock when entering hibernation, and re-lock when awakened)
  10. -(void)signal;Send a signal to activate the thread waiting for the condition, bearing in mind that the thread received it from the WAIT state
  11. - (void)broadcast;Send a broadcast signal to activate all threads waiting for the condition, keeping in mind that the thread received it was in a WAIT state

NSCondition use

#import "ViewController.h"

@interface ViewController (a)

@property (nonatomic, strong) NSMutableArray *dataArr;
@property (nonatomic, strong) NSCondition *condition;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    // Initialize the array
    self.dataArr = [NSMutableArray array];
    
    self.condition = [[NSCondition alloc] init];
    dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    __weak typeof(self) _self = self;
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if(! self)return;
        
        [self deleteObj];
    });
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if(! self)return;
        
        [self deleteObj];
    });
    
    // sleep for 0.5 seconds to ensure that the lock is acquired before deleting the element
    sleep(0.5);
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if(! self)return;
        
        [self addObj];
    });
}

#pragma mark - Private Methods

- (void)deleteObj {
    [self.condition lock];
    NSLog(@"🧑 ‍ 💻 🧑 ‍ 💻 🧑 ‍ 💻 delete begin");
    
    // Add a judgment and a condition if no data is available
    if (self.dataArr.count < 1) {
        // Add a condition, if the array is empty, then add the waiting thread to sleep and release the lock. This will release the lock, so that the following addObj thread can acquire the lock
        // When a signal is received, the lock is reimposed and the execution continues downward
        
        NSLog(@"Next is to enter wait...");
        [self.condition wait];
        
        // Continue to execute when the broadcast is coming,
        // Do not start from the deleteObj function header, start from here
        // So when the first asynchronous removes an element from the array, the second asynchronous comes in and the array is empty
        NSLog(@"The starting point of a function after receiving a broadcast or signal");
    }
    
    NSLog(@"% @", self.dataArr);
    [self.dataArr removeLastObject];
    NSLog(@"🧑‍💻🧑‍💻🧑‍💻 array to delete elements");
    [self.condition unlock];
}

- (void)addObj {
    [self.condition lock];
    NSLog(@"🧑 ‍ 💻 🧑 ‍ 💻 🧑 ‍ 💻 add begin");
    
    [self.dataArr addObject:@"CHM"];
    
    // Send a signal indicating that the element has been added
    // [self.condition signal];
    // Notify all eligible threads
    [self.condition broadcast];
    
    NSLog(@"🧑‍💻🧑‍💻🧑‍💻 array performs add element operation");
    [self.condition unlock];
}

#pragma mark - dealloc
- (void)dealloc {
    NSLog(@"🧑‍🎤🧑‍🎤🧑‍🎤 dealloc simultaneously releases 🔒...");
}
@end

// Print the result:

}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
// It is not locked because self. Condition was acquired first by one thread and the other thread is in the blocked wait state.🧑 💻 🧑 💻 🧑 💻deleteBegin to wait... 🧑 💻 🧑 💻 🧑 💻deleteBegin to wait... 🧑 💻 🧑 💻 🧑 💻 add the begin 🧑 💻 🧑 💻 🧑 💻 add elements operation on array receives the broadcast or signal after the function of the starting point (CHM) 🧑 💻 🧑 💻 🧑 💻 array element deletion operation to receive Broadcast or signal after the function start () 🧑 💻 🧑 💻 🧑 💻 array element deletion operation 🧑 🎤 🧑 🎤 🧑 🎤 dealloc releasing 🔒...Copy the code

NSConditionLock

NSConditionLock is just like NSLock, it inherits from NSObject, it follows the NSLocking protocol, it unlocks, it tries, it’s just a condition, And each operation has an additional method on the condition attribute, such as tryLock and tryLockWhenCondition:, NSConditionLock can be called a conditional lock. A lock can only be properly locked if the condition parameter is the same as when it was initialized or is the same as the condition set after the last unlock. UnlockWithCondition: It is not unlocked when the condition meets the conditions, but when unlocked, the value of the condition is changed as an input. When unlocked, the value of the condition remains the same. Condition defaults to 0 if init is used. LockWhenCondition: Similar to the lock method, failure to lock blocks the current thread and waits until the lock is successfully applied. TryLockWhenCondition: similar to tryLock, an attempt to lock the thread will not block if the lock fails, but the attempt succeeds only if the lock is idle and the condition meets the conditions. As you can see from the above, NSConditionLock can also implement dependencies between tasks.

  1. Based on theNSConditionFurther encapsulation, you can set the condition value more advanced.

If you have three threads (A, B, and C), then you need to execute B after A, and then you need to execute C after B, then you need to add dependencies between the threads. NSConditionLock is A convenient way to do this.

  1. Comply with theNSLockingAgreement,NSLockingThere are only two methods in the protocol-(void)lock-(void)unlock.
  2. Possible methods:
  3. Initialization is as straightforward as any other OC objectallocinitWithCondition:(NSInteger)conditionOperation; (If usinginitMethods,conditionThe default is 0).
  4. One of the properties is@property(readonly) NSInteger condition;This parameter is used to set the value of the condition. If it is not set, it defaults to zero.
  5. -(void)lock;Lock it directly.
  6. -(void)lockWhenCondition:(NSInteger)condition;According to theconditionValue is locked if the input parameter and the currentconditionIf not, no.
  7. -(void)unlockWithCondition:(NSInteger)condition;Unlock and setconditionThe value of is the incoming parameter.
  8. -(BOOL)tryLock;Try locking.
  9. -(BOOL)lockBeforeDate:(NSDate *)limit;Wait for a lock to be added before a certain point in time.

NSConditionLock use

#import "ViewController.h"

@interface ViewController (a)

@property (nonatomic, strong) NSConditionLock *lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.lock = [[NSConditionLock alloc] initWithCondition:0];
    [self createThreads];
}

#pragma mark - Private Methods

- (void)createThreads {
    // The order of execution is a-b-c, but since we are in the child thread, we can't determine who is going to execute first. Add sleep to make the problem more obvious. C line starts first, then B, then A.
    NSThread *c = [[NSThread alloc] initWithTarget:self selector:@selector(threadC) object:nil];
    [c start];
    sleep(0.2);
    
    NSThread *b = [[NSThread alloc] initWithTarget:self selector:@selector(threadB) object:nil];
    [b start];
    sleep(0.2);
    
    NSThread *a = [[NSThread alloc] initWithTarget:self selector:@selector(threadA) object:nil];
    [a start];
}

- (void)threadA {
    NSLog(@"A begin");
    [self.lock lockWhenCondition:0]; // In this case, the lock can be successfully applied only if the Condition value is 0
    NSLog(@"A threadExcute");
    [self.lock unlockWithCondition:1]; // Unlock and set the Condition to 1
    // [self unlock]; // If unlock is used here, it causes a deadlock between B and C threads and the ViewController is not released
}

- (void)threadB {
    NSLog(@"B begin");
    [self.lock lockWhenCondition:1]; // In this case, the lock can be successfully added only when the Condition value is 1
    NSLog(@"B threadExcute");
    [self.lock unlockWithCondition:2]; // Unlock and set Condition to 2
}

- (void)threadC {
    NSLog(@"C begin");
    [self.lock lockWhenCondition:2]; // In this case, the lock can be successfully added only when the Condition value is 2
    NSLog(@"C threadExcute");
    [self.lock unlock]; / / unlock
}

#pragma mark - dealloc
- (void)dealloc {
    NSLog(@"🧑‍🎤🧑‍🎤🧑‍🎤 dealloc simultaneously releases 🔒...");
}

// Print the result:
// Although the boot order is C B A, the execution order is A B C, which is controlled by the Condition. The lock is successful only when the Condition matches. Otherwise, the block waitsC begin B begin A begin A threadExcute B threadExcute C threadExcute 🧑🎤🧑🎤🧑 🧑 deallocCopy the code
  1. [self.lock unlock];After performingconditionIt stays the same, it’s the same value that was initialized or the last time it was executedlockWhenCondition:When the value of the.
  2. Threads A, B, and C must be locked and unlockedViewControllerOnly the last thread can be used directlyunlockPerform the unlock outside of the first two threadsunlockWithCondition:The argumentsconditionThe values of must sumNSConditionLockThe currentconditionValues of. Make sure that every thread islockunlock, causes the thread to block and wait,ViewControllerNo release.
  3. Continuous in the same thread[self.lock lockWhenCondition:1];It will block the deadlock. It won’t workconditionWhether or not to the current lockconditionAre equal to each other, resulting in a blocking deadlock.

NSLocking, NSLock, NSConditionLock, NSRecursiveLock and NSCondition are defined

#import <Foundation/NSObject.h>

@class NSDate;

NS_ASSUME_NONNULL_BEGIN

// NSLocking protocol. The preceding lock type starts with NS and complies with this protocol
@protocol NSLocking // The NSLocking protocol has only two protocol methods: locking and unlocking

- (void)lock;
- (void)unlock;

@end

@interface NSLock : NSObject <NSLocking> { // NSLock is inherited from NSObject and follows the NSLocking protocol
@private
    void *_priv;
}

- (BOOL)tryLock; // Attempts to lock, returns true for success
- (BOOL)lockBeforeDate:(NSDate *)limit; // Lock before an NSDate

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

Lock / / conditions
@interface NSConditionLock : NSObject <NSLocking> { // Inherits from NSObject and follows the NSLocking protocol
@private
    void *_priv;
}

- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition; // The read-only condition attribute
- (void)lockWhenCondition:(NSInteger)condition; // If the value of the condition is not satisfied, the lock is not added;

- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition; 

- (void)unlockWithCondition:(NSInteger)condition; // Unlock and set the value of condition;
- (BOOL)lockBeforeDate:(NSDate *)limit; // Wait for a lock before a certain point in time
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

/ / recursive locking
@interface NSRecursiveLock : NSObject <NSLocking> { // Inherits from NSObject and follows the NSLocking protocol
@private
    void *_priv;
}

- (BOOL)tryLock; // Attempts to lock, returns true for success
- (BOOL)lockBeforeDate:(NSDate *)limit; // Lock before an NSDate

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0))
@interface NSCondition : NSObject <NSLocking> { // Inherits from NSObject and follows the NSLocking protocol
@private
    void *_priv;
}

- (void)wait; // Add wait, hibernate the thread, and surrender the lock
- (BOOL)waitUntilDate:(NSDate *)limit; / / an NSDate
- (void)signal; // Send a signal telling the waiting thread that the condition is met
- (void)broadcast; // Notify all eligible threads, (notify all waiting threads)

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

NS_ASSUME_NONNULL_END
Copy the code

Refer to the link

Reference link :🔗

  • spinlocks
  • OSSpinLock is no longer secure
  • IOS Locking part one
  • How to understand locking in iOS development?
  • IOS common knowledge (3) : Lock
  • IOS Lock -OSSpinLock and OS_UNfair_LOCK
  • os_unfair_lock pthread_mutex
  • IOS Locking part one
  • IOS Locking part two
  • IOS Locking part three
  • Several ways to ensure thread safety in iOS and performance comparison
  • Here’s more than you might want to know about @synchronized