美文网首页
iOS底层探索-多线程锁

iOS底层探索-多线程锁

作者: spyn_n | 来源:发表于2021-12-11 19:41 被阅读0次

多线程的锁大致可分为两大类:互斥锁、自旋锁;也可以分为三类:互斥锁、自旋锁、读写锁。

一、互斥锁:互斥+同步(强调的是顺序)

为了保证共享数据操作的安全完整性。对于对象来讲保证每一时刻,只能有一个线程访问该对象,其可以保护单线程代码。

  1. 递归锁 :NSRecursiveLock,可递归,但是无法多线程递归
  2. 不递归 : NSLock,不可以递归加锁
二、自旋锁:互斥 + 忙等
三、读写锁:

特点:

  • 同一时间,只能有一个线程进行写的操作(写互斥)
  • 同一时间,允许多个线程进行读操作
  • 同一时间,不允许既有写的操作,又有读的操作(读写互斥)
- (void)psy_safeWrite:(NSString *)result time:(int)time{
      dispatch_barrier_async(self.psy_queue, ^{  // 在异步线程中加入栅栏函数
          sleep(time);
          self.mDict[@"pansy"] = result;
          NSLog(@"写入: %@ -- %@",result,[NSThread currentThread]);
      }); 
}
- (NSString *)psy_safeRead:(int)time{ 
    __block NSString *result = @"还未赋值"; 
    dispatch_sync(self.psy_queue, ^{  // 加同步函数
          result = self.mDict[@"pansy"];
          NSLog(@"读取: %@ -- %@",result,[NSThread currentThread]);   
      });
      return result;
  }

OK,下面就讲讲iOS的锁,大佬勿喷。

  1. OSSpinLock
      OSSpinLock 是一种自旋锁,会出现do...while忙等,缺点是在等待过程中会损耗CPU资源,因为每一条线程会被分配10~100ms时间片,操作系统线程调度采用时间片轮转调度算法(Round Robin,RR),它会占用线程时间片,也就是CPU资源,而不是线程休眠挂起的方式。而且会出现线程优先级翻转可能性,即低优先级线程先访问资源,高优先级等待低优先级的情况(在访问资源和执行任务的时候),已经不安全,iOS10开始以后就被os_unfair_lock替代了
    相关函数:
  • OSSpinLock psy_spinlock = OS_SPINLOCK_INIT;
  • OSSpinLockLock(&psy_spinlock);
  • OSSpinLockUnlock(&psy_spinlock);
    案例
__block OSSpinLock psy_spinlock = OS_SPINLOCK_INIT;
 dispatch_queue_t concurrentQueue = dispatch_queue_create("psybiu", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
    OSSpinLockLock(& psy_spinlock);
    printf("操作1 开始\n");
    sleep(3);
    printf("操作1 结束\n");
    OSSpinLockUnlock(& psy_spinlock);
    
});

dispatch_async(concurrentQueue, ^{
    OSSpinLockLock(& psy_spinlock);
    sleep(1);
    printf("操作2\n");
    OSSpinLockUnlock(& psy_spinlock);
    
});

输出结果为:
操作1 开始
操作1 结束
操作2
  1. os_unfair_lock
    要使用,需要导入头文件#import <os/lock.h>
    os_unfair_lock psy_unfairlock = OS_UNFAIR_LOCK_INIT;
    os_unfair_lock_lock(&psy_unfairlock);
    os_unfair_lock_unlock(&psy_unfairlock);
    案例
__block os_unfair_lock psy_unfairlock = OS_UNFAIR_LOCK_INIT;
    dispatch_queue_t concurrentQueue = dispatch_queue_create("psybiu", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue, ^{
        os_unfair_lock_lock(&psy_unfairlock);
            printf("操作1 开始\n");
            sleep(3);
            printf("操作1 结束\n");
            os_unfair_lock_unlock(&psy_unfairlock);
        
    });
    
    dispatch_async(concurrentQueue, ^{
        sleep(1);
        os_unfair_lock_lock(&psy_unfairlock);
        printf("操作2\n");
        os_unfair_lock_unlock(&psy_unfairlock);
    });

输出结果为:
操作1 开始
操作1 结束
操作2

  1. dispatch_semaphore_t
    GCD的信号量,当信号量是1的时候就可以当锁来使用。主要的函数如下,wait和signal是成对配合出现的,一个是等待信号量减1,一个是信号量加1。
    dispatch_semaphore_t psy_sem = dispatch_semaphore_create(1);
    dispatch_semaphore_wait(psy_sem, DISPATCH_TIME_FOREVER);
    dispatch_semaphore_signal(psy_sem);
    案例
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
    for (NSInteger k = 0; k < 4; k++) {
        dispatch_semaphore_wait(semaphore, timeout);
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
             for (NSInteger i = 0; i < 10; i++) {
                printf("k:%ld( %ld )--- ",k,i);
             }
            dispatch_semaphore_signal(semaphore);
        });
    }
