iOS底层原理(27)-锁

154 阅读6分钟

一、互斥锁

互斥锁 = 互斥 + 同步

闲等

1.1递归锁

NSRecursiveLock

self.recursiveLock = [[NSRecursiveLock alloc] init];

for (int i= 0; i<10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            static void (^testMethod)(int);
            testMethod = ^(int value){
                [self.recursiveLock lock];
                    if (value > 0) {
                        NSLog(@"current value = %d",value);
                        testMethod(value - 1);
                    }
                [self.recursiveLock unlock];
                }
            };
            testMethod(10);
        });
    }

上面的代码打印发现,打印结果是有序的,但是本来应该打印10遍的,结果只打印了1遍。

该锁是递归锁,但是不支持多线程的递归

Synchronized

for (int i= 0; i<10; i++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        static void (^testMethod)(int);
        testMethod = ^(int value){
            @synchronized (self) {
                if (value > 0) {
                    NSLog(@"current value = %d",value);
                    testMethod(value - 1);
                }
            }
        };
        testMethod(10);
    });
}

上面的打印结果是正确的,Synchronized锁,是多线程递归锁

1.2非递归锁

NSLock

NSLock *lock = [[NSLock alloc] init];
    for (int i= 0; i<10; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            static void (^testMethod)(int);
            testMethod = ^(int value){
                if (value > 0) {
                    NSLog(@"current value = %d",value);
                    testMethod(value - 1);
                }
            };
            [lock lock];
            testMethod(10);
            [lock unlock];
        });
    }

NSLock底层原理分析:

跟踪 [lock lock];

@protocol NSLocking

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

@end

@interface NSLock : NSObject <NSLocking> {

发现 lock其实是NSLocking协议的一个方法,那么我们进一步分析源码 swift-corelibs-foundation-master

首先我们发现底层加锁的代码都是一样的如下所示:

pthread_mutex_lock(mutex)
pthread_mutex_unlock(mutex)

不一样的地方在于,递归锁NSRecursiveLock多了如下的代码设置:

withUnsafeMutablePointer(to: &attrib) { attrs in
            pthread_mutexattr_init(attrs)
            pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))//关键
            pthread_mutex_init(mutex, attrs)
        }

条件锁加了如下的处理:

cond.deinitialize(count: 1)
pthread_cond_wait(cond, mutex)
pthread_cond_signal(cond)

上面的代码只打印了一次,然后就卡主了。

NSLock是非递归锁,因此上面的代码其实是多次在加锁,但是无法解锁。

二、自旋锁

自旋锁 = 互斥 + 忙等

NSCondication 条件锁

生产者-消费者模型

- (void)lg_testConditon{
    
    _testCondition = [[NSCondition alloc] init];
    //创建生产-消费者
    for (int i = 0; i < 50; i++) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self lg_producer];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self lg_consumer];
        });
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self lg_consumer];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self lg_producer];
        });
    }
}

- (void)lg_producer{
    [_testCondition lock]; // 操作的多线程影响
    self.ticketCount = self.ticketCount + 1;
    NSLog(@"生产一个 现有 count %zd",self.ticketCount);
    [_testCondition signal]; // 信号
    [_testCondition unlock];
}

- (void)lg_consumer{
 
     [_testCondition lock];  // 操作的多线程影响
    if (self.ticketCount == 0) {
        NSLog(@"等待 count %zd",self.ticketCount);
        [_testCondition wait];
    }
    //注意消费行为,要在等待条件判断之后
    self.ticketCount -= 1;
    NSLog(@"消费一个 还剩 count %zd ",self.ticketCount);
     [_testCondition unlock];
}

NSConditionLock

NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    [conditionLock lockWhenCondition:1];
    NSLog(@"线程 1");
    [conditionLock unlockWithCondition:0];
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
    [conditionLock lockWhenCondition:2];
    sleep(0.1);
    NSLog(@"线程 2");
    [conditionLock unlockWithCondition:1];
});

//正常的加锁,没有条件控制
dispatch_async(dispatch_get_global_queue(0, 0), ^{
   [conditionLock lock];
   NSLog(@"线程 3");
   [conditionLock unlock];
});

执行结果: 3 2 1

NSConditionLock底层原理

汇编跟踪分析

下符号断点,由于 initWithCondition是个类对象方法,因此符号为 [NSConditionLock initWithCondition:]

image.png

(lldb) register read x0
      x0 = 0x00000002817bc630
(lldb) register read x1
      x1 = 0x00000001deda54ab  
(lldb) register read x2
      x2 = 0x0000000000000002
(lldb) po 0x00000002817bc630
<NSConditionLock: 0x2817bc630>{condition = 0, name = nil}

(lldb) po (SEL)0x00000001deda54ab
"initWithCondition:"

(lldb) po 0x0000000000000002
2

因为 bl 有相关的跳转处理,因此跟踪bl

1、找到第一个bl的地方:

(lldb) register read x0
      x0 = 0x000000016d4798b8
(lldb) register read x1
      x1 = 0x00000001df7fd10c  
(lldb) register read x2
      x2 = 0x0000000000000002
(lldb) po 0x000000016d4798b8
6128375992

(lldb) po (SEL)0x00000001df7fd10c
"init"

可以知道,这里有一个对象调用了 init 方法,参数为2.

2、找到第二个bl的地方:

(lldb) register read x0
      x0 = 0x00000002838102a0
(lldb) register read x1
      x1 = 0x00000001df7fd10c  
(lldb) register read x2
      x2 = 0x0000000000000002
(lldb) po 0x00000002838102a0
<NSConditionLock: 0x2838102a0>{condition = 0, name = nil}

(lldb) po 0x00000001df7fd10c
8044663052

