当咱们写下@property (weak) id obj时,编译器默认会给obj这个属性加atomic关键字,也就是说,默认的setter和getter方法里是加了锁的。ios
在这个系列以前的文章中说过,属性的attribute关键字大部分都是做用在了setter和getter的代码实现中,像weak和strong是直接做用在了成员变量上,而atomic,nonatomic,copy等等这些关键字则是在setter和getter中实现。因此,一旦须要重写setter和getter方法,那么在@property中声明的这几个关键字,固然也就再也不有用了,须要你本身来实现效果,例如@property (nonatomic, copy) NSString *name的setter方法重写,就须要写成:算法
- (void)setName:(NSString *)name { _name = [name copy]; }
那么atomic是应该怎么在setter和getter中实现呢,事实上,仅须要给self加锁,好比@property (atomic, copy) NSString *name重写setter,getter:编程
@synthesize name = _name; - (void)setName:(NSString *)name { @synchronized(self) { _name = [name copy]; } } - (NSString *)name { @synchronized(self) { return _name; } }
不过这么写,也依然不能保证线程安全。若是线程A调用了getter,同时线程B和线程C都调用了setter,那么A线程getter获得的值,多是BC在set以前的原始值,也多是B set的值或者C set的值。同时这个属性的值,也多是B set的值或者C set的值。
因此,保证数据完整性不能简单靠一把锁来完成,毕竟这个是多线程编程最大的难点。swift
虽然一把锁不能保证数据完整性,可是咱们仍是有必要弄清楚OC究竟均可以用哪些锁,他们都是什么特征。api
了解多线程加锁必须知道时间片轮转调度算法,才能深切理解其原理、性能瓶颈。
现代操做系统在管理普通线程时,一般采用时间片轮转算法(Round Robin,简称 RR)。每一个线程会被分配一段时间片(quantum),一般在 10-100 毫秒左右。当线程用完属于本身的时间片之后,就会被操做系统挂起,放入等待队列中,直到下一次被分配时间片,若是线程在时间片结束前阻塞或结束,则CPU立即进行切换。因为线程切换须要时间,若是时间片过短,会致使大量CPU时间浪费在切换上;而若是这个时间片若是太长,会使得其它线程等待过久。数组
狭义上的原子操做表示一条不可打断的操做,也就是说线程在执行操做过程当中,不会被操做系统挂起,而是必定会执行完(理论上拥有CPU时间片无限长)。在单处理器环境下,一条汇编指令显然是原子操做,由于中断也要经过指令来实现,但一句高级语言的代码却不是原子的,由于它最终是由多条汇编语言完成,CPU在进行时间片切换时,大多都会在某条代码的执行过程当中。
但在多核处理器下,则须要硬件支持。安全
都属于CPU时间分片算法下的实现保护共享资源的一种机制。都实现互斥操做,加锁后仅容许一个访问者。
却别在于自旋锁不会使线程进入wait状态,而经过轮训不停查看是否该自旋锁的持有者已经释放的锁;对应的,互斥锁在出现锁已经被占用的状况会进入wait状态,CPU会立即切换时间片。多线程
__block OSSpinLock oslock = OS_SPINLOCK_INIT; //线程2 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ NSLog(@"线程2 befor lock"); OSSpinLockLock(&oslock); NSLog(@"线程2"); OSSpinLockUnlock(&oslock); NSLog(@"线程2 unlock"); }); //线程1 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ NSLog(@"线程1 befor lock"); OSSpinLockLock(&oslock); NSLog(@"线程1 sleep"); sleep(4); NSLog(@"线程1"); OSSpinLockUnlock(&oslock); NSLog(@"线程1 unlock"); });
OSSpinLock效率奇高,主要缘由是:并无进入系统kernel,使用它能够节省系统调用和上下文切换。
YY大神在本身的博客中说,OSSpinLock再也不安全(连接:再也不安全的 OSSpinLock)
可是在GCD多线程实际使用中,并不会发现什么问题。并行线程只要获取oslock,其余线程一概阻塞。
多线程中每每会遇到另外一个概念:优先级翻转并发
低优先级线程拿到锁时,高优先级线程进入忙等(busy-wait)状态,消耗大量 CPU 时间,从而致使低优先级线程拿不到 CPU 时间,也就没法完成任务并释放锁。这种问题被称为优先级反转。框架
YY大神说OSSpinLock不安全实际上就是由于这个缘由,具体能够看他的文章。
YY大神推荐使用信号量dispatch_semaphore做为自旋锁的替代方案。
dispatch_semaphore_t signal = dispatch_semaphore_create(1); dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 5.0f * NSEC_PER_SEC); //线程1 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程1 holding"); dispatch_semaphore_wait(signal, timeout); //signal 值 -1 NSLog(@"线程1 sleep"); sleep(4); NSLog(@"线程1"); dispatch_semaphore_signal(signal); //signal 值 +1 NSLog(@"线程1 post singal"); }); //线程2 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程2 holding"); dispatch_semaphore_wait(signal, timeout); NSLog(@"线程2 sleep"); sleep(4); NSLog(@"线程2"); dispatch_semaphore_signal(signal); NSLog(@"线程2 post signal"); });
dispatch_semaphore_create(1)为建立信号,()中数字表示能够同时几个线程使用信号。为1表示同步使用。上述代码若是此处标2就和没设置信号量同样,并发自行运行。若是设置为0,则一概等待overTime时自动释放,全部代码都不执行,理论上也具备同步做用。
dispatch_semaphore_wait中传入的timeout表示最长加锁时间,自动释放锁后,其它线程能够获取信号并继续运行。
pthread表示的是POSIX thread,定义的是一组跨平台线程相关的API。
pthread_mutex互斥锁是一个非递归锁,若是同一线程重复调用加锁会形成死锁。
用法比较简单
static pthread_mutex_t pmutexLock; pthread_mutex_init(&pLock, NULL); //1.线程2 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ NSLog(@"线程2 befor lock"); pthread_mutex_lock(&pLock); NSLog(@"线程2"); pthread_mutex_unlock(&pLock); }); //2.线程1 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程1 before lock"); pthread_mutex_lock(&pLock); sleep(3); NSLog(@"线程1"); pthread_mutex_unlock(&pLock); });
pthread_mutex(recursive) 递归锁,比较安全,同一线程有且仅有一次加锁,重复加锁不会死锁。不管加锁几回,只需解锁一次。
static pthread_mutex_t pLock; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); //初始化attr赋初值 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //设置锁类型为递归锁 pthread_mutex_init(&pLock, &attr); pthread_mutexattr_destroy(&attr); //1.线程1 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static void (^RecursiveBlock)(int); RecursiveBlock = ^(int value) { pthread_mutex_lock(&pLock); if (value > 0) { NSLog(@"value: %d", value); RecursiveBlock(value - 1); } }; NSLog(@"线程1 before lock"); RecursiveBlock(5); NSLog(@"线程1"); pthread_mutex_unlock(&pLock); NSLog(@"线程1 unlock"); }); //2.线程2 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程2 before lock"); pthread_mutex_lock(&pLock); NSLog(@"线程2"); pthread_mutex_unlock(&pLock); NSLog(@"线程2 unlock"); });
NS开头类都是对CoreFoundation的封装,会更易用。
NSRecursiveLock顾名思义是递归锁。
NSLock 只是在内部封装了一个 pthread_mutex,属性为PTHREAD_MUTEX_ERRORCHECK
NSRecursiveLock 与 NSLock 的区别在于内部封装的 pthread_mutex_t
对象的类型不一样,NSRecursiveLock 的类型为 PTHREAD_MUTEX_RECURSIVE。
和NSLock主要区别是增长了一个NSInteger类型的condition参数,api很简单,也不多。condition就是一个条件标识。在加锁和解锁时对NSConditionLock作条件判断和修改,至关于if语句。
实际的实现原理就是里面封装了一个NSCondition对象。
NSCondition它一般用于标明共享资源是否可被访问或者确保一系列任务能按照指定的执行顺序执行。若是一个线程试图访问一个共享资源,而正在访问该资源的线程将其条件设置为不可访问,那么该线程会被阻塞,直到正在访问该资源的线程将访问条件更改成可访问状态或者说给被阻塞的线程发送信号后,被阻塞的线程才能正常访问这个资源。
NSConditionLock在lock时判断NSCondition对象的条件是否知足,不知足则wait,unlock时对发送NSCondition的broadcast,属于一个常见的生产者–消费者模型。
@synchronized 其实是把修饰对象当作锁来使用。这是经过一个哈希表来实现的,OC 在底层使用了一个互斥锁的数组(你能够理解为锁池),经过对对象去哈希值来获得对应的互斥锁。
以上各类锁的实现原理能够参考深刻理解 iOS 开发中的锁
他们的性能能够参考YY大神的这张图。
各类锁单从效率上来看dispatch_barrier_async和@synchronized差的比较多,不建议使用,其它总体相差不大;相同类型的锁递归锁和普通锁效率相差接近一倍,若是不会在循环或者递归中频繁使用加锁和解锁,不建议使用递归锁;OSSpinlock各路大神都说有问题,从效率上讲,建议用互斥锁pthread_mutex(YYKit方案)或者信号量dispatch_semaphore(CoreFoundation和protobuf方案)做为替代。