输出结果为:
k:0( 0 )--- k:0( 1 )--- k:0( 2 )--- k:0( 3 )--- k:0( 4 )--- k:0( 5 )--- k:0( 6 )--- k:0( 7 )--- k:0( 8 )--- k:0( 9 )--- k:1( 0 )--- k:1( 1 )--- k:1( 2 )--- k:1( 3 )--- k:1( 4 )--- k:1( 5 )--- k:1( 6 )--- k:1( 7 )--- k:1( 8 )--- k:1( 9 )--- k:2( 0 )--- k:2( 1 )--- k:2( 2 )--- k:2( 3 )--- k:2( 4 )--- k:2( 5 )--- k:2( 6 )--- k:2( 7 )--- k:2( 8 )--- k:2( 9 )--- k:3( 0 )--- k:3( 1 )--- k:3( 2 )--- k:3( 3 )--- k:3( 4 )--- k:3( 5 )--- k:3( 6 )--- k:3( 7 )--- k:3( 8 )--- k:3( 9 )---
  1. pthread_mutex_t
    要使用需要导入头文件#import <pthread/pthread.h>
    初始化一个pthread_mutex_t互斥锁,可以PTHREAD_MUTEX_INITIALIZER静态初始化,也可以int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)动态初始化
    操作:
    pthread_mutex_lock(&psy_metext); 加锁
    pthread_mutex_unlock(&psy_metext); 解锁
    pthread_mutex_trylock(&psy_metext); 这个方法与pthread_mutex_lock类似,不一样的是trylock在加锁时如果锁已经被占用,则返回EBUSY而不是挂起等待。
    案例
__block pthread_mutex_t customLock;
    pthread_mutex_init(&customLock, NULL);
    dispatch_queue_t concurrentQueue = dispatch_queue_create("psybiu", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(concurrentQueue, ^{
        pthread_mutex_lock(&customLock);
            printf("操作1 开始");
            sleep(3);
            printf("操作1 结束");
            pthread_mutex_unlock(&customLock);
        
    });
    
    dispatch_async(concurrentQueue, ^{
        sleep(1);
        pthread_mutex_lock(&customLock);
        printf("操作2");
        pthread_mutex_unlock(&customLock);
    });

输出结果为:
操作1 开始
操作1 结束
操作2
  1. pthread_mutex_recursive
    pthread_mutex_t psy_metext_recurive;
    pthread_mutexattr_t attr;
    pthread_mutexattr_init (&attr);
    pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init (&psy_metext_recurive, &attr);

pthread_mutex_lock(&psy_metext_recurive);
pthread_mutex_unlock(&psy_metext_recurive);

@protocol NSLocking

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

@end
  1. NSLock
    其属于Foundation框架,但是这个框架没有开源,可以看一下其头文件源码如下<NSLock源码:>:
    NSLock *psy_lock = [[NSLock alloc] init]; 创建锁对象
    [psy_lock lock]; 加锁
    [psy_lock unlock]; 解锁
    NSLock源码:
@interface NSLock : NSObject <NSLocking> {
@private
    void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end

实例

 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) {
                        printf("current value = %d\n",value);
                        testMethod(value - 1);
                    }
            };
            [lock lock];
            testMethod(10);   // 对关键业务代码加锁
            [lock unlock];
        });
    }

其输出结果是输出10次的:
current value = 10
current value = 9
current value = 8
current value = 7
current value = 6
current value = 5
current value = 4
current value = 3
current value = 2
current value = 1
.................

虽然OC的Foundation框架没有开源,但是可以查看swift的Foundation框架,从swift查看NSLock底层源码实现,底层封装的是pthread_mutex_t,
NSLock *psy_lock = [[NSLock alloc] init]; -->底层就是进行了pthread_mutex_init(mutex, nil);
[psy_lock lock]; --> 实际上底层是调用了 pthread_mutex_lock
[psy_lock unlock]; --> pthread_mutex_unlock

  1. NSRecursiveLock
    底层也是封装的pthread_mutex_t
    NSRecursiveLock *psy_recursiveLock = [NSRecursiveLock new];
    [psy_recursiveLock lock]; --> 实际上底层是调用了 pthread_mutex_lock
    [psy_recursiveLock unlock]; -->pthread_mutex_unlock
    源码
@interface NSRecursiveLock : NSObject <NSLocking> {
@private
    void *_priv;
}

- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;

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

@end

案例

NSRecursiveLock  *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) {
                      printf("current value = %d\n",value);
                      testMethod(value - 1);
                  }
                [self.recursiveLock unlock];
            };
            
            testMethod(10);
            [lock unlock];
        });
//    }
输出结果为:
current value = 10
current value = 9
current value = 8
current value = 7
current value = 6
current value = 5
current value = 4
current value = 3
current value = 2
current value = 1

查看swift的Foundation框架,与NSLock的底层实现非常相似,不一样的就是NSRecursiveLock对pthread_mutexattr_t属性进行了配置

internal var mutex = _RecursiveMutexPointer.allocate(capacity: 1)

withUnsafeMutablePointer(to: &attrib) { attrs in
            pthread_mutexattr_init(attrs)
            // 设置了type为PTHREAD_MUTEX_RECURSIVE
            pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE)) 
            pthread_mutex_init(mutex, attrs)
        }
  1. NSCondition
    NSCondition *psy_condition = [NSCondition new];
    [psy_condition lock];
    [psy_condition unlock];
    源码
@interface NSCondition : NSObject <NSLocking> {
@private
    void *_priv;
}
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end

案例
生产与消费问题,当生产为0,不允许消费,直到生产 >=1 才允许消费。

self.ticketCount = 0;
_testCondition = [[NSCondition alloc] init];
    //创建生产-消费者
    for (int i = 0; i < 50; i++) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [_testCondition lock]; // 操作的多线程影响
            self.ticketCount = self.ticketCount + 1;
            NSLog(@"生产一个 现有 count %zd",self.ticketCount);
            [_testCondition signal]; // 信号
            [_testCondition unlock];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
              [_testCondition lock];  // 操作的多线程影响
              if (self.ticketCount == 0) {
                   NSLog(@"等待 count %zd",self.ticketCount);
                  [_testCondition wait];
              }
              //注意消费行为,要在等待条件判断之后
              self.ticketCount -= 1;
              NSLog(@"消费一个 还剩 count %zd ",self.ticketCount);
              [_testCondition unlock];
        });
    }
其输出结果:比较长可自己运行看看

在swift的Foundation中其本质也是封装的pthread_mutex_t,[[NSCondition alloc] init],实际上底层做了两件事:

  • pthread_mutex_init(mutex, nil)
  • pthread_cond_init(cond, nil)
    比NSLock多了一个pthread_cond_init,具体源码如下:
public override init() {
        pthread_mutex_init(mutex, nil)
        pthread_cond_init(cond, nil)
    }
 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
    }
  1. NSConditionLock
    NSConditionLock *psy_conditionLock = [NSConditionLock new];
    [psy_conditionLock lock];
    [psy_conditionLock unlock];
    源码:
@interface NSConditionLock : NSObject <NSLocking> {
@private
    void *_priv;
}

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

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (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

案例

NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        [conditionLock lockWhenCondition:1];
        printf("线程 1\n");
        [conditionLock unlockWithCondition:0];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        [conditionLock lockWhenCondition:2];
        sleep(0.1);
        printf("线程 2\n");
        [conditionLock unlockWithCondition:1];
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
       [conditionLock lock];
       printf("线程 3\n");
       [conditionLock unlock];
    });
运行结果:
线程 3
线程 2
线程 1

别看NSConditionLock多了很多方法,其内部封装了NSCondition并在原来的基础上做一些扩展,其swift源码如下:
swift 源码

internal var _cond = NSCondition()   // 直接使用NSCondition创建条件锁
    internal var _value: Int  // 定义一个条件值
    internal var _thread: _swift_CFThreadRef?
    
    public convenience override init() {
        self.init(condition: 0)   // init方法,condition默认为0
    }
    
    public init(condition: Int) {
        _value = condition
    }

    open func lock() {  // lock 默认 Date.distantFuture
        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
    }
  1. @synchronized
      其是一把互斥锁,但是前提是标识objc要一样,如果标识不一样,就达不到互斥效果。其具有可重入多线程可递归性,因为操作简单,功能强大,深受青睐。
    这篇文章已经分析过@synchronized锁了
    操作
    @synchronized(objc) {
       // 业务代码
    }
    案例
// @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) {
                        printf("current value = %d\n",value);
                        testMethod(value - 1);
                    }
                }
            };
            testMethod(10);
        });
    }
其输出结果是输出10次下列:
current value = 10
current value = 9
current value = 8
current value = 7
current value = 6
current value = 5
current value = 4
current value = 3
current value = 2
current value = 1
.................

相关文章

网友评论

      本文标题:iOS底层探索-多线程锁

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