• [iOS开发]iOS中的相关锁


    锁作为一种非强制的机制,被用来保证线程安全。每一个线程在访问数据或者资源前,要先获取(Acquire)锁,并在访问结束之后释放(Release)锁。如果锁已经被占用,其它试图获取锁的线程会等待,直到锁重新可用。
    注:不要将过多的其他操作代码放到锁里面,否则一个线程执行的时候另一个线程就一直在等待,就无法发挥多线程的作用了。
    在iOS中锁的基本种类只有两种:互斥锁、自旋锁,其他的比如条件锁、递归锁、信号量都是上层的封装和实现。

    在这里插入图片描述

    一、NSSpinLock

    iOS10之后被弃用,使用os_unfair_lock替代。

    typedef int32_t OSSpinLock OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock); 
    
    • 1

    线程反复检查锁变量是否可用。由于线程在这一过程中保持执行, 因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。 自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。
    OSSpinLock 就是典型的自旋锁。自旋锁的特点是在没有获取到锁时,锁已经被添加,还没有被解开时,OSSpinLock处于忙等状态,一直占用CPU,类似如下伪代码:

    while(锁没解开);
    
    • 1

    优先级反转:
    由于线程调度,每条线程的分配时间权重不一样,当权重小的线程先进入OSSpinLock优先加锁,当权重大的线程再来访问,就阻塞在这,可能权重大的线程会一直分配到cpu所以一直会进来,但是因为有锁,只能等待,权重小的线程得不到cpu资源分配,所以不会解锁,造成一定程度的死锁。

    // 初始化
    OSSpinLock spinLock = OS_SPINLOCK_INIT;
    // 加锁
    OSSpinLockLock(&spinLock);
    // 解锁
    OSSpinLockUnlock(&spinLock);
    // 尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO
    OSSpinLockTry(&spinLock)
    /*
    OSSpinLock已经不再线程安全,OSSpinLock有潜在的优先级反转问题
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    (一)atomic

    自旋锁的实际应用,自动生成的setter方法会根据修饰符不同调用不同方法,最后统一调用reallySetProperty方法,其中就有一段关于atomic修饰词的代码。

    static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
    {
        if (offset == 0) {
            object_setClass(self, newValue);
            return;
        }
    
        id oldValue;
        id *slot = (id*) ((char*)self + offset);
    
        if (copy) {
            newValue = [newValue copyWithZone:nil];
        } else if (mutableCopy) {
            newValue = [newValue mutableCopyWithZone:nil];
        } else {
            if (*slot == newValue) return;
            newValue = objc_retain(newValue);
        }
    
        if (!atomic) {
            oldValue = *slot;
            *slot = newValue;
        } else {
            spinlock_t& slotlock = PropertyLocks[slot];
            slotlock.lock();
            oldValue = *slot;
            *slot = newValue;        
            slotlock.unlock();
        }
    
        objc_release(oldValue);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    比对一下atomic的逻辑分支:

    • 原子性修饰的属性进行了spinlock加锁处理
    • 非原子性的属性除了没加锁,其他逻辑与atomic一般无二

    前面刚说OSSpinLock因为安全问题被废弃了,但苹果源码怎么还在使用呢?其实点进去就会发现用os_unfair_lock替代了OSSpinLock(iOS10之后替换)。

    using spinlock_t = mutex_tt<LOCKDEBUG>;
    class mutex_tt : nocopy_t {
        os_unfair_lock mLock;
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    getter方法也是如此:atomic修饰的属性进行加锁处理。

    id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
        if (offset == 0) {
            return object_getClass(self);
        }
    
        // Retain release world
        id *slot = (id*) ((char*)self + offset);
        if (!atomic) return *slot;
            
        // Atomic retain release world
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        id value = objc_retain(*slot);
        slotlock.unlock();
        
        // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
        return objc_autoreleaseReturnValue(value);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    atomic修饰的属性绝对安全吗?

    atomic只能保证settergetter方法的线程安全,并不能保证数据安全

    (二)读写锁

    读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的CPU数
    写者是排他性的,⼀个读写锁同时只能有⼀个写者或多个读者(与CPU数相关),但不能同时既有读者⼜有写者。在读写锁保持期间也是抢占失效的
    如果读写锁当前没有读者,也没有写者,那么写者可以⽴刻获得读写锁,否则它必须⾃旋在那⾥,直到没有任何写者或读者。如果读写锁没有写者,那么读者可以⽴即获得该读写锁,否则读者必须⾃旋在那⾥,直到写者释放该读写锁。

    // 导入头文件
    #import <pthread.h>
    //普通初始化
    // 全局声明读写锁
    pthread_rwlock_t lock;
    // 初始化读写锁
    pthread_rwlock_init(&lock, NULL);
    //宏定义初始化
    pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;
    
    // 读操作-加锁
    pthread_rwlock_rdlock(&lock);
    // 读操作-尝试加锁
    pthread_rwlock_tryrdlock(&lock);
    // 写操作-加锁
    pthread_rwlock_wrlock(&lock);
    // 写操作-尝试加锁
    pthread_rwlock_trywrlock(&lock);
    // 解锁
    pthread_rwlock_unlock(&lock);
    // 释放锁
    pthread_rwlock_destroy(&lock);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    二、互斥锁

    pthread_mutex就是互斥锁——当锁被占用,而其他线程申请锁时,不是使用忙等,而是阻塞线程并睡眠。是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区,从而达成效果。
    这里有两个要注意的点:互斥跟同步:

    • 互斥就是当多个线程进行同一操作的时候,同一时间只有一个线程可以进行操作。
    • 同步是多个线程进行同一操作的时候,按照相应的顺序执行。

    互斥锁又分为两种情况,可递归和不可递归。
    互斥锁(Mutual exclusion,缩写Mutex)是防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒。互斥锁又分为:

    • 递归锁:可重入锁,同一个线程在锁释放前可再次获取锁,即可以递归调用(比如@synchronizedNSRecursiveLock
    • 非递归锁:不可重入,必须等锁释放后才能再次获取锁

    os_unfair_lockpthread_mutex是典型的互斥锁,在没有获取到锁时锁已经被添加;还没有被解开时,它们都会让当前线程进入休眠状态,即不占用CPU资源。但是为什么,互斥锁比自旋锁的效率低呢,是因为休眠,以及唤醒休眠,比忙等更加消耗CPU资源。
    NSLock 封装的pthread_mutexPTHREAD_MUTEX_NORMAL 模式

    (一)NSLock(互斥锁、对象锁)

    NSLock是对互斥锁的简单封装,使用如下:

    // 初始化
    NSLock *_lock = [[NSLock alloc]init];
    // 加锁
    [_lock lock];
    // 解锁
    [_lock unlock];
    // 尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO
    [_lock tryLock];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    - (void)test {
        self.testArray = [NSMutableArray array];
        NSLock *lock = [[NSLock alloc] init];
        for (int i = 0; i < 200000; i++) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                [lock lock];
                self.testArray = [NSMutableArray array];
                [lock unlock];
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    NSLockAFNetworkingAFURLSessionManager.m中有使用到。Swift对Foundation开源了,可以下载到源码,用来探究NSLock的底层实现:

    open class NSLock: NSObject, NSLocking {
        internal var mutex = _MutexPointer.allocate(capacity: 1)
    #if os(macOS) || os(iOS) || os(Windows)
        private var timeoutCond = _ConditionVariablePointer.allocate(capacity: 1)
        private var timeoutMutex = _MutexPointer.allocate(capacity: 1)
    #endif
    
        public override init() {
    #if os(Windows)
            InitializeSRWLock(mutex)
            InitializeConditionVariable(timeoutCond)
            InitializeSRWLock(timeoutMutex)
    #else
            pthread_mutex_init(mutex, nil)
    #if os(macOS) || os(iOS)
            pthread_cond_init(timeoutCond, nil)
            pthread_mutex_init(timeoutMutex, nil)
    #endif
    #endif
        }
        
        deinit {
    #if os(Windows)
            // SRWLocks do not need to be explicitly destroyed
    #else
            pthread_mutex_destroy(mutex)
    #endif
            mutex.deinitialize(count: 1)
            mutex.deallocate()
    #if os(macOS) || os(iOS) || os(Windows)
            deallocateTimedLockData(cond: timeoutCond, mutex: timeoutMutex)
    #endif
        }
        
        open func lock() {
    #if os(Windows)
            AcquireSRWLockExclusive(mutex)
    #else
            pthread_mutex_lock(mutex)
    #endif
        }
    
        open func unlock() {
    #if os(Windows)
            ReleaseSRWLockExclusive(mutex)
            AcquireSRWLockExclusive(timeoutMutex)
            WakeAllConditionVariable(timeoutCond)
            ReleaseSRWLockExclusive(timeoutMutex)
    #else
            pthread_mutex_unlock(mutex)
    #if os(macOS) || os(iOS)
            // Wakeup any threads waiting in lock(before:)
            pthread_mutex_lock(timeoutMutex)
            pthread_cond_broadcast(timeoutCond)
            pthread_mutex_unlock(timeoutMutex)
    #endif
    #endif
        }
    
        open func `try`() -> Bool {
    #if os(Windows)
            return TryAcquireSRWLockExclusive(mutex) != 0
    #else
            return pthread_mutex_trylock(mutex) == 0
    #endif
        }
        
        open func lock(before limit: Date) -> Bool {
    #if os(Windows)
            if TryAcquireSRWLockExclusive(mutex) != 0 {
              return true
            }
    #else
            if pthread_mutex_trylock(mutex) == 0 {
                return true
            }
    #endif
    
    #if os(macOS) || os(iOS) || os(Windows)
            return timedLock(mutex: mutex, endTime: limit, using: timeoutCond, with: timeoutMutex)
    #else
            guard var endTime = timeSpecFrom(date: limit) else {
                return false
            }
    #if os(WASI)
            return true
    #else
            return pthread_mutex_timedlock(mutex, &endTime) == 0
    #endif
    #endif
        }
    
        open var name: String?
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

    从源码来看就是对互斥锁的封装。

    使用互斥锁NSLock异步并发调用block块,block块内部递归调用自己,问打印什么?

    - (void)test {
        NSLock *lock = [[NSLock alloc] init];
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            static void (^block)(int);
            
            block = ^(int value) {
                NSLog(@"加锁前");
                [lock lock];
                NSLog(@"加锁后");
                if (value > 0) {
                    NSLog(@"value——%d", value);
                    block(value - 1);
                }
                [lock unlock];
                NSLog(@"解锁后");
            };
            block(10);
        });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    输出:
    请添加图片描述
    原因:互斥锁在递归调用时会造成堵塞,并非死锁——这里的问题是后面的代码无法执行下去。

    • 第一次加完锁之后还没出锁就进行递归调用
    • 第二次加锁就堵塞了线程(因为不会查询缓存)

    解决方案: 使用递归锁NSRecursiveLock替换NSLock

    NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
    
    • 1

    (二)pthread_mutex(互斥锁)

    pthread_mutex就是互斥锁本身——当锁被占用,而其他线程申请锁时,不是使用忙等,而是阻塞线程并睡眠。

    #import <pthread/pthread.h>
    // 初始化(两种)
    // 1.普通初始化
    pthread_mutex_t mutex_t;
    pthread_mutex_init(&mutex_t, NULL);
    
    // 2.宏初始化
    pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;
    
    // 加锁
    pthread_mutex_lock(&mutex_t);
    // 这里做需要线程安全操作
    // 解锁
    pthread_mutex_unlock(&mutex_t);
    // 尝试加锁,可以加锁时返回的是 0,否则返回一个错误
    pthread_mutex_trylock(&mutex_t)
    // 释放锁
    pthread_mutex_destroy(&mutex_t);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    (三)@synchronized

    @synchronized可能是日常开发中用的比较多的一种互斥锁,因为它的使用比较简单,但并不是在任意场景下都能使用@synchronized,且它的性能较低。

    // 初始化
    @synchronized(锁对象){
    
    }
    /*
    底层封装的pthread_mutex的PTHREAD_MUTEX_RECURSIVE 模式,
    锁对象来表示是否为同一把锁
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    接下来就通过源码探索来看一下@synchronized。

    - (void)run {
        @synchronized (self) {
            NSLog(@"s1");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    将上面源代码转化成cpp代码:

    static void _I_MyPerson_run(MyPerson * self, SEL _cmd) {
        { 
        	id _rethrow = 0; id _sync_obj = (id)self; 
        	objc_sync_enter(_sync_obj);
    		try {
    			struct _SYNC_EXIT { 
    				_SYNC_EXIT(id arg) : sync_exit(arg) {}
    				~_SYNC_EXIT() {objc_sync_exit(sync_exit);}
    			id sync_exit;
    			} _sync_exit(_sync_obj);
    
            	NSLog((NSString *)&__NSConstantStringImpl__var_folders_rx_h53wjns9787gpxxz8tg94y6r0000gn_T_MyPerson_9b8773_mi_3);
        	} catch (id e) {_rethrow = e;}
        
    		{ 
    			struct _FIN { _FIN(id reth) : rethrow(reth) {}
    			~_FIN() { if (rethrow) objc_exception_throw(rethrow); }
    			id rethrow;
    		} _fin_force_rethow(_rethrow);}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    synchronized调用了try catch,内部调用了objc_sync_enterobjc_sync_exit

    1. objc_sync_enter

    // Begin synchronizing on 'obj'. 
    // Allocates recursive mutex associated with 'obj' if needed.
    // Returns OBJC_SYNC_SUCCESS once lock is acquired.  
    int objc_sync_enter(id obj)
    {
        int result = OBJC_SYNC_SUCCESS;
    
        if (obj) {
            SyncData* data = id2data(obj, ACQUIRE);
            ASSERT(data);
            data->mutex.lock();
        } else {
            // @synchronized(nil) does nothing
            if (DebugNilSync) {
                _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
            }
            objc_sync_nil();
        }
    
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    BREAKPOINT_FUNCTION(
        void objc_sync_nil(void)
    );
    
    • 1
    • 2
    • 3
    1. 首先从它的注释中recursive mutex可以得出@synchronized是递归锁
    2. 如果加锁的对象obj 不存在时分别会走objc_sync_nil()和不做任何操作。这也是@synchronized作为递归锁但能防止死锁的原因所在:在不断递归的过程中如果对象不存在了就会停止递归从而防止死锁。
    3. 正常情况下(obj存在)会通过id2data方法生成一个SyncData对象
      typedef struct alignas(CacheLineSize) SyncData {
      	struct SyncData* nextData;
      	DisguisedPtr<objc_object> object;
      	int32_t threadCount;  // number of THREADS using this block
      	recursive_mutex_t mutex;
      } SyncData;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • nextData指的是链表中下一个SyncData
      • object指的是当前加锁的对象
      • threadCount表示使用该对象进行加锁的线程数
      • mutex即对象所关联的锁

    2. objc_sync_exit

    // End synchronizing on 'obj'. 
    // Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
    int objc_sync_exit(id obj)
    {
        int result = OBJC_SYNC_SUCCESS;
        
        if (obj) {
            SyncData* data = id2data(obj, RELEASE); 
            if (!data) {
                result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
            } else {
                bool okay = data->mutex.tryUnlock();
                if (!okay) {
                    result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
                }
            }
        } else {
            // @synchronized(nil) does nothing
        }
    	
    
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    3. 注意事项

    • 不能使用非OC对象作为加锁条件——id2data中接收参数为id类型
    • 多次锁同一个对象会有什么后果吗——会从高速缓存中拿到data,所以只会锁一次对象
    • 都说@synchronized性能低——是因为在底层增删改查消耗了大量性能
    • 加锁对象不能为nil,否则加锁无效,不能保证线程安全

    (四)os_unfair_lock

    由于OSSpinLock自旋锁的bug,替代方案是内部封装了os_unfair_lock,而os_unfair_lock在加锁时会处于休眠状态,而不是自旋锁的忙等状态 os_unfair_lock属于互斥锁。

    #import <os/lock.h>
    // 初始化
    os_unfair_lock unfair_lock = OS_UNFAIR_LOCK_INIT;
    // 加锁
    os_unfair_lock_lock(&unfair_lock);
    // 解锁
    os_unfair_lock_unlock(&unfair_lock);
    // 尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO
    os_unfair_lock_trylock(&unfair_lock);
    /*
    注:解决不同优先级的线程申请锁的时候不会发生优先级反转问题.
    不过相对于 OSSpinLock , os_unfair_lock性能方面减弱了许多.
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    typedef mutex_t spinlock_t;
    
    class mutex_tt : nocopy_t {
        os_unfair_lock mLock;
     public:
        constexpr mutex_tt() : mLock(OS_UNFAIR_LOCK_INIT) {
            lockdebug_remember_mutex(this);
        }
    
        constexpr mutex_tt(__unused const fork_unsafe_lock_t unsafe) : mLock(OS_UNFAIR_LOCK_INIT) { }
    
        void lock() {
            lockdebug_mutex_lock(this);
    
            // 
            uint32_t opts = OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION | OS_UNFAIR_LOCK_ADAPTIVE_SPIN;
            os_unfair_lock_lock_with_options_inline
                (&mLock, (os_unfair_lock_options_t)opts);
        }
    
        void unlock() {
            lockdebug_mutex_unlock(this);
    
            os_unfair_lock_unlock_inline(&mLock);
        }
    
        void forceReset() {
            lockdebug_mutex_unlock(this);
    
            bzero(&mLock, sizeof(mLock));
            mLock = os_unfair_lock OS_UNFAIR_LOCK_INIT;
        }
    
        void assertLocked() {
            lockdebug_mutex_assert_locked(this);
        }
    
        void assertUnlocked() {
            lockdebug_mutex_assert_unlocked(this);
        }
    
    
        // Address-ordered lock discipline for a pair of locks.
    
        static void lockTwo(mutex_tt *lock1, mutex_tt *lock2) {
            if ((uintptr_t)lock1 < (uintptr_t)lock2) {
                lock1->lock();
                lock2->lock();
            } else {
                lock2->lock();
                if (lock2 != lock1) lock1->lock(); 
            }
        }
    
        static void unlockTwo(mutex_tt *lock1, mutex_tt *lock2) {
            lock1->unlock();
            if (lock2 != lock1) lock2->unlock();
        }
    
        // Scoped lock and unlock
        class locker : nocopy_t {
            mutex_tt& lock;
        public:
            locker(mutex_tt& newLock) 
                : lock(newLock) { lock.lock(); }
            ~locker() { lock.unlock(); }
        };
    
        // Either scoped lock and unlock, or NOP.
        class conditional_locker : nocopy_t {
            mutex_tt& lock;
            bool didLock;
        public:
            conditional_locker(mutex_tt& newLock, bool shouldLock)
                : lock(newLock), didLock(shouldLock)
            {
                if (shouldLock) lock.lock();
            }
            ~conditional_locker() { if (didLock) lock.unlock(); }
        };
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    三、条件锁

    就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运行。
    在一定条件下,让其等待休眠,并放开锁,等接收到信号或者广播,会从新唤起线程,并重新加锁,像NSCondition封装了pthread_mutex的以上几个函数,NSConditionLock封装了NSCondition

    (一)NSCondition

    NSCondition是一个条件锁,可能平时用的不多,但与信号量相似:线程1需要等到条件1满足才会往下走,否则就会堵塞等待,直至条件满足。

    // 初始化
    NSCondition *_condition= [[NSCondition alloc]init];
    // 加锁
    [_condition lock];
    // 解锁
    [_condition unlock];
    /*
    其他功能接口
    wait 进入等待状态
    waitUntilDate:让一个线程等待一定的时间
    signal 唤醒一个等待的线程
    broadcast 唤醒所有等待的线程
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在Swift源码中找到关于NSCondition部分:

    open class NSCondition: NSObject, NSLocking {
        internal var mutex = _MutexPointer.allocate(capacity: 1)
        internal var cond = _ConditionVariablePointer.allocate(capacity: 1)
    
        public override init() {
            pthread_mutex_init(mutex, nil)
            pthread_cond_init(cond, nil)
        }
        
        deinit {
            pthread_mutex_destroy(mutex)
            pthread_cond_destroy(cond)
        }
        
        open func lock() {
            pthread_mutex_lock(mutex)
        }
        
        open func unlock() {
            pthread_mutex_unlock(mutex)
        }
        
        open func wait() {
            pthread_cond_wait(cond, mutex)
        }
    
        open func wait(until limit: Date) -> Bool {
            guard var timeout = timeSpecFrom(date: limit) else {
                return false
            }
            return pthread_cond_timedwait(cond, mutex, &timeout) == 0
        }
        
        open func signal() {
            pthread_cond_signal(cond)
        }
        
        open func broadcast() {
            pthread_cond_broadcast(cond) // wait  signal
        }
        
        open var name: String?
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    我们可以看出:

    • NSCondition是对mutex和cond的一种封装(cond就是用于访问和操作特定类型数据的指针)
    • wait操作会阻塞线程,使其进入休眠状态,直至超时
    • signal操作是唤醒一个正在休眠等待的线程
    • broadcast会唤醒所有正在等待的线程

    (二)NSConditionLock

    // 初始化
    NSConditionLock *_conditionLock = [[NSConditionLock alloc]init];
    // 加锁
    [_conditionLock lock];
    // 解锁
    [_conditionLock unlock];
    // 尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO
    [_conditionLock tryLock];
    /*
    其他功能接口
    - (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER; //初始化传入条件
    - (void)lockWhenCondition:(NSInteger)condition;//条件成立触发锁
    - (BOOL)tryLockWhenCondition:(NSInteger)condition;//尝试条件成立触发锁
    - (void)unlockWithCondition:(NSInteger)condition;//条件成立解锁
    - (BOOL)lockBeforeDate:(NSDate *)limit;//触发锁 在等待时间之内
    - (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;//触发锁 条件成立 并且在等待时间之内
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    它和NSCondition的区别在于哪里呢?

    open class NSConditionLock : NSObject, NSLocking {
        internal var _cond = NSCondition()
        internal var _value: Int
        internal var _thread: _swift_CFThreadRef?
        
        public convenience override init() {
            self.init(condition: 0)
        }
        
        public init(condition: Int) {
            _value = condition
        }
    
        open func lock() {
            let _ = lock(before: Date.distantFuture)
        }
    
        open func unlock() {
            _cond.lock()
            _thread = nil
            _cond.broadcast()
            _cond.unlock()
        }
        
        open var condition: Int {
            return _value
        }
    
        open func lock(whenCondition condition: Int) {
            let _ = lock(whenCondition: condition, before: Date.distantFuture)
        }
    
        open func `try`() -> Bool {
            return lock(before: Date.distantPast)
        }
        
        open func tryLock(whenCondition condition: Int) -> Bool {
            return lock(whenCondition: condition, before: Date.distantPast)
        }
    
        open func unlock(withCondition condition: Int) {
            _cond.lock()
            _thread = nil
            _value = condition
            _cond.broadcast()
            _cond.unlock()
        }
    
        open func lock(before limit: Date) -> Bool {
            _cond.lock()
            while _thread != nil {
                if !_cond.wait(until: limit) {
                    _cond.unlock()
                    return false
                }
            }
            _thread = pthread_self()
            _cond.unlock()
            return true
        }
        
        open func lock(whenCondition condition: Int, before limit: Date) -> Bool {
            _cond.lock()
            while _thread != nil || _value != condition {
                if !_cond.wait(until: limit) {
                    _cond.unlock()
                    return false
                }
            }
            _thread = pthread_self()
            _cond.unlock()
            return true
        }
        
        open var name: String?
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    可以看出:

    • NSConditionLock是NSCondition加线程数的封装
    • NSConditionLock可以设置锁条件,而NSCondition只是通知信号

    四、递归锁

    递归锁就是同一个线程可以加锁N次而不会引发死锁。
    递归锁的主要意思是,同一条线程可以加多把锁。什么意思呢,就是相同的线程访问一段代码,如果是加锁的可以继续加锁,继续往下走,不同线程来访问这段代码时,发现有锁要等待所有锁解开之后才可以继续往下走。
    NSRecursiveLock 封装的pthread_mutexPTHREAD_MUTEX_RECURSIVE模式

    (一)NSRecursiveLock

    // 初始化
    NSRecursiveLock *_recursiveLock = [[NSRecursiveLock alloc] init];
    // 加锁
    [_recursiveLock lock];
    // 解锁
    [_recursiveLock unlock];
    // 尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO
    [_recursiveLock tryLock];
    /*
    注: 递归锁可以被同一线程多次请求,而不会引起死锁。
    即在同一线程中在未解锁之前还可以上锁, 执行锁中的代码。
    这主要是用在循环或递归操作中。
    - (BOOL)lockBeforeDate:(NSDate *)limit;//触发锁 在等待时间之内
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    NSRecursiveLock使用和NSLock类似,如下代码就能解决上个问题:

    - (void)test {
        NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            static void (^block)(int);
            
            block = ^(int value) {
                [lock lock];
                if (value > 0) {
                    NSLog(@"value——%d", value);
                    block(value - 1);
                }
                [lock unlock];
            };
            block(10);
        });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    递归锁在使用时需要注意死锁问题——前后代码相互等待便会产生死锁。上述代码在外层加个for循环,问输出结果?

    - (void)test {
        NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
        for (int i = 0; i < 10; i++) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                static void (^block)(int);
                block = ^(int value) {
                    [lock lock];
                    if (value > 0) {
                        NSLog(@"value——%d", value);
                        block(value - 1);
                    }
                    [lock unlock];
                };
                block(10);
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    运行代码会崩溃,并会提示野指针错误。
    原因: for循环在block内部对同一个对象进行了多次锁操作,直到这个资源身上挂着N把锁,最后大家都无法一次性解锁——找不到解锁的出口
    即 线程1中加锁1、同时线程2中加锁2-> 解锁1等待解锁2 -> 解锁2等待解锁1 -> 无法结束解锁——形成死锁
    解决: 可以采用使用缓存的@synchronized,因为它对对象进行锁操作,会先从缓存查找是否有锁syncData存在。如果有,直接返回而不加锁,保证锁的唯一性

    - (void)testSynchronized {
        for (int i = 0; i < 10; i++) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                static void (^block)(int);
                block = ^(int value) {
                    @synchronized (self) {
                        if (value > 0) {
                            NSLog(@"value——%d", value);
                            block(value - 1);
                        }
                    }
                };
                block(10);
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    (二)pthread_mutex(recursive)

    递归锁可以被同一线程多次请求,而不会引起死锁。 即在同一线程中在未解锁之前还可以上锁, 执行锁中的代码。 这主要是用在循环或递归操作中。

    #import <pthread/pthread.h>
    // 初始化
    pthread_mutex_t mutex_t;
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr); //初始化attr并且给它赋予默认pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //设置锁类型,这边是设置为递归锁
    pthread_mutex_init(&mutex_t, &attr);
    pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutexattr_destroy(&attr); //销毁一个属性对象,在重新进行初始化之前该结构不能重新使用
    // 加锁
    pthread_mutex_lock(&mutex_t);
    // 解锁
    pthread_mutex_unlock(&mutex_t);
    /*
    注: 递归锁可以被同一线程多次请求,而不会引起死锁。
    即在同一线程中在未解锁之前还可以上锁, 执行锁中的代码。
    这主要是用在循环或递归操作中。
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    - (void)testPthreadMutex_Recursive {
        pthread_mutex_t mutex_t;
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
        pthread_mutex_init(&mutex_t, &attr);
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            static void (^block)(int);
            block = ^(int value) {
                pthread_mutex_lock(&mutex_t);
                if (value > 0) {
                    NSLog(@"value——%d", value);
                    block(value - 1);
                }
                pthread_mutex_unlock(&mutex_t);
            };
            block(10);
        });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    五、信号量

    信号量(semaphore):是一种更高级的同步机制,互斥锁可以说是 semaphore 在仅取值 0/1 时的特例。信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥。

    // 初始化
    dispatch_semaphore_t semaphore_t = dispatch_semaphore_create(1);
    // 加锁
    dispatch_semaphore_wait(semaphore_t,DISPATCH_TIME_FOREVER);
    // 解锁
    dispatch_semaphore_signal(semaphore_t);
    /*
    注: dispatch_semaphore  其他两个功能
    1.还可以起到阻塞线程的作用.
    2.可以实现定时器功能,这里不做过多介绍.
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    总结

    其实基本的锁就包括了三类,自旋锁, 互斥锁, 读写锁,其他的比如条件锁,递归锁,信号量都是上层的封装和实现。

    • OSSpinLock不再安全,底层用os_unfair_lock替代
    • atomic只能保证setter、getter时线程安全,所以更多的使用nonatomic来修饰
    • 读写锁更多使用栅栏函数来实现
    • @synchronized在底层维护了一个哈希链表进行data的存储,使用recursive_mutex_t进行加锁
    • NSLock、NSRecursiveLock、NSCondition和NSConditionLock底层都是对pthread_mutex的封装
    • NSCondition和NSConditionLock是条件锁,当满足某一个条件时才能进行操作,和信号量dispatch_semaphore类似
    • 普通场景下涉及到线程安全,可以用NSLock
    • 循环调用时用NSRecursiveLock
    • 循环调用且有线程影响时,请注意死锁,如果有死锁问题请使用@synchronized
  • 相关阅读:
    JS—DOM节点的使用知识整理
    面试--线程池的执行流程和拒绝策略有哪些?
    【WPF】CAD工程图纸转WPF可直接使用的xaml代码技巧
    JavaScrip学习
    HTML静态网页作业——我的家乡安庆
    右击图片出现Microsoft WinRT Storage API,点击提示找不到元素的解决思路
    程序题:从对象中排除指定的属性并返回新对象
    计算机毕业设计基于springboot+vue+elementUI的医院急诊系统(前后端分离)
    我国融资租赁行业有望达到13万亿元 互融云融资租赁系统助力行业稳健发展
    MYSQL窗口函数(Rows & Range)——滑动窗口函数用法
  • 原文地址:https://blog.csdn.net/weixin_52192405/article/details/126172366