概述

在开发进程中,运用多线程来能够提高程序运行功率。本文不说多线程,重点说说锁的运用。

什么时分需求用到锁呢?

比如相亲,多少独身狗的痛。你经过七大姑八大姨的介绍,争取到了一个相亲的时机,于是你就屁颠屁颠的去见人家姑娘了。成果殊不知,等你到了人家姑娘的家中后,发现她正在和另一个同学相谈甚欢,这个时分你能进去见人家姑娘吗?明显不能。或许她的妈妈就在门口看着呢。此处的妈妈的责任就是确保正在进行相亲不会由于其他相亲者到来而被中止,此时,你应该怎么办,等呗!你必须等那位同学相亲走了,你才能够进去和人家闺女相亲。

iOS 中的锁就相当于妈妈,当一个线程拜访数据的时分,其他的线程就必须等到该线程拜访完毕今后才可拜访。

假如在同一时刻多个线程对同一数据进行拜访,会呈现不行预期的成果,这就造成了线程不安全。比如,一个线程正在写入数据,假如这个时分有另一个线程来读取数据,那么获取到的数据将是不行预期的,也不是你真实想要的数据。

iOS 中常见的锁

为了线程在拜访时的安全性,咱们有必要运用锁来确保。iOS 开发中,常用的锁有以下几种:

  • SLock
  • SRecursiveLock
  • SCondition
  • SConditionLock
  • thread_mutex
  • hread_rwlock
  • OSIX Conditions
  • SSpinLock
  • s_unfair_lock
  • synchronized

NSLock

NSLock 完成了最基本的互斥锁,遵循了NSLocking 协议,经过 lockunlock 办法加锁和解锁。

@interface ViewController ()
@property (strong, nonatomic) NSLock *lock;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.lock = [[NSLock alloc] init];
    NSThread *yourThread = [[NSThread alloc] initWithTarget:self selector:@selector(blindDate) object:nil];
    yourThread.name = @"你";
    NSThread *otherThead = [[NSThread alloc] initWithTarget:self selector:@selector(blindDate) object:nil];
    otherThead.name = @"他人";
    [yourThread start];
    [otherThead start];
}
- (void)blindDate {
    [self.lock lock];
    NSLog(@"%@正在相亲", [NSThread currentThread].name);
    sleep(2);
    NSLog(@"%@相亲完毕", [NSThread currentThread].name);
    [self.lock unlock];
}

iOS 锁

假如咱们把锁撤掉:

iOS 锁

是不是乱套了,妹纸此时此刻应该是一脸懵逼的状况。

NSRecursiveLock

归锁,该锁能够被一个线程多次获取,而不会引起死锁。它记录了成功取得锁的次数,每一次成功的获取锁,就必须有与其对应的开释锁,确保不会呈现死锁。而且只需一切的锁被开释之后,其他线程才能够持续取得锁。

self.lock = [[NSRecursiveLock alloc] init];
NSThread *interviewThread1 = [[NSThread alloc] initWithTarget:self selector:@selector(interview) object:nil];
interviewThread1.name = @"面试官 Jack";
[interviewThread1 start];
NSThread *interviewThread2 = [[NSThread alloc] initWithTarget:self selector:@selector(interview) object:nil];
interviewThread2.name = @"面试官 Rose";
[interviewThread2 start];
- (void)interview {
for (int i = 1; i < 4; i++) {
    [self.lock lock];
    NSLog(@"提名人%d正在面试,%@繁忙", i, [NSThread currentThread].name);
    sleep(2);
    NSLog(@"提名人%d面试完毕,%@空闲", i, [NSThread currentThread].name);
    [self.lock unlock];
}
}

iOS 锁

iOS 锁

从输出成果看到,加锁之前,两个面试官一起面试一个提名人。而在加锁之后,面试场景变为初试和复试,先由一个面试官 Jack 担任初试,由面试官 Rose 担任复试。这个面试的场景或许放在这儿欠妥,咱们主要了解递归锁的运用办法。

NSCondition

NSCondition 是一种特殊的锁,它能够完成不同线程间的调度。线程 1 因不满足某一个条件而遭到堵塞,直到线程 2 满足该条件从而发出放行信号给线程 1,尔后,线程 1 才能够被正确履行。