(lldb) po (SEL)0x00000001df7fd10c
"init"

可以知道,这里NSConditionLock调用了 init 方法,参数为2.

3、找到第三个bl的地方:

(lldb) register read x0
      x0 = 0x00000002838102a0
(lldb) register read x1
      x1 = 0x00000001deb4f88c  
(lldb) register read x2
      x2 = 0x0000000000000002
(lldb) po 0x00000002838102a0
<NSConditionLock: 0x2838102a0>{condition = 0, name = nil}

(lldb) po (SEL)0x00000001deb4f88c
"zone"

可以知道,这里NSConditionLock调用了 zone 方法,参数为2.

4、找到第四个bl的地方:

(lldb) register read x0
      x0 = 0x00000001ee2da7a8  (void *)0x00000001ee2da7d0: NSCondition
(lldb) register read x1
      x1 = 0x00000001de9bad0c  
(lldb) register read x2
      x2 = 0x00000001eeafc000  libsystem_malloc.dylib`virtual_default_zone
(lldb) po 0x00000001ee2da7a8
NSCondition

(lldb) po (SEL)0x00000001de9bad0c
"allocWithZone:"

(lldb) po 0x00000001eeafc000
8299462656

可以知道,这里NSCondition调用了 allocWithZone 方法.

5、找到第五个bl的地方:

(lldb) register read x0
      x0 = 0x00000002821a4000
(lldb) register read x1
      x1 = 0x00000001df7fd10c  
(lldb) register read x2
      x2 = 0xffffffffffffffd0
(lldb) po 0x00000002821a4000
<NSCondition: 0x2821a4000>{name = nil}

(lldb) po (SEL)0x00000001df7fd10c
"init"

(lldb) po 0xffffffffffffffd0
²šO

(lldb) 

可以知道,这里NSCondition调用了 init 方法.

6、我们来到retab这里

(lldb) register read x0
      x0 = 0x0000000281df0030
(lldb) po 0x0000000281df0030
<NSConditionLock: 0x281df0030>{condition = 2, name = nil}

(lldb) x/6gx 0x0000000281df0030
0x281df0030: 0x01000001ee2da849 0x0000000000000000
0x281df0040: 0x00000002821a4000 0x0000000000000000
0x281df0050: 0x0000000000000002 0x0000000000000000

(lldb) po 0x00000002821a4000
<NSCondition: 0x2821a4000>{name = nil}

可以得出, NSConditionLock里面有一个NSCondition成员变量。

7、继续下符号断点:[NSConditionLock lockWhenCondition:1][NSConditionLock unlockWithCondition:0]

image.png

image.png

发现调用了distantFuture

8、

image.png

image.png

9、继续下符号断点[NSConditionLock lockWhenCondition:beforeDate:]

image.png

image.png 10、

image.png

11、

image.png image.png

12、

image.png

image.png

看到[NSCondition lock]

image.png

看到[NSCondition broadcast]

14、

image.png

image.png

看到[NSCondition unlock]

小结:

- [NSConditionLock initWithCondition:]
 1: [? init:2]
 2: [NSConditionLock init]
 3: [NSConditionLock zone:]
 4: [NSCondition allocWithZone]
 5: [NSCondition init]

- [NSConditionLock lockWhenCondition:1]
    1: [NSDate distantFuture]
    2: [NSConditionLock lockWhenCondition:beforeDate:]
        2.1: [NSCondition lock];
        2.2: waitUntilDate 
        2.3: 0x1  1 ->: 不再等待
        2.4: unlock
        
- SConditionLock unlockWithCondition:0]
    1: [Condition lock]
    2: [Condition broadcast]
    3: [NSCondition unlock]

底层源码分析

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()
#if os(Windows)
        _thread = INVALID_HANDLE_VALUE
#else
        _thread = nil
#endif
        _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()
#if os(Windows)
        _thread = INVALID_HANDLE_VALUE
#else
        _thread = nil
#endif
        _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
            }
        }
#if os(Windows)
        _thread = GetCurrentThread()
#else
        _thread = pthread_self()
#endif
        _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
            }
        }
#if os(Windows)
        _thread = GetCurrentThread()
#else
        _thread = pthread_self()
#endif
        _cond.unlock()
        return true
    }
    
    open var name: String?
}

image.png

三、读写锁

image.png

image.png

读写锁的实现:

/**
 读写锁:
 1、多读单写
 2、写写互斥
 3、读写互斥
 
 采用的方法:
 1、写操作采用函数:dispatch_barrier_async,保证了写写互斥,读写互斥,不阻塞主线程任务的执行
 2、读操作: 并发队列 ,dispatch_sync (之所以不使用dispatch_async,是为了防止返回值为空的情况出现) ,保证了多读
 
 */
 
@property (nonatomic, strong) NSMutableDictionary *dict;
@property (nonatomic, strong) dispatch_queue_t queue;
 
self.dict = [NSMutableDictionary dictionary];
self.queue = dispatch_queue_create("bingfaduilie", DISPATCH_QUEUE_CONCURRENT);
[self writeDataWithName:@"我的"];
[self writeDataWithName:@"名字是"];
[self writeDataWithName:@"jim"];
    
#pragma mark --- (void)writeDataWithName:(NSString *)name{
    dispatch_barrier_async(self.queue, ^{
        [self.dict setValue:name forKey:@"name"];
        NSLog(@"%@",name);
    });
}


#pragma mark --- (NSString *)readData{
    __block NSString *name = @"";
    dispatch_sync(self.queue, ^{
        name = self.dict[@"name"];
    });
    return name;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    dispatch_async(self.queue, ^{
        NSLog(@"%@", [self readData]);
    });
}

四、锁的归类

image.png

image.png