iOS底层探索--@synchronized锁

667 阅读3分钟

小谷底层探索合集

  • 兄弟们大多都听说过--锁。锁诞生的原因就是为了安全

  • @synchronized锁应该是我们在开发中用的最多的。今天小小的浅谈一波~

1. @synchronized简单使用

    1. 首先我们先写一个买票的小demo
#import "ViewController.h"

@interface ViewController ()
@property(nonatomic)NSInteger ticketCounts;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.ticketCounts = 30;
    [self xg_doPay];
}

//买票
- (void)xg_doPay{
    //异步买票
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (int i = 0;i < 10; i++) {
            [self saleTickets];
        }
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (int i = 0;i < 15; i++) {
            [self saleTickets];
        }
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (int i = 0;i < 10; i++) {
            [self saleTickets];
        }
    });
}

//卖票
- (void)saleTickets{
    if (self.ticketCounts > 0) {
        NSLog(@"当前票数为:%ld",self.ticketCounts);
        self.ticketCounts--;
    }else{
        NSLog(@"没票了~~~");
    }
    
}

由于买票是异步的--他没有顺序的,所以经常会造成混乱,也就是不安全的。

    1. 这个时候,我们经常会做一波操作。(增加一把@synchronized 锁)
//卖票
- (void)saleTickets{
    
    @synchronized (self) {
        if (self.ticketCounts > 0) {
            NSLog(@"当前票数为:%ld",self.ticketCounts);
            self.ticketCounts--;
        }else{
            NSLog(@"没票了~~~");
        }
    }
}

这个时候运行 发现这个时候就没有问题了。

    1. 今天我们就看下。这个@synchronized 做了什么,他就没有问题了~

2. @synchronized底层分析

2.1. 源码定位

对于底层分析,我就会那么几种方法。😆

    1. 首先,我先写个简单的例子,然后看结构
    1. 我们接下来开汇编,打断点

这样我们就找到了源码位置

2.2. 源码分析

    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;
}

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;
}

这两个方法,都调用了id2data,且返回类型都是SyncData,那我感觉这个有点东西

    1. 兄弟们看下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. 我们第一次看这个结构,然后不多想,看命名给我的感觉就是这样的

当然,这也只是第一印象,不知道对不对~

给人的感觉就是个单向链表,然后每个节点都存了些东西

    1. 接下来,我们剖析id2data函数(这个函数比较长,不过我还是用老方法,折叠大法~)

大概预览下~

    1. 这个SUPPORT_DIRECT_THREAD_KEYS 的定义
    1. 我们那我们研究下这个判断吧
    1. cache操作
    1. 在里面继续加锁的操作
    1. 然后done之后就会解锁
done:
    lockp->unlock();
    if (result) {
        // Only new ACQUIRE should get here.
        // All RELEASE and CHECK and recursive ACQUIRE are 
        // handled by the per-thread caches above.
        if (why == RELEASE) {
            // Probably some thread is incorrectly exiting 
            // while the object is held by another thread.
            return nil;
        }
        if (why != ACQUIRE) _objc_fatal("id2data is buggy");
        if (result->object != object) _objc_fatal("id2data is buggy");

#if SUPPORT_DIRECT_THREAD_KEYS
        if (!fastCacheOccupied) {
            // Save in fast thread cache
            tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
            tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
        } else 
#endif
        {
            // Save in thread cache
            if (!cache) cache = fetch_cache(YES);
            cache->list[cache->used].data = result;
            cache->list[cache->used].lockCount = 1;
            cache->used++;
        }
    }

    return result;
}

这个就好多了,有很多注释

    1. 我偷了一幅结构图~

以上就是@synchronized的使用和结构了

继续加班了~~~