下面咱们模拟图片下载到处理的进程,拓荒一个线程远程网络下载图片,拓荒另一个线程处理下载好的图片,由于处理图片的线程因没有图片,而被限行,只需在图片下载完结后,才去处理图片。这样就能够在下载线程完结图片下载后发出一个信号,让另一个线程在拿到图片后在其线程上处理图片。

self.condition = [[NSCondition alloc] init];
NSThread *downloadImageThread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage) object:nil];
downloadImageThread.name = @"下载图片";
NSThread *dealWithImageThread = [[NSThread alloc] initWithTarget:self selector:@selector(dealWithImage) object:nil];
dealWithImageThread.name = @"处理图片";
[downloadImageThread start];
[dealWithImageThread start];
static BOOL finished = NO;
- (void)downloadImage {
    [self.condition lock];
    NSLog(@"正在下载图片...");
    sleep(2);
    NSLog(@"图片下载完结");
    finished = YES;
    if (finished) {
        [self.condition signal];
        [self.condition unlock];
    }
}
- (void)dealWithImage {
    [self.condition lock];
    while (!finished) {
        [self.condition wait];
    }
    NSLog(@"正在处理图片...");
    sleep(2);
    NSLog(@"图片处理完结");
    [self.condition unlock];
}

iOS 锁

NSConditionLock

NSConditionLock 互斥锁跟 NSCondition 很像,能够在某种条件下进行加锁和解锁,但完成方法不同。当两个线程需求特定顺序履行的时分,就能够运用 NSConditionLock。如生产者顾客模型,当生产者履行的时分,顾客能够经过特定的条件取得锁;当生产者完结时解锁,然后把说的条件设置成唤醒顾客线程的条件。

加锁和解锁调用能够随意组合,lockunlockWithCondition: 合作运用,lockWhenCondition:unlock 合作运用。

@interface ViewController ()
@property (strong, nonatomic) NSConditionLock *conditionLock;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.conditionLock = [[NSConditionLock alloc] init];
    NSThread *producerThread = [[NSThread alloc] initWithTarget:self selector:@selector(producer) object:nil];
    producerThread.name = @"生产者";
    NSThread *consumerThread = [[NSThread alloc] initWithTarget:self selector:@selector(consumer) object:nil];
    consumerThread.name = @"顾客";
    [producerThread start];
    [consumerThread start];
}
- (void)producer {
    [self.conditionLock lock];
    NSLog(@"生产产品中...");
    sleep(2);
    NSLog(@"产品生产完结");
    [self.conditionLock unlockWithCondition:2];
}
- (void)consumer {
    [self.conditionLock lockWhenCondition:2];
    sleep(2);
    NSLog(@"顾客运用产品");
    [self.conditionLock unlock];
}

当生产者开释锁时,将条件设置为2,顾客就能够经过此条件取得锁,从而程序履行。假如生产者和顾客给出的条件不一致,会导致程序不能正常履行。

iOS 锁

pthread_mutex

POSIX 互斥锁,在运用时,只需初始化一个 pthread_mutex_t,运用 pthread_mutex_lockpthread_mutex_unlock 来加锁解锁,运用完结后,需求运用 pthread_mutex_destroy 来销毁锁。

#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@end
@implementation ViewController {
    pthread_mutex_t mutex_lock;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    pthread_mutex_init(&mutex_lock,NULL);
    pthread_mutex_lock(&mutex_lock);
    // .....
    pthread_mutex_unlock(&mutex_lock);
    pthread_mutex_destroy(&mutex_lock);
}

pthread_rwlock

读写锁,当咱们对文件数据进行读写操作时,写操作是排他的。一旦有多个线程对同一文件数据进行写操作,那成果不行预期。多个线程对同一文件读取时是可行的。

当读写锁被一个线程以读的形式占用时,写操作的其他线程就会被堵塞,但读操作的其他线程能够持续作业。 当读写锁被一个线程以写的形式占用时,写操作的其他线程会被堵塞,读操作的其他线程也被堵塞。

