iOS 中的锁

57 阅读5分钟
  • 同步锁
  • 互斥锁
  • 条件锁
  • 递归锁
  • 自旋锁
  • 信号量

同步锁

@synchronized(self)

这个是objective-c的一个锁定令牌,防止self对象在同一时间内被其它线程访问,起到线程的保护作用。 一般在公用变量的时候使用,如单例模式或者操作类的static变量中使用。

@synchronized(self) {
    // 关键代码
}

互斥锁

互斥锁是用来保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问对象

1.NSLock

NSLock *lock = [NSLock new];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"线程1 尝试加锁ing...");
    [lock lock];
    sleep(3);//睡眠3秒
    NSLog(@"线程1");
    [lock unlock];
    NSLog(@"线程1解锁成功");
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"线程2 尝试加锁ing...");
    BOOL x =  [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];
    if (x) {
        NSLog(@"线程2");
        [lock unlock];
        NSLog(@"线程2解锁成功");
    }else{
        NSLog(@"失败");
    }
});

线程1 优先获得锁

其中线程2中的数字4,必须大于线程1执行执行的时间 3秒。否则线程2无法获得锁。将打印失败的日志。

如果将线程2中的代码换成

[lock lock];
sleep(3);//睡眠3秒
NSLog(@"线程2");
[lock unlock];
NSLog(@"线程2解锁成功");

这就相当于加了互斥锁, 谁都有优先获得锁的权利。并且都会获得锁。

2.pthread_mutex

pthread除了创建互斥锁,还可以创建递归锁、读写锁、once等锁

 __block pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, NULL);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"+++++ 线程1 start");
        pthread_mutex_lock(&mutex);
        sleep(2);
        pthread_mutex_unlock(&mutex);
        NSLog(@"+++++ 线程1 end");
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"----- 线程2 start");
        pthread_mutex_lock(&mutex);
        sleep(3);
        pthread_mutex_unlock(&mutex);
        NSLog(@"----- 线程2 end");
    });

这里是互斥锁, 其他锁待研究

条件锁

1.NSCondition

NSCondition 的对象实际上是作为一个锁和线程检查器,锁主要是为了检测条件时保护数据源,执行条件引发的任务。线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞。

- (NSArray*)removeLastImage:(NSMutableArray *)images {
    if (images.count > 0) {
        NSCondition *condition = [[NSCondition alloc] init];
        [condition lock];
        [images removeLastObject];
        [condition unlock];
        return images.copy;
    }else{
        return NULL;
    }
}

NSCondition可以给每个线程分别加锁,加锁后不影响其他线程进入临界区

- (void)testLock{
    self.conditionArray = [NSMutableArray array];
    self.condition = [[NSCondition alloc] init];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self.condition lock];
        if (self.conditionArray.count == 0) {
            NSLog(@"等待制作数组");
            [self.condition wait];
        }
        NSLog(@"获取对象进行操作:%@",self.conditionArray[0]);
        [self.condition unlock];
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self.condition lock];
        id obj = @"xxxxxxx";
        [self.conditionArray addObject:obj];
        NSLog(@"创建了一个对象:%@",obj);
        [self.condition signal];
        [self.condition unlock];
    });
}

等待2秒

NSCondition *cLock = [NSCondition new];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"start");
    [cLock lock];
    [cLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
    NSLog(@"线程1");
    [cLock unlock];
});

唤醒一个等待线程

NSCondition *cLock = [NSCondition new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [cLock lock];
    NSLog(@"线程1加锁成功");
    [cLock wait];
    NSLog(@"线程1");
    [cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [cLock lock];
    NSLog(@"线程2加锁成功");
    [cLock wait];
    NSLog(@"线程2");
    [cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(2);
    NSLog(@"唤醒一个等待的线程");
    [cLock signal];
});

唤醒所有等待的线程

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(2);
    NSLog(@"唤醒所有等待的线程");
    [cLock broadcast];
});

2. NSConditionLock

当达到某个条件时才可以获得锁, unlockWithCondition 解锁条件之后, 让某一个线程可以获得相应条件的锁。

NSConditionLock *conditionLock = [[NSConditionLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [conditionLock lock];
    NSLog(@"thread1");
    [conditionLock unlockWithCondition:2];
});
 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [conditionLock lockWhenCondition:2];
    NSLog(@"thread2");
    [conditionLock unlockWithCondition:3];
});
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [conditionLock lockWhenCondition:3];
    NSLog(@"thread3");
    [conditionLock unlock];
});

递归锁

NSRecursiveLock 是递归锁,他和 NSLock 的区别在于,NSRecursiveLock 可以在一个线程中重复加锁(反正单线程内任务是按顺序执行的,不会出现资源竞争问题),NSRecursiveLock 会记录上锁和解锁的次数,当二者平衡的时候,才会释放锁,其它线程才可以上锁成功。

NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    static void (^TestMethod)(int);
    TestMethod = ^(int value) {
        [recursiveLock lock];
        if (value > 0) {
            NSLog(@"加锁层数: %d",value);
            TestMethod(--value);
        }
        NSLog(@"程序退出!");
        [recursiveLock unlock];
    };
    TestMethod(3);
});

自旋锁

是用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。 自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。

1.os_unfair_lock

os_unfair_lock是在iOS10之后为了替代自旋锁OSSpinLock而诞生的,主要是通过线程休眠的方式来继续加锁,而不是一个“忙等”的锁。猜测是为了解决自旋锁的优先级反转的问题。

__block os_unfair_lock mlock = OS_UNFAIR_LOCK_INIT;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    os_unfair_lock_lock(&mlock);
    NSLog(@"线程1加锁");
    sleep(2);
    NSLog(@"线程1");
    os_unfair_lock_unlock(&mlock);
    NSLog(@"线程1释放锁");
});
    
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    os_unfair_lock_lock(&mlock);
    NSLog(@"线程2加锁");
    sleep(2);
    NSLog(@"线程2");
    os_unfair_lock_unlock(&mlock);
    NSLog(@"线程2释放锁");
});

信号量

1.dispatch_semaphore

如果获取不到锁,会将当前线程阻塞、休眠,直到其他线程释放锁时,会唤醒当前线程。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"task A");
    dispatch_semaphore_signal(semaphore);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"task B");
    dispatch_semaphore_signal(semaphore);
});

⚠️ 死锁

死锁是由于多个线程(进程)在执行过程中,因为争夺资源而造成的互相等待现象,你可以理解为卡主了。产生死锁的必要条件有四个:

互斥条件: 指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。

请求和保持条件: 指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。

不可剥夺条件: 指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

环路等待条件: 指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

NSLock *rLock = [NSLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    static void (^TestBlock)(int);
    TestBlock = ^(int value) {
        [rLock lock];
        if (value > 0) {
            NSLog(@"线程%d", value);
            TestBlock(value - 1);
        }
        [rLock unlock];
    };
    TestBlock(4);
});

最常见的就是 同步函数 + 主队列 的组合,本质是队列阻塞。

死锁是由于阻塞闭环造成的,那么我们只用消除其中一个因素,就能打破这个闭环,避免死锁。

参考链接 - 浅谈iOS中的锁的介绍及实战使用