说到锁不得不提线程安全,说到线程安全,作为iOS程序员又不得不提
与
- nonatomic
- atomic
不会对生成的
- nonatomic
、
- getter
方法加同步锁(非原子性)
- setter
会对生成的
- atomic
、
- getter
加同步锁(原子性)
- setter
/
- setter
被
- getter
修饰的属性时,该属性是读写安全的。然而读写安全并不代表线程安全。
- atomic
非线程安全
- atomic
- #import "ViewController.h"
- @interface ViewController()
- @property(strong, atomic) NSString * name;
- @end
- @implementation ViewController
- - (void) viewDidLoad { [super viewDidLoad];
- //atomic非线程安全验证
- //Jack
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- while (1) {
- self.name = @"Jack";
- NSLog(@"Jack is %@", self.name);
- }
- });
- //Rose
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- while (1) {
- self.name = @"Rose";
- NSLog(@"Rose is %@", self.name);
- }
- });
- }
- 2017-11-29 11:21:27.713446+0800 LockDemo[42637:1199500] Jack is Jack
- 2017-11-29 11:21:27.713487+0800 LockDemo[42637:1199499] Rose is Rose
- 2017-11-29 11:21:27.713638+0800 LockDemo[42637:1199500] Jack is Jack
- 2017-11-29 11:21:27.713659+0800 LockDemo[42637:1199499] Rose is Rose
- 2017-11-29 11:21:27.713840+0800 LockDemo[42637:1199500] Jack is Jack
- 2017-11-29 11:21:27.714050+0800 LockDemo[42637:1199499] Rose is Rose
- 2017-11-29 11:21:27.714205+0800 LockDemo[42637:1199500] Jack is Jack
- 2017-11-29 11:21:27.718069+0800 LockDemo[42637:1199499] Rose is Rose
- 2017-11-29 11:21:27.718069+0800 LockDemo[42637:1199500] Jack is Rose
- 2017-11-29 11:21:27.718199+0800 LockDemo[42637:1199500] Jack is Jack
- 2017-11-29 11:21:27.718199+0800 LockDemo[42637:1199499] Rose is Jack
最后一行和倒数第三行可以看到,
非线程安全验证完毕。
- atomic
只能做到读写安全并不能做到线程安全,若要实现线程安全还需要采用更为深层的锁定机制才行。
- atomic
属性,因为在iOS中使用同步锁的开销较大,这会带来性能问题,但是在Mac OS X程序时,使用
- nonatomic
属性通常都不会有性能瓶颈。
- atomic
在计算机科学中,锁是一种同步机制,用于在存在多线程的环境中实施对资源的访问限制。
- @synchronized
对象锁
- NSLock
递归锁
- NSRecursiveLock
条件锁
- NSConditionLock
互斥锁(C语言)
- pthread_mutex
信号量实现加锁(
- dispatch_semaphore
)
- GCD
自旋锁
- OSSpinLock
其实是一个 OC 层面的锁, 主要是通过牺牲性能换来语法上的简洁与可读性。
- @synchronized
是我们平常使用最多的但是性能最差的。
- @synchronized
- @synchronized(self) {
- //需要执行的代码块
- }
swift写法:
- objc_sync_enter(self)
- //需要执行的代码块
- objc_sync_exit(self)
代码示例:
- //线程1
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- @synchronized(self) {
- NSLog(@"第一个线程同步操作开始");
- sleep(3);
- NSLog(@"第一个线程同步操作结束");
- }
- });
- //线程2
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- sleep(1);
- @synchronized(self) {
- NSLog(@"第二个线程同步操作");
- }
- });
结果:
- 2017-11-29 14:36:52.056457+0800 LockDemo[46145:1306472] 第一个线程同步操作开始
- 2017-11-29 14:36:55.056868+0800 LockDemo[46145:1306472] 第一个线程同步操作结束
- 2017-11-29 14:36:55.057261+0800 LockDemo[46145:1306473] 第二个线程同步操作
指令使用的
- @synchronized(self)
为该锁的唯一标识,只有当标识相同时,才为满足互斥,如果线程2中的
- self
改成其它对象,线程2就不会被阻塞。
- self
- NSString *s = [NSString string];
- //线程1
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- @synchronized(self) {
- NSLog(@"第一个线程同步操作开始");
- sleep(3);
- NSLog(@"第一个线程同步操作结束");
- }
- });
- //线程2
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- sleep(1);
- @synchronized(s) {
- NSLog(@"第二个线程同步操作");
- }
- });
- 2017-11-29 14:43:54.930414+0800 LockDemo[46287:1312173] 第一个线程同步操作开始
- 2017-11-29 14:43:55.930761+0800 LockDemo[46287:1312158] 第二个线程同步操作
- 2017-11-29 14:43:57.932287+0800 LockDemo[46287:1312173] 第一个线程同步操作结束
指令实现锁的优点就是我们不需要在代码中显式的创建锁对象,便可以实现锁的机制,但作为一种预防措施,
- @synchronized
块会隐式的添加一个异常处理来保护代码,该处理会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。
- @synchronized
中实现了一个简单的互斥锁。通过
- NSLock
协议定义了
- NSLocking
和
- lock
方法。
- unlock
- @protocol NSLocking
- - (void)lock;
- - (void)unlock;
- @end
举个栗子卖冰棍儿
- - (void) nslockTest {
- //设置冰棍儿的数量为5
- _count = 5;
- //创建锁
- _lock = [[NSLock alloc] init];
- //线程1
- dispatch_async(dispatch_get_global_queue(0, 0), ^{ [self saleIceCream];
- });
- //线程2
- dispatch_async(dispatch_get_global_queue(0, 0), ^{ [self saleIceCream];
- });
- }
- - (void) saleIceCream {
- while (1) {
- sleep(1);
- //加锁
- [_lock lock];
- if (_count > 0) {
- _count--;
- NSLog(@"剩余冰棍儿数= %ld, Thread - %@", _count, [NSThread currentThread]);
- } else {
- NSLog(@"冰棍儿卖光光 Thread - %@", [NSThread currentThread]);
- break;
- }
- //解锁
- [_lock unlock];
- }
- }
加锁结果:
- 2017-11-29 16:21:29.728198+0800 LockDemo[55262:1411318] 剩余冰棍儿数= 4, Thread - <NSThread: 0x604000475dc0>{number = 3, name = (null)}
- 2017-11-29 16:21:29.728428+0800 LockDemo[55262:1411319] 剩余冰棍儿数= 3, Thread - <NSThread: 0x604000475e00>{number = 4, name = (null)}
- 2017-11-29 16:21:30.729009+0800 LockDemo[55262:1411318] 剩余冰棍儿数= 2, Thread - <NSThread: 0x604000475dc0>{number = 3, name = (null)}
- 2017-11-29 16:21:30.729378+0800 LockDemo[55262:1411319] 剩余冰棍儿数= 1, Thread - <NSThread: 0x604000475e00>{number = 4, name = (null)}
- 2017-11-29 16:21:31.733061+0800 LockDemo[55262:1411318] 剩余冰棍儿数= 0, Thread - <NSThread: 0x604000475dc0>{number = 3, name = (null)}
- 2017-11-29 16:21:31.733454+0800 LockDemo[55262:1411319] 冰棍儿卖光光 Thread - <NSThread: 0x604000475e00>{number = 4, name = (null)}
不加锁结果:
- 2017-11-29 16:23:38.702352+0800 LockDemo[55316:1412917] 剩余冰棍儿数= 3, Thread - <NSThread: 0x604000270b80>{number = 3, name = (null)}
- 2017-11-29 16:23:38.702352+0800 LockDemo[55316:1412919] 剩余冰棍儿数= 4, Thread - <NSThread: 0x604000271040>{number = 4, name = (null)}
- 2017-11-29 16:23:39.705096+0800 LockDemo[55316:1412919] 剩余冰棍儿数= 2, Thread - <NSThread: 0x604000271040>{number = 4, name = (null)}
- 2017-11-29 16:23:39.705099+0800 LockDemo[55316:1412917] 剩余冰棍儿数= 1, Thread - <NSThread: 0x604000270b80>{number = 3, name = (null)}
- 2017-11-29 16:23:40.709617+0800 LockDemo[55316:1412919] 剩余冰棍儿数= 0, Thread - <NSThread: 0x604000271040>{number = 4, name = (null)}
- 2017-11-29 16:23:40.709617+0800 LockDemo[55316:1412917] 冰棍儿卖光光 Thread - <NSThread: 0x604000270b80>{number = 3, name = (null)}
- 2017-11-29 16:23:41.714002+0800 LockDemo[55316:1412919] 冰棍儿卖光光 Thread - <NSThread: 0x604000271040>{number = 4, name = (null)}
类还增加了
- NSLock
和
- tryLock
方法
- lockBeforeDate:
- - (BOOL)tryLock;
- - (BOOL)lockBeforeDate:(NSDate *)limit;
试图获取一个锁,但是如果锁不可用的时候,它不会阻塞线程,相反,它只是返回NO。
- tryLock
方法试图获取一个锁,但是如果锁没有在规定的时间内被获得,它会让线程从阻塞状态变为非阻塞状态(或者返回NO)。
- lockBeforeDate:
有时候“加锁代码”中存在递归调用,递归开始前加锁,递归调用开始后会重复执行此方法以至于反复执行加锁代码最终造成死锁。
- - (void)recursiveLockTest {
- //创建锁
- _lock = [[NSLock alloc] init];
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- static void(^TestMethod)(int);
- TestMethod = ^(int value)
- {
- [_lock lock];
- if (value > 0)
- {
- [NSThread sleepForTimeInterval:1];
- value--;
- TestMethod(value);
- }
- [_lock unlock];
- };
- TestMethod(5);
- NSLog(@"结束");
- });
- }
我们发现 "结束" 永远不会被打印出来,这个时候可以使用递归锁来解决。使用递归锁可以在一个线程中反复获取锁而不造成死锁,这个过程中会记录获取锁和释放锁的次数,只有最后两者平衡锁才被最终释放。
- - (void)recursiveLockTest {
- //创建锁
- _recursiveLock = [[NSRecursiveLock alloc] init];
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- static void(^TestMethod)(int);
- TestMethod = ^(int value)
- {
- [_recursiveLock lock];
- if (value > 0)
- {
- [NSThread sleepForTimeInterval:1];
- value--;
- TestMethod(value);
- }
- [_recursiveLock unlock];
- };
- TestMethod(5);
- NSLog(@"结束");
- });
- }
此时 "结束" 5秒后会被打印出来。
做多线程之间的任务等待调用,而且是线程安全的。
- NSCoditionLock
- - (void)conditionLockTest {
- NSInteger HAS_DATA = 1;
- NSInteger NO_DATA = 0;
- _conditionLock = [[NSConditionLock alloc] initWithCondition:NO_DATA];
- NSMutableArray *products = [NSMutableArray array];
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- while (1) {
- [_conditionLock lockWhenCondition:NO_DATA];
- [products addObject:[[NSObject alloc] init]];
- NSLog(@"生产");
- [_conditionLock unlockWithCondition:HAS_DATA];
- sleep(5);
- }
- });
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- while (1) {
- NSLog(@"等待");
- [_conditionLock lockWhenCondition:HAS_DATA];
- [products removeObjectAtIndex:0];
- NSLog(@"售卖");
- [_conditionLock unlockWithCondition:NO_DATA];
- }
- });
- }
也跟其它的锁一样,是需要
- NSConditionLock
与
- lock
对应的,只是
- unlock
,
- lock
与
- lockWhenCondition:
,
- unlock
是可以随意组合的,当然这是与需求相关的。
- unlockWithCondition:
很像,但是完全不同。pthread_mutex 是Unix/Linux平台上提供的一套条件互斥锁的API。
- dispatch_semaphore_t
声明并初始化一个
- #import <pthread.h>
的结构。使用
- pthread_mutex_t
和
- pthread_mutex_lock
函数。调用
- pthread_mutex_unlock
来释放该锁的数据结构。
- pthread_mutex_destroy
使用:
- #import <pthread.h>
- - (void) pthreadTest {
- __block pthread_mutex_t theLock;
- pthread_mutex_init( & theLock, NULL);
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- pthread_mutex_lock( & theLock);
- NSLog(@"第一个线程同步操作开始");
- sleep(3);
- NSLog(@"第一个线程同步操作结束");
- pthread_mutex_unlock( & theLock);
- });
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- sleep(1);
- pthread_mutex_lock( & theLock);
- NSLog(@"第二个线程同步操作");
- pthread_mutex_unlock( & theLock);
- });
- }
执行结果:
- 2017-11-29 17:51:11.901064+0800 LockDemo[56729:1466788] 第一个线程同步操作开始
- 2017-11-29 17:51:14.904834+0800 LockDemo[56729:1466788] 第一个线程同步操作结束
- 2017-11-29 17:51:14.905195+0800 LockDemo[56729:1466789] 第二个线程同步操作
一样的条件控制,初始化互斥锁同时使用
- NSCondition
来初始化条件数据结构
- pthread_cond_init
- // 初始化
- int pthread_cond_init(pthread_cond_t * cond, pthread_condattr_t * attr);
- // 等待(会阻塞)
- int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mut);
- // 定时等待
- int pthread_cond_timedwait(pthread_cond_t * cond, pthread_mutex_t * mut, const struct timespec * abstime);
- // 唤醒
- int pthread_cond_signal(pthread_cond_t * cond);
- // 广播唤醒
- int pthread_cond_broadcast(pthread_cond_t * cond);
- // 销毁
- int pthread_cond_destroy(pthread_cond_t * cond);
pthread_mutex 还提供了很多函数,有一套完整的API,包含
线程的创建控制等等,非常底层,可以手动处理线程的各个状态的转换即管理生命周期,甚至可以实现一套自己的多线程,感兴趣的可以继续深入了解。
- Pthreads
GCD中信号量,也可以解决资源抢占问题,支持信号通知和信号等待。每当发送一个信号通知,则信号量 +1;每当发送一个等待信号时信号量 -1,;如果信号量为 0 则信号会处于等待状态,直到信号量大于 0 开始执行。
- dispatch_semaphore_t
api注释:
- /*!
- * @param value
- *信号量的起始值,当传入的值小于零时返回NULL
- * @result
- * 成功返回一个新的信号量,失败返回NULL
- */
- dispatch_semaphore_t dispatch_semaphore_create(long value)
- /*!
- * @discussion
- * 信号量减1,如果结果小于0,那么等待队列中信号增量到来直到timeout
- * @param dsema
- * 信号量
- * @param timeout
- * 等待时间
- * 类型为dispatch_time_t,这里有两个宏DISPATCH_TIME_NOW、DISPATCH_TIME_FOREVER
- * @result
- * 若等待成功返回0,timeout返回非0
- */
- long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
- /*!
- * @discussion
- * 信号量加1,如果之前的信号量小于0,将唤醒一条等待线程
- * @param dsema
- * 信号量
- * @result
- * 唤醒一条线程返回非0,否则返回0
- */
- long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
使用:
- - (void) semaphoreTest {
- // 创建信号量
- dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
- //线程1
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
- NSLog(@"任务1");
- sleep(10);
- dispatch_semaphore_signal(semaphore);
- });
- //线程2
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- sleep(1);
- dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
- NSLog(@"任务2");
- dispatch_semaphore_signal(semaphore);
- });
- }
执行结果:
- 2017-11-30 14:38:11.943521+0800 LockDemo[91493:2075379] 任务1
- 2017-11-30 14:38:21.946222+0800 LockDemo[91493:2075380] 任务2
使用:
- #import <libkern/OSAtomic.h>
- __block OSSpinLock theLock = OS_SPINLOCK_INIT;
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- OSSpinLockLock( & theLock);
- NSLog(@"第一个线程同步操作开始");
- sleep(3);
- NSLog(@"第一个线程同步操作结束");
- OSSpinLockUnlock( & theLock);
- });
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- OSSpinLockLock( & theLock);
- sleep(1);
- NSLog(@"第二个线程同步操作");
- OSSpinLockUnlock( & theLock);
- });
执行结果:
- 2017-11-30 15:12:31.701180+0800 LockDemo[92422:2104479] 第一个线程同步操作开始
- 2017-11-30 15:12:39.705473+0800 LockDemo[92422:2104479] 第一个线程同步操作结束
- 2017-11-30 15:12:39.705820+0800 LockDemo[92422:2104478] 第二个线程同步操作开始
OSSpinLock 自旋锁,性能最高的锁。它的缺点是当等待时会消耗大量 CPU 资源,不太适用于较长时间的任务。 YY大神在博客 不再安全的 OSSpinLock 中说明了OSSpinLock已经不再安全,暂不建议使用。
iOS 10 之后,苹果给出了解决方案,就是用 os_unfair_lock 代替 OSSpinLock。
- 'OSSpinLockLock'is deprecated: first deprecated in iOS 10.0 - Use os_unfair_lock_lock() from < os / lock.h > instead
- #import <os/lock.h>
- __block os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- os_unfair_lock_lock( & lock);
- NSLog(@"第一个线程同步操作开始");
- sleep(8);
- NSLog(@"第一个线程同步操作结束");
- os_unfair_lock_unlock( & lock);
- });
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- sleep(1);
- os_unfair_lock_lock( & lock);
- NSLog(@"第二个线程同步操作开始");
- os_unfair_lock_unlock( & lock);
- });
执行结果:
- 2017-11-30 15:12:31.701180+0800 LockDemo[92422:2104479] 第一个线程同步操作开始
- 2017-11-30 15:12:39.705473+0800 LockDemo[92422:2104479] 第一个线程同步操作结束
- 2017-11-30 15:12:39.705820+0800 LockDemo[92422:2104478] 第二个线程同步操作开始
来源: https://juejin.im/entry/5a2a4e805188255ea95bddb9