#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@end
@implementation ViewController {
    pthread_rwlock_t rwlock;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;
    rwlock = lock;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self readDataWithFlag:1];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self readDataWithFlag:2];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self writeDataWithFlag:3];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self writeDataWithFlag:4];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self readDataWithFlag:5];
    });
}
- (void)readDataWithFlag:(NSInteger)flag {
    pthread_rwlock_rdlock(&rwlock);
    NSLog(@"开端读取文件数据 -- %ld", flag);
    sleep(2);
    NSLog(@"读取完结 -- %ld", flag);
    pthread_rwlock_unlock(&rwlock);
}
- (void)writeDataWithFlag:(NSInteger)flag {
    pthread_rwlock_wrlock(&rwlock);
    NSLog(@"开端写入文件数据 -- %ld", flag);
    sleep(2);
    NSLog(@"写入完结 -- %ld", flag);
    pthread_rwlock_unlock(&rwlock);
}

iOS 锁

POSIX Conditions

POSIX 条件锁 = 互斥锁 + 条件。初始化条件和互斥锁,当 ready_to_go 为 flase 的时分,进入循环,然后线程将会挂起,直到另一个线程将 ready_to_go 设置为 ture 的时分,而且发送信号的时分,该线程才会被唤醒。

#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@end
@implementation ViewController {
    pthread_mutex_t mutex;
    pthread_cond_t condition;
    Boolean ready_to_go;
}
- (void)condInit {
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&condition, NULL);
    ready_to_go = true;
}
- (void)waitWhenConditionCompleted {
    pthread_mutex_lock(&mutex);
    while (ready_to_go == false) {
        pthread_cond_wait(&condition, &mutex);
    }
    // insert your code ...
    ready_to_go = false;
    pthread_mutex_unlock(&mutex);
}
- (void)signalThreadUsingCondition {
    pthread_mutex_lock(&mutex);
    ready_to_go = true;
    pthread_cond_signal(&condition);
    pthread_mutex_unlock(&mutex);
}

OSSpinLock

自旋锁,与互斥锁相似。但二者还是有区别的:

  1. 互斥锁,当一个线程取得这个锁后,其他想要取得此锁的线程将会被堵塞,直到该锁被开释。
  2. 自旋锁,当一个线程取得这个锁后,其他线程将会一向循环检测,可该锁时分被解锁。

因而,自旋锁适用于持有者在较短的情况下持有该锁。

#import "ViewController.h"
#import <libkern/OSAtomic.h>
@interface ViewController ()
@end
@implementation ViewController {
    OSSpinLock spinLock;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    spinLock = OS_SPINLOCK_INIT;
    OSSpinLockLock(&spinLock);
    OSSpinLockUnlock(&spinLock);
}

当然,YYKit 作者也有提到过 不再安全的 OSSpinLock,这个自旋锁存在优先级回转的问题。因而这种锁也不再安全。在 iOS 10.0 后,Apple 也将其弃用了。

os_unfair_lock

OSSpinLock 因存在优先级回转的问题而使得其不再安全,为此 Apple 推出了 os_unfair_lock_t,用于解决回转的问题。

#import "ViewController.h"
#import <os/lock.h>
@interface ViewController ()
@end
@implementation ViewController {
}
- (void)viewDidLoad {
    [super viewDidLoad];
    os_unfair_lock_t unfair_lock;
    unfair_lock = &(OS_UNFAIR_LOCK_INIT);
    os_unfair_lock_lock(unfair_lock);
    // insert your code ...
    os_unfair_lock_unlock(unfair_lock);
}

dispatch_semaphore

运用信号量的机制完成锁的功用,相同也是等候信号和发送信号的进程。当多个线程拜访时,只需有一个取得信号,那么其他线程就必须等候该信号开释。

#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController {
    dispatch_semaphore_t semaphore_t;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_semaphore_wait(semaphore_t, DISPATCH_TIME_FOREVER);
    // insert your code ...
    dispatch_semaphore_signal(semaphore_t);
}

@synchronized

便捷的互斥锁。假如你在多个线程中传过去的是同一个标识符,那么先取得锁的会确定其中的代码块,其他线程将会被堵塞,反之亦然。但运用这种锁,程序功率比较低,所以咱们更多的会运用其他方法的锁来代替。

@synchronized (self) {
    // insert your code ...
}

总结

  1. 假如进行文件读写操作,运用 pthread_rwlock 比较好。由于文件读写通常会消耗很多资源,假如运用互斥锁,在多个线程一起读取文件的时分,会堵塞其他读文件的线程,而 pthread_rwlock 则不会这样,所以运用其进行文件的读写,会更合适一点。
  2. 假如用到互斥锁,当功能要求比较高,可运用 pthread_mutex 或者 dispath_semaphore

参考

# iOS中的各种锁