1、介绍ios
在多线程开发中,锁的使用基本必不可少,主要是为了解决资源共享时出现争夺而致使数据不一致的问题,也就是线程安全问题。锁的种类不少,在实际开发中,须要根据状况选择性的选取使用,毕竟使用锁也是消耗CPU的。 本人虽然一直有使用多线程进行开发,可是对于锁的使用和理解并非特别的深刻,这不看到一篇挺mark的博客:https://www.jianshu.com/p/a236130bf7a2,在此基础上稍添加点东西转载过来(尊重原创),一是为了记录便于随时翻阅,而是为了写一遍加深印象,知识都是一个copy和attract的过程。 编程
2、种类安全
一、互斥锁多线程
概念:对共享数据进行锁定,保证同一时刻只能有一个线程去操做。 并发
经常使用:异步
@synchronized:同步代码块async
example:执行操做分布式
/** *设置属性值 */ -(void)setMyTestString:(NSString *)myTestString{ @synchronized(self) { // todo something _myTestString = myTestString; } }
example:建立单例函数
//注意:此时为了保证单例模式的更加严谨,须要重写`allocWithZone`方法,保证其余开发者使用`alloc`和`init`方法时,再也不建立新的对象。必要的时候还须要重写`copyWithZone`方法防止`copy`属性对单例模式的影响。 iOS中还有一种更加轻便的方法实现单例模式,即便用GCD中的dispatch_once函数实现。
+(instancetype)shareInstance{ // 1.定义一个静态实例,初值nil static TestSynchronized *myClass = nil; // 2.添加同步锁,建立实例 @synchronized(self) { // 3.判断实例是否建立过,建立过则退出同步锁,直接返回该实例 if (!myClass) { // 4.未建立过,则新建一个实例并返回 myClass = [[self alloc] init]; } } return myClass; }
NSLock:不能迭代加锁,若是发生两次lock,而未unlock过,则会产生死锁问题。工具
example:执行操做
///定义一个静态锁变量, lock--unlock 、tryLuck---unLock 必须成对存在 static NSLock *mylock; -(void)viewDidLoad { [super viewDidLoad]; mylock = [[NSLock alloc] init]; } //当前线程锁失败,也能够继续其它任务,用 trylock 合适 -(void)myLockTest1{ if ([mylock tryLock]) { // to do something [mylock unlock]; } } //当前线程只有锁成功后,才会作一些有意义的工做,那就lock,不必轮询trylock -(void)myLockTest2{ [mylock lock]; // to do something [mylock unlock]; }
二、递归锁
概念:递归锁能够被同一线程屡次请求,而不会引发死锁,即在屡次被同一个线程进行加锁时,不会形成死锁,这主要是用在循环或递归操做中。
经常使用:
NSRecursiveLock: 递归锁
example: 异步执行block
//建立递归锁 NSRecursiveLock *myRecursiveLock = [[NSRecursiveLock alloc] init]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static void (^MyRecursiveLockBlock)(int value); MyRecursiveLockBlk = ^(int value){ [myRecursiveLock lock]; if (value > 0) { // to do something NSLog(@"MyRecursiveLockBlk value = %d", value); MyRecursiveLockBlock(value - 1); } [myRecursiveLock unlock]; }; MyRecursiveLockBlock(6); }); //注意:此时若是将例程中的递归锁换成互斥锁: //NSRecursiveLock *myRecursiveLock = [[NSRecursiveLock alloc] init];换成 //NSLock *myLock = [[NSLock alloc] init];,则会发生死锁问题。
三、读写锁
概念:读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分红读者和写者,读者只对共享资源进行读访问,写者则须要对共享资源进行写操做。
读写锁将访问者分为读出和写入两种,当读写锁在读加锁模式下,全部以读加锁方式访问该资源时,都会得到访问权限,而全部试图以写加锁方式对其加锁的线程都将阻塞,直到全部的读锁释放。
当在写加锁模式下,全部试图对其加锁的线程都将阻塞。
经常使用:
pthread_rwlock_t(读写锁)、 pthread_rwlock_wrlock(写锁)、 pthread_rwlock_rdlock(读锁)
example: 异步读写数据
#import "ViewController.h" #import <pthread.h> @interface ViewController () @property(nonatomic, copy) NSString *rwStr; @end @implementation ViewController ///全局的读写锁 pthread_rwlock_t rwlock; -(void)viewDidLoad { [super viewDidLoad]; // 初始化读写锁 pthread_rwlock_init(&rwlock,NULL); __block int i; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ i = 5; while (i>=0) { NSString *temp = [NSString stringWithFormat:@"writing == %d", i]; [self writingLock:temp]; i--; } }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ i = 5; while (i>=0) { [self readingLock]; i--; } }); } // 写加锁 -(void)writingLock:(NSString *)temp{ pthread_rwlock_wrlock(&rwlock); // writing self.rwStr = temp; NSLog(@"%@", temp); pthread_rwlock_unlock(&rwlock); } // 读加锁 -(NSString *)readingLock{ pthread_rwlock_rdlock(&rwlock); // reading NSString *str = self.rwStr; NSLog(@"reading == %@",self.rwStr); pthread_rwlock_unlock(&rwlock); return str; } @end
四、自旋锁
概念:它是一种忙等的锁,适用于轻量访问。自旋锁是非阻塞的,当一个线程没法获取自旋锁时,会自旋,直到该锁被释放,等待的过程当中线程并不会挂起。(实质上就是,若是自旋锁已经被别的执行单元保持,调用者就一直循环在等待该自旋锁的保持着已经释放了锁)。
自旋锁的使用者通常保持锁的时间很短,此时其效率远高于互斥锁
自旋锁保持期间是抢占失效的
经常使用:
OSSpinLock:自旋锁
example: 执行操做
// 头文件 #import <libkern/OSAtomic.h> // 初始化自旋锁 static OSSpinLock myLock = OS_SPINLOCK_INIT; // 自旋锁的使用 -(void)SpinLockTest{ OSSpinLockLock(&myLock); // to do something OSSpinLockUnlock(&myLock); }
五、分布锁
概念:跨进程的分布式锁,是进程间同步的工具,底层是用文件系统实现的互斥锁,并不强制进程休眠,而是起到告知的做用。
经常使用:
NSDistributedLock:自旋锁
example: 执行操做
//给文件建立分布锁 NSDistributedLock *lock = [[NSDistributedLock alloc] initWithPath:@"/Users/mac/Desktop/lock.lock"]; while (![lock tryLock]) { sleep(1); } //do something [lock unlock]; //但在实际使用过程当中,当执行到do something时程序退出,程序再次启动以后tryLock就不再能成功了,陷入死锁状态.这是使用NSDistributedLock时很是隐蔽的风险.其//实要解决的问题就是如何在进程退出时会自动释放锁.
六、条件变量
概念:与互斥锁不一样,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊状况发生为止。一般条件变量和互斥锁同时使用。
一个线程须要等待某一条件出现才能继续执行,而这个条件是由别的线程产生的,这个时候就用到条件变量。常见的状况是:生产者-消费者问题。
条件变量可让一个线程等待某一条件,当条件知足时,会收到通知。在获取条件变量并等待条件发生的过程当中,也会产生多线程的竞争,因此条件变量一般和互斥锁一块儿工做。
经常使用:
NSCondition:是互斥锁和条件锁的结合,即一个线程在等待signal而阻塞时,能够被另外一个线程唤醒,因为操做系统实现的差别,即便没有发送signal消息,线程也有可能被唤醒,因此须要增长谓词变量来保证程序的正确性。
example: 执行操做
// 建立锁 NSCondition *condition = [[NSCondition alloc] init]; static int count = 0; // 生产者 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ while(count<20) { [condition lock]; // 生产 count ++; NSLog(@"生产 = %d",count); [condition signal]; [condition unlock]; } }); // 消费者 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ while (count>0) { [condition lock]; // 消耗 count --; NSLog(@"消耗剩余 = %d",count); [condition unlock]; } });
NSConditionLock:与NSCondition的实现机制不同,当定义的条件成立的时候会获取锁,反之,释放锁。
example: 执行操做
// 建立锁 NSConditionLock *condLock = [[NSConditionLock alloc] initWithCondition:ConditionHASNOT]; static int count = 0; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 生产者 while(true) { [condLock lock]; // 生产 count ++; [condLock unlockWithCondition:ConditionHAS]; } } dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 消费者 while (true) { [condLock lockWhenCondition:ConditionHAS]; // 消耗 count --; [condLock unlockWithCondition:(count<=0 ? ConditionHASNOT : ConditionHAS)]; } }
七、信号量
概念:信号量是一个计数器,经常使用于处理进程或线程的同步问题,特别是对临界资源的同步访问。临界资源能够简单的理解为在某一时刻只能由一个进程或线程进行操做的资源,这里的资源能够是一段代码、一个变量或某种硬件资源。
经常使用:
dispatch_semaphore_t(信号)、dispatch_semaphore_signal(持有信号)、diapatch_semaphore_wait(释放信号)
example: 执行操做
//建立信号 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [self getTasksWithCompletionHandler:^ { //doing something //持有信号 dispatch_semaphore_signal(semaphore); }]; //释放信号 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
八、栅栏/屏障(barrier)
概念:dispatch_barrier_async函数的做用与barrier的意思相同,在进程管理中起到一个栅栏的做用,它等待全部位于barrier函数以前的操做执行完毕后执行,而且在barrier函数执行以后,barrier函数以后的操做才会获得执行,该函数须要同dispatch_queue_create函数生成的concurrent Dispatch Queue队列一块儿使用。
经常使用:
dispatch_barrier_async:异步栅栏函数
example: 多读单写(读读并发、读写互斥、写写互斥)
- (id)objectForKey:(NSString *)key { __block id obj; //同步读取指定数据 dispatch_sync(concurrent_queue, ^{ obj = [userCenterDic objectForKey:key]; }); return obj; } -(void)setObject:(id )obj foeKey:(NSString *)key{ //异步栅栏调用设置数据 dispatch_async(concurrent_queue, ^{ [userCenterDic setObject:obj forKey:key]; }); }
九、pthread_mutex
概念:C语言定义下的多线程加锁方式,在不少OC对象的底层结构中,能够看到pthread_mutex使用的仍是很受苹果官方推荐的。
用法:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 宏初始化锁变量mutex
经常使用:
pthread_mutex:互斥锁
example:
//建立锁
__block pthread_mutex_t theLock; pthread_mutex_init(&theLock, NULL);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ pthread_mutex_lock(&theLock); NSLog(@"须要线程同步的操做1 开始"); sleep(3); NSLog(@"须要线程同步的操做1 结束"); pthread_mutex_unlock(&theLock); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); pthread_mutex_lock(&theLock); NSLog(@"须要线程同步的操做2"); pthread_mutex_unlock(&theLock); });
pthread_mutex(recursive):递归锁
example:
//注意:这是pthread_mutex为了防止在递归的状况下出现死锁而出现的递归锁。做用和NSRecursiveLock递归锁相似。 //若是使用pthread_mutex_init(&theLock, NULL)初始化锁的话,下面的代码会出现死锁现象,可是改为使用递归锁的形式,则没有问题。 //建立锁 __block pthread_mutex_t theLock; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //设置成递归类型 pthread_mutex_init(&lock, &attr); pthread_mutexattr_destroy(&attr); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static void (^RecursiveMethod)(int); RecursiveMethod = ^(int value) { pthread_mutex_lock(&theLock); if (value > 0) { NSLog(@"value = %d", value); sleep(1); RecursiveMethod(value - 1); } pthread_mutex_unlock(&theLock); }; RecursiveMethod(5); });
3、性能