美文网首页
第十四篇:iOS锁

第十四篇:iOS锁

作者: 坚持才会看到希望 | 来源:发表于2022-06-07 20:51 被阅读0次

    自旋锁,互斥锁,读写锁
    自旋锁就是一个忙等状态 do--while
    互斥锁就是一个闲等,可以使得CPU进行休眠去做别的处理

    下面是iOS中所用到的锁:执行10万次所用的时间

    OSSpinLock:                   1.70 ms  自旋锁  iOS10后被移除,因为其有优先级占用,苹果用os_unfair_lock这个互斥锁来代替被废除的OSSpinLock锁
    
    dispatch_semaphore:           3.19 ms
    pthread_mutex:                2.18 ms
    NSCondition:                  3.33 ms
    NSLock:                       2.93 ms
    pthread_mutex(recursive):     3.08 ms
    NSRecursiveLock:              4.66 ms
    NSConditionLock:             10.82 ms
    @synchronized:                9.09 ms
    
    

    下面代码打印后,我们发现其并不是以49依次递减的,这说明self.count这个是线程不安全的。如果改成atomic原子属性,其也不会保证线程安全的。这个是因为 self.count --这句代码的执行,其实就是执行了set和get方法,这样同时执行原子属性无法同时满足线程安全,只满足了一边的线程安全。

    解决这个问题就需要用到锁功能。

    @property (nonatomic ,assign) int count;
    
     self.count = 50;
    
    - (void)test {
        for (int i = 0; i < 10; i ++) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                self.count --;
                NSLog(@"%d",self.count);
            });
        }
    }
    
    2022-05-31 15:11:04.921282+0800 iOSLockDemo[4510:100720] 49
    2022-05-31 15:11:04.921301+0800 iOSLockDemo[4510:100719] 47
    2022-05-31 15:11:04.921291+0800 iOSLockDemo[4510:100732] 48
    2022-05-31 15:11:04.921311+0800 iOSLockDemo[4510:100727] 46
    2022-05-31 15:11:04.921334+0800 iOSLockDemo[4510:100733] 45
    2022-05-31 15:11:04.921342+0800 iOSLockDemo[4510:100720] 44
    2022-05-31 15:11:04.921355+0800 iOSLockDemo[4510:100734] 43
    2022-05-31 15:11:04.921397+0800 iOSLockDemo[4510:100732] 42
    2022-05-31 15:11:04.921410+0800 iOSLockDemo[4510:100719] 41
    2022-05-31 15:11:04.921458+0800 iOSLockDemo[4510:100724] 40
    

    下面是set属性源码:

    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];//如果是原子操作就加一把spinlock_t锁
            slotlock.lock();
            oldValue = *slot;
            *slot = newValue;        
            slotlock.unlock();
        }
    
        objc_release(oldValue);
    }
    

    下面是get属性方法源码:

    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];//是原子就会加spinlock_t把锁
        slotlock.lock();//加锁
        id value = objc_retain(*slot);
        slotlock.unlock();//解锁
        
        // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
        return objc_autoreleaseReturnValue(value);
    }
    

    os_unfair_lock互斥锁

    当加了os_unfair_lock锁后发现其打印就是按顺序进行打印了

    - (void)test {
        for (int i = 0; i < 10; i ++) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                [self unfairLock_test];
            });
        }
    }
    
    -(void)unfairLock_test {
        os_unfair_lock_lock(&_unfairLock);
        self.count --;
        NSLog(@"%d",self.count);
        os_unfair_lock_unlock(&_unfairLock);
    }
    
    
    2022-05-31 16:38:19.268185+0800 iOSLockDemo[6256:161713] 49
    2022-05-31 16:38:19.268249+0800 iOSLockDemo[6256:161718] 48
    2022-05-31 16:38:19.268320+0800 iOSLockDemo[6256:161718] 47
    2022-05-31 16:38:19.268413+0800 iOSLockDemo[6256:161713] 46
    2022-05-31 16:38:19.268449+0800 iOSLockDemo[6256:161715] 45
    2022-05-31 16:38:19.268492+0800 iOSLockDemo[6256:161712] 44
    2022-05-31 16:38:19.268539+0800 iOSLockDemo[6256:161726] 43
    2022-05-31 16:38:19.268621+0800 iOSLockDemo[6256:161717] 42
    2022-05-31 16:38:19.268733+0800 iOSLockDemo[6256:161725] 41
    2022-05-31 16:38:19.268998+0800 iOSLockDemo[6256:161719] 40
    
    WechatIMG2017.jpeg

    NSLock锁

    其是对Pthreds的封装

    - (void)test {
        for (int i = 0; i < 10; i ++) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                [self nslock_test];
            });
        }
    }
    
    -(void)nslock_test {
        [self.iLock lock];
        self.count --;
        NSLog(@"%d",self.count);
        [self.iLock unlock];
    }
    
    WechatIMG2018.jpeg

    NSConditon锁

    其是遵循了NSLocking协议,所以其有lock和unlock方法。

    下面代码可以模拟一个抢票的机制

    - (void)nscondition_test {
        for (int i = 0; i < 50; i ++) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                [self lg_production];
            });
        }
        for (int i = 0; i < 100; i ++) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                [self lg_consumption];
            });
        }
    }
    
    - (void)lg_production {
        [self.iCondition lock];
        self.count ++;
        NSLog(@"生产了一个产品,现有产品 : %d个",self.count);
        [self.iCondition signal];//唤醒
        [self.iCondition unlock];
    }
    
    - (void)lg_consumption {//这里依然会有负数打印,这个是因为是系统的bug,虚假唤醒
        [self.iCondition lock];
        while (self.count == 0) {//等待,如果没有这个会打印负数,用while就会避免虚假唤醒
            [self.iCondition wait];
        }
        self.count --;
        NSLog(@"消费了一个产品,现有产品: %d个",self.count);
        [self.iCondition unlock];
    }
    
    
    WechatIMG2019.jpeg

    NSConditonLock锁

    其是对NSConditon的又一次封装,通过下面的锁,可以使得线程按1,2,3顺序打印

    - (void)lg_testConditonLock{
        
        self.iConditionLock = [[NSConditionLock alloc] initWithCondition:3];//3个线程锁
    
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self.iConditionLock lockWhenCondition:3];//加锁
            NSLog(@"线程 1");
            [self.iConditionLock unlockWithCondition:2];//解锁变成2
        });
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self.iConditionLock lockWhenCondition:2];
            NSLog(@"线程 2");
            [self.iConditionLock unlockWithCondition:1];
        });
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self.iConditionLock lockWhenCondition:1];
            NSLog(@"线程 3");
            [self.iConditionLock unlockWithCondition:0];
        });
    }
    
    WechatIMG2020.jpeg

    NSRecursiveLock锁

    这个是个递归锁,这个只能保证在同一个线程里操作

    锁在同一时刻只能被一条线程拥有
    递归锁同一时刻能被多条线程锁拥有

    -(void)recursiveLock_test {
        
        [self.iRecursiveLock lock];
        self.count --;
        NSLog(@"%d",self.count);
        [self.iRecursiveLock unlock];
    }
    
    - (void)test {
        for (int i = 0; i < 10; i ++) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                [self recursiveLock_test];
            });
        }
    }
    
    - (void)recursiveTest {
     
    如果这里加了for循环进行几次,每次调用下面的代码就会出现奔溃。这里是因为加了for就会有多个线程,递归锁同一时刻能被多条线程锁拥有,但是在解锁的时候没有按顺序就会奔溃,如果要解决这个问题可以添加 @synchronized这个锁就可以解决。
    
    
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            static void (^recursiveMethod)(int);
            recursiveMethod = ^(int value){//block的递归调用,递归操作需要用到self.iRecursiveLock这种递归锁
                if (value > 0) {
                    [self.iRecursiveLock lock];
                    NSLog(@"%d",value);
                    recursiveMethod(value - 1);
                    [self.iRecursiveLock unlock];
                }
            };
            recursiveMethod(10);
        });
    }
    
    
    WechatIMG2021.jpeg

    pthread_mutex锁

    其是c语言底层写的

    - (void)lg_pthread_mutex {
        
        //非递归
        pthread_mutex_t lock0;
        pthread_mutex_init(&lock0, NULL);
        pthread_mutex_lock(&lock0);
        pthread_mutex_unlock(&lock0);
        pthread_mutex_destroy(&lock0);
        
        //递归
        pthread_mutex_t lock;
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        pthread_mutex_init(&lock, &attr);
        pthread_mutexattr_destroy(&attr);
        pthread_mutex_lock(&lock);
        pthread_mutex_unlock(&lock);
        pthread_mutex_destroy(&lock);
    }
    
    WechatIMG2022.jpeg

    读写锁

    读和写互斥,多读单写,下面我们用GCD来实现

    - (NSString *)lg_read {
        // 异步读取
        __block NSString *ret;
        dispatch_sync(self.iQueue, ^{
            // 读取的代码
            ret = self.dataDic[@"name"];
        });
        NSLog(@"%@",ret);
        return ret;
    }
    
    - (void)lg_write: (NSString *)name {
        // 写操作
        dispatch_barrier_async(self.iQueue, ^{
            [self.dataDic setObject:name forKey:@"name"];
        });
    }
    

    @synchronized锁

    首先在main函数里添加 @synchronized (obj) 这个方法,然后用clang命令可以打印出main.cpp文件。

    #import <Cocoa/Cocoa.h>
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // Setup code that might create autoreleased objects goes here.
            NSObject *obj = [NSObject alloc];
            @synchronized (obj) {
                
            }
        }
        return NSApplicationMain(argc, argv);
    }
    

    下面是得到的cpp

    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"));
            { id _rethrow = 0; id _sync_obj = (id)obj; objc_sync_enter(_sync_obj);
    try {
        struct _SYNC_EXIT {
        _SYNC_EXIT(id arg) : sync_exit(arg) {}
        ~_SYNC_EXIT() {objc_sync_exit(sync_exit);}//~_SYNC_EXIT() 是一个析构函数
        id sync_exit;
        }
        
        _sync_exit(_sync_obj);//结构体_SYNC_EXIT的构造函数
    
            } 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);}
    }
    
        }
        return NSApplicationMain(argc, argv);
    }
    
    

    其实在synchronized里有用到下面两个函数,objc_sync_exit和objc_sync_enter。这里的SyncData是一个单向的链表。其中其里面有个spinlock_t,这个其实就是 os_unfair_lock锁。

    WechatIMG2027.jpeg
    static SyncData* id2data(id object, enum usage why)
    {
        spinlock_t *lockp = &LOCK_FOR_OBJ(object);
        SyncData **listp = &LIST_FOR_OBJ(object);
        SyncData* result = NULL;
    
    #if SUPPORT_DIRECT_THREAD_KEYS
        // Check per-thread single-entry fast cache for matching object
        bool fastCacheOccupied = NO;
        SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
        if (data) {
            fastCacheOccupied = YES;
    
            if (data->object == object) {
                // Found a match in fast cache.
                uintptr_t lockCount;
    
                result = data;
                lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
             
    

    下面的StripedMap是用来缓存带spinlock锁能力的类或者结构体,如果一张表存储一个那么会浪费内存,如果一张表存储多个那么效率会很低,所以就执行设置特定的个数StripeCount = 8(非模拟器上) 或者StripeCount = 64 (模拟器上)。其实这个SyncList里是存着sDataLists这样的数据形式。

    在线程的局部存储空间里也就是TLS,其里面缓存有synchronized的信息.也就是线程里有单独开辟了一小部分空间让其存储一些关于线程的数据。

    static StripedMap<SyncList> sDataLists;
    
    template<typename T>
    class StripedMap {
    #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
        enum { StripeCount = 8 };
    #else
        enum { StripeCount = 64 };
    #endif
    
    static SyncCache *fetch_cache(bool create)
    {
        _objc_pthread_data *data;
        
        data = _objc_fetch_pthread_data(create);
        if (!data) return NULL;
    
        if (!data->syncCache) {
            if (!create) {
                return NULL;
            } else {
                int count = 4;
                data->syncCache = (SyncCache *)
                    calloc(1, sizeof(SyncCache) + count*sizeof(SyncCacheItem));
                data->syncCache->allocated = count;
            }
        }
    

    下面是_objc_pthread_data 这个数据结构体里存syncCache信息,这里就是关于synchronize的信息。

    
    typedef struct {
        struct _objc_initializing_classes *initializingClasses; // for +initialize
        struct SyncCache *syncCache;  // for @synchronize
        struct alt_handler_list *handlerList;  // for exception alt handlers
        char *printableNames[4];  // temporary demangled names for logging
        const char **classNameLookups;  // for objc_getClass() hooks
        unsigned classNameLookupsAllocated;
        unsigned classNameLookupsUsed;
    
        // If you add new fields here, don't forget to update 
        // _objc_pthread_destroyspecific()
    
    } _objc_pthread_data;
    
    

    下面SyncCache里是存着SyncCacheItem,在SyncCacheItem里有lockCount,这个是记录当前线程的加锁次数

    typedef struct {
        SyncData *data;
        unsigned int lockCount;  // number of times THIS THREAD locked this block
    } SyncCacheItem;
    

    下面是加锁的时候lockCount会加1,解锁的时候lockCount会减1,下面的源码也就是在线程的缓存里去找和我们这个 加锁的对象SyncData,找到的话就会返回。这里有快速的缓存,去查找拿取,没有的话就去objc_pthresd_data里正常查找。如果还没有就会去表里找(StripeCount数量表中),如果还没有那就会创建一个添加到表里。

    static SyncData* id2data(id object, enum usage why)
    {
        spinlock_t *lockp = &LOCK_FOR_OBJ(object);
        SyncData **listp = &LIST_FOR_OBJ(object);
        SyncData* result = NULL;
    
    #if SUPPORT_DIRECT_THREAD_KEYS
        // Check per-thread single-entry fast cache for matching object
        bool fastCacheOccupied = NO;
        SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
        if (data) {
            fastCacheOccupied = YES;
    
            if (data->object == object) {
                // Found a match in fast cache.
                uintptr_t lockCount;
    
                result = data;
                lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
                if (result->threadCount <= 0  ||  lockCount <= 0) {
                    _objc_fatal("id2data fastcache is buggy");
                }
    
                switch(why) {
                case ACQUIRE: {
                    lockCount++;
                    tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
                    break;
                }
                case RELEASE:
                    lockCount--;
                    tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
                    if (lockCount == 0) {
                        // remove from fast cache
                        tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
                        // atomic because may collide with concurrent ACQUIRE
                        OSAtomicDecrement32Barrier(&result->threadCount);
                    }
                    break;
                case CHECK:
                    // do nothing
                    break;
                }
    
                return result;
            }
        }
    #endif
    

    下面用一个图示来显示整个过程:
    其中lockcount是指当前线程的加锁数,thredcount是指当前锁被几条线程所拥有。

    WechatIMG2033.jpeg

    相关文章

      网友评论

          本文标题:第十四篇:iOS锁

          本文链接:https://www.haomeiwen.com/subject/sehoprtx.html