iOS锁下

上篇文章介绍synchronized锁,今天介绍的是其他常用锁:NSLock,NSRecursiveLock ,NSCondition,NSConditionLock

锁的概念

锁的分类——互斥锁,自旋锁,读写锁

自旋锁
  • 自旋锁是一种用于保护多线程共享资源的锁,与一般互斥锁(mutex)不同之处在于当它尝试获取锁时以忙等待(busy waiting)的形式不断地循环检查锁是否可用。当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会一直等待(不会睡眠),当上一个线程的任务执行完毕,下一个线程会立即执行。在多CPU的环境中,对持有锁较短的程序来说,使用自旋锁代替一般的互斥锁往往能够提高程序的性能。

  • 优点:自旋锁不会引起调用者睡眠,所以不会进行线程调度、CPU时间片轮转等耗时操作。所有如果能在很短的时间内获得锁,自旋锁的效率远高于互斥锁

  • 缺点:自旋锁一直占用CPU,他在未获得锁的情况下一直运行(自旋)占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低

总结:效率高,但是一直占用CPU耗费资源,不能实现递归调用。

互斥锁

什么是互斥锁呢?

  • 当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会进入睡眠状态等待任务执行完毕,此时CPU可以调度其他线程。当上一个线程的任务执行完毕,下一个线程会自动唤醒然后执行任务。
  • 说到互斥后想到同步,同步是只有一个任务执行完了,下个任务才可以执行。

同步:互斥+顺序

常见的互斥锁:@synchronizedNSLockpthread_mutexNSConditionLock(条件锁),NSCondition(条件锁),NSRecursiveLock(递归锁)
以上概念转载自:https://juejin.cn/post/6999520963658268709/

锁的归类

条件锁:就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了,当资源被分配到了,条件锁打开,进程继续运行

  • NSCondition
  • NSConditionLock

递归锁:就是同一个线程可以加锁N次而不会引发死锁

  • NSRecursiveLock
  • pthread_mutext(recursive)

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

  • dispatch_semaphore

读写锁

读写锁实际是一种特殊的互斥锁,他把对共享资源的访问分为读者和写者,读者只有对共享资源的读权限,写着可以对共享资源做写处理。所以一般我们在读的时候不会去加互斥锁,允许多个读者进行访问,但是如果有遇到写者访问资源,这时候必须对资源进行加锁,等其访问结束后才能继续读/写
特性:

  • 多个读者同时访问
  • 读与写互斥
  • 写和写互斥即单写

上面都是一些概念,接下来就开始详细分析常用的锁

NSLock,NSRecursiveLock

简单的锁,不能够递归嵌套也不能多线程

- (void)testRecursive{
    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){
                // [lock lock];
                    if (value > 0) {
                        NSLog(@"current value = %d",value);
                        testMethod(value - 1);
                    }
               // [lock unlock];
            };
            [lock lock];
            testMethod(10);
            [lock unlock];
        });
    }
}

输出:


image.png

这边我们的锁直接加在testMethod(10);等线程这边递归完成后再继续下个线程,但是有时候我们可能会加在递归函数里面,如上面的注释,那就会造成死锁,为什么会造成死锁呢?在testMethod刚进去加锁,但是testMethod还没出来又嵌套进入到testMethod里又加锁,就造成了死锁,解决办法就是使用嵌套锁NSRecursiveLock/synchronized

- (void)testRecursive{
    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){
                [self.recursiveLock lock];
                    if (value > 0) {
                        NSLog(@"current value = %d -- %@",value, [NSThread currentThread]);
                        testMethod(value - 1);
                    }
                [self.recursiveLock unlock];
            };
            testMethod(10);
        });
    }
}

输出结果:
 current value = 10 -- {number = 3, name = (null)}
 current value = 9 -- {number = 3, name = (null)}
 current value = 8 -- {number = 3, name = (null)}
 current value = 7 -- {number = 3, name = (null)}
current value = 6 -- {number = 3, name = (null)}
 current value = 5 -- {number = 3, name = (null)}
current value = 4 -- {number = 3, name = (null)}
 current value = 3 -- {number = 3, name = (null)}
 current value = 2 -- {number = 3, name = (null)}
 current value = 1 -- {number = 3, name = (null)}

这边使用NSRecursiveLock尝试解决,但是很遗憾还是崩,只是崩溃的不一样,最后输出的结果如上图,为什么呢?因为NSRecursiveLock不支持多线程,如果只开一个线程就不会崩,但是换成synchronized就OK

- (void)testRecursive{
    NSLock *lock = [[NSLock alloc] init];
    for (int i= 0; i<2; 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, [NSThread currentThread]);
                        testMethod(value - 1);
                    }
                }
            };
            testMethod(10);
        });
    }
}
输出结果:
current value = 10 -- {number = 4, name = (null)}
current value = 9 -- {number = 4, name = (null)}
current value = 8 -- {number = 4, name = (null)}
current value = 7 -- {number = 4, name = (null)}
current value = 6 -- {number = 4, name = (null)}
current value = 5 -- {number = 4, name = (null)}
current value = 4 -- {number = 4, name = (null)}
current value = 3 -- {number = 4, name = (null)}
current value = 2 -- {number = 4, name = (null)}
current value = 1 -- {number = 4, name = (null)}
current value = 10 -- {number = 5, name = (null)}
current value = 9 -- {number = 5, name = (null)}
current value = 8 -- {number = 5, name = (null)}
current value = 7 -- {number = 5, name = (null)}
current value = 6 -- {number = 5, name = (null)}
current value = 5 -- {number = 5, name = (null)}
current value = 4 -- {number = 5, name = (null)}
current value = 3 -- {number = 5, name = (null)}
current value = 2 -- {number = 5, name = (null)}
current value = 1 -- {number = 5, name = (null)}

接下来我们来看看NSLockNSRecursiveLock的源码,因为NSLockNSRecursiveLock是在Foundation框架,这个是不开源的但是swiftFoundation框架是开源的,我们可以看看swiftFoundation框架

image.png

通过源码就知道NSLock就是对mutex的封装
image.png

这个也是对mutex的封装,但是在初始化跟NSlock有所不同,pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))添加了嵌套属性

NSCondition和NSConditionLock

NSCondition条件锁主要运用在生产着消费者模型

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

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

当消费者消费到没有物品时就会处于等待,而生产者每生产一个产品都会通知一下消费者,让那些处于等待状态的消费者及时消费。
底层是对cond的封装
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

通过lockWhenCondition控制线程的执行顺序,这边预先设置condition为2,因为线程2的条件是[conditionLock lockWhenCondition:2]满足条件会比线程1优先执行,执行完[conditionLock unlockWithCondition:1];设置为1,这样线程1就满足条件,等待cpu调度

读写锁

之前介绍的读写锁,多个线程可以同时对资源进行读操作不用加锁,但是写操作是需要加锁。这样我们就可以通过GCD的栅栏函数来实现

- (id)init{
    self = [super init];
    if (self){
        // 创建一个并发队列:
        self.concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
        // 创建数据字典:
        self.dataCenterDic = [NSMutableDictionary dictionary];
    }
    return self;
}

- (void)getObjc {
    for (int i=0; i<5; i++) {
        dispatch_async(self.concurrent_queue, ^{
            // 多个线程读取数据
            [self objectForKey:@"a"];
        });
    }
}

#pragma mark - 读数据
- (id)objectForKey:(NSString *)key{
    __block id obj;
    // 同步读取指定数据:
    dispatch_sync(self.concurrent_queue, ^{
        obj = [self.dataCenterDic objectForKey:key];
    });
    return obj;
}

#pragma mark - 写数据
- (void)setObject:(id)obj forKey:(NSString *)key{
    // 异步栅栏调用设置数据:
    dispatch_barrier_async(self.concurrent_queue, ^{
        [self.dataCenterDic setObject:obj forKey:key];
    });
}

注意:同步读取指定数据之所以要加dispatch_sync是为了满足读和写的互斥

你可能感兴趣的:(iOS锁下)