iOS 多线程的简单理解(4) 线程锁的简单使用
要用到多线程 ,就不得不考虑,线程之间的交互,线程是否安全
推荐一个原文链接 是关于 线程锁的基本使用的 http://blog.csdn.net/qq_30513483/article/details/52349968
这篇博客,也主要引用 这个链接的内容
4.1 OSSpinLock 自旋锁 :
线程通过busy-wait-loop的方式来获取锁,任时刻只有一个线程能够获得锁,其他线程忙等待直到获得锁。
spinlock在多处理器多线程环境的场景中有很广泛的使用,一般要求使用spinlock的临界区尽量简短,这样获取的锁可以尽快释放,以满足其他忙等的线程。
Spinlock和mutex不同,spinlock不会导致线程的状态切换(用户态->内核态),但是spinlock使用不当(如临界区执行时间过长)会导致cpu busy飙高。
/*
OS_SPINLOCK_INIT: 默认值为
0
,在locked
状态时就会大于0
,unlocked
状态下为0
OSSpinLockLock(&oslock):上锁,参数为OSSpinLock
地址
OSSpinLockUnlock(&oslock):解锁,参数为OSSpinLock
地址
OSSpinLockTry(&oslock):尝试加锁,可以加锁则立即加锁并返回YES
,反之返回NO
这里顺便提一下trylock
和lock
使用场景:
当前线程锁失败,也可以继续其它任务,用 trylock 合适
当前线程只有锁成功后,才会做一些有意义的工作,那就 lock,没必要轮询 trylock
*/
使用:::导入 #import <libkern/OSAtomic.h> iOS 10.0 后被取消 ,需要更换这个锁
- (void)osspinLock{
__block OSSpinLock oslock = OS_SPINLOCK_INIT;
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程1 准备上锁 %@",[NSThread currentThread]);
OSSpinLockLock(&oslock);
sleep(4);
NSLog(@"线程1 代码执行 %@",[NSThread currentThread]);
OSSpinLockUnlock(&oslock);
NSLog(@"线程1 解锁成功 %@",[NSThread currentThread]);
NSLog(@"--------------------------------------------------------");
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程2 准备上锁 %@",[NSThread currentThread]);
OSSpinLockLock(&oslock);
NSLog(@"线程2 代码执行 %@",[NSThread currentThread]);
OSSpinLockUnlock(&oslock);
NSLog(@"线程2 解锁成功 %@",[NSThread currentThread]);
}); }
执行结果:::
2017-12-21 15:24:11.495148+0800 DeadThread[12619:4480008] 线程1 准备上锁 <NSThread: 0x608000071940>{number = 3, name = (null)}
2017-12-21 15:24:11.495148+0800 DeadThread[12619:4480007] 线程2 准备上锁 <NSThread: 0x60c000261180>{number = 4, name = (null)}
2017-12-21 15:24:11.495376+0800 DeadThread[12619:4480007] 线程2 代码执行 <NSThread: 0x60c000261180>{number = 4, name = (null)}
2017-12-21 15:24:11.495470+0800 DeadThread[12619:4480007] 线程2 解锁成功 <NSThread: 0x60c000261180>{number = 4, name = (null)}
2017-12-21 15:24:15.498949+0800 DeadThread[12619:4480008] 线程1 代码执行 <NSThread: 0x608000071940>{number = 3, name = (null)}
2017-12-21 15:24:15.499124+0800 DeadThread[12619:4480008] 线程1 解锁成功 <NSThread: 0x608000071940>{number = 3, name = (null)}
2017-12-21 15:24:15.499216+0800 DeadThread[12619:4480008] --------------------------------------------------------
总结结果:::
1.两个任务都要 异步 + 并发:所以会重新开启两个子线程;
2.两个线程都有锁:当锁定后,会执行完这个任务,才会执行下一个任务;
3.这个结果 只是一中情况,异步并发 会先发起哪个任务是不定的;
对上面的代码稍作修改:::: 给 解锁的代码 添加注释
- (void)osspinLock{ __block OSSpinLock oslock = OS_SPINLOCK_INIT; //线程1 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程1 准备上锁 %@",[NSThread currentThread]); OSSpinLockLock(&oslock); sleep(4); NSLog(@"线程1 代码执行 %@",[NSThread currentThread]); // OSSpinLockUnlock(&oslock); NSLog(@"线程1 解锁成功 %@",[NSThread currentThread]); NSLog(@"--------------------------------------------------------"); }); //线程2 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程2 准备上锁 %@",[NSThread currentThread]); OSSpinLockLock(&oslock); NSLog(@"线程2 代码执行 %@",[NSThread currentThread]); // OSSpinLockUnlock(&oslock); NSLog(@"线程2 解锁成功 %@",[NSThread currentThread]); }); }
执行结果::::
2017-12-21 15:29:34.099561+0800 DeadThread[12653:4494849] 线程1 准备上锁 <NSThread: 0x60c000262100>{number = 3, name = (null)} 2017-12-21 15:29:34.099561+0800 DeadThread[12653:4494848] 线程2 准备上锁 <NSThread: 0x60000047bf00>{number = 4, name = (null)} 2017-12-21 15:29:38.100529+0800 DeadThread[12653:4494849] 线程1 代码执行 <NSThread: 0x60c000262100>{number = 3, name = (null)} 2017-12-21 15:29:38.100739+0800 DeadThread[12653:4494849] 线程1 解锁成功 <NSThread: 0x60c000262100>{number = 3, name = (null)} 2017-12-21 15:29:38.100880+0800 DeadThread[12653:4494849] --------------------------------------------------------
总结结果:::
1.两个任务,每个都含有锁:当有个任务先得到执行的时候,任务执行完没有了解锁的过程
但是另一个任务,会一直等待 锁解除才能执行, 始终不会执行……
4.2 pthread_mutex 互斥锁 ::
使用::#import <pthread.h>
/**
pthread_mutex 中也有个pthread_mutex_trylock(&pLock),
和上面提到的 OSSpinLockTry(&oslock)
区别在于,前者可以加锁时返回的是 0,否则返回一个错误提示码;
后者返回的 YES和NO
*/
-(void)mutexLock{ static pthread_mutex_t pLock; pthread_mutex_init(&pLock, NULL); //1.线程1 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程1 准备上锁 %@",[NSThread currentThread]); pthread_mutex_lock(&pLock); sleep(3); NSLog(@"线程1 执行 %@",[NSThread currentThread]); pthread_mutex_unlock(&pLock); NSLog(@"线程1 成功解锁 %@",[NSThread currentThread]); }); //1.线程2 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程2 准备上锁 %@",[NSThread currentThread]); pthread_mutex_lock(&pLock); sleep(3); NSLog(@"线程2 执行 %@",[NSThread currentThread]); pthread_mutex_unlock(&pLock); NSLog(@"线程2 成功解锁 %@",[NSThread currentThread]); }); }
执行结果:::
2017-12-21 15:37:37.219816+0800 DeadThread[12685:4529083] 线程2 准备上锁 <NSThread: 0x60000006c980>{number = 4, name = (null)} 2017-12-21 15:37:37.219816+0800 DeadThread[12685:4529086] 线程1 准备上锁 <NSThread: 0x604000469880>{number = 3, name = (null)} 2017-12-21 15:37:40.223145+0800 DeadThread[12685:4529083] 线程2 执行 <NSThread: 0x60000006c980>{number = 4, name = (null)} 2017-12-21 15:37:40.223404+0800 DeadThread[12685:4529083] 线程2 成功解锁 <NSThread: 0x60000006c980>{number = 4, name = (null)} 2017-12-21 15:37:43.226685+0800 DeadThread[12685:4529086] 线程1 执行 <NSThread: 0x604000469880>{number = 3, name = (null)} 2017-12-21 15:37:43.226809+0800 DeadThread[12685:4529086] 线程1 成功解锁 <NSThread: 0x604000469880>{number = 3, name = (null)}
结果分析:::
1.异步 + 并发 -> 两个线程;
2.先加锁的任务 先完成,等待任务完成解锁,在完成下一个任务;
4.3 pthread_mutex(recursive) 递归锁
/**
经过上面几种例子,我们可以发现:
加锁后只能有一个线程访问该对象,后面的线程需要排队,
并且 lock 和 unlock 是对应出现的,同一线程多次 lock 是不允许的,
而递归锁允许同一个线程在未释放其拥有的锁时反复对该锁进行加锁操作。
上面的代码如果我们用 pthread_mutex_init(&pLock, NULL) 初始化会出现死锁的情况,递归锁能很好的避免这种情况的死锁;
*/
-(void)recursiveLock{ 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); //销毁一个属性对象,在重新进行初始化之前该结构不能重新使用 static void (^RecursiveBlock)(int); //1.线程1 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ RecursiveBlock = ^(int value) { pthread_mutex_lock(&pLock); if (value > 0) { NSLog(@"value: %d", value); RecursiveBlock(value - 1); } pthread_mutex_unlock(&pLock); }; RecursiveBlock(5); }); }
执行结果:::
2017-12-21 15:45:39.095417+0800 DeadThread[12721:4562499] value: 5 2017-12-21 15:45:39.095583+0800 DeadThread[12721:4562499] value: 4 2017-12-21 15:45:39.096599+0800 DeadThread[12721:4562499] value: 3 2017-12-21 15:45:39.097190+0800 DeadThread[12721:4562499] value: 2 2017-12-21 15:45:39.097645+0800 DeadThread[12721:4562499] value: 1
结果分析:::
1.开启新的线程;
2.递归锁 ->允许同一个线程在未释放其拥有的锁时反复对该锁进行加锁操作;
4.4 dispatch_semaphore 信号量
/**
dispatch_semaphore_create(1): 传入值必须
>=0
, 若传入为0
则阻塞线程并等待timeout,时间到后会执行其后的语句
dispatch_semaphore_wait(signal, overTime):可以理解为lock
,会使得signal
值-1
dispatch_semaphore_signal(signal):可以理解为unlock
,会使得signal
值+1
*/
-(void)GCDSemaphore{ dispatch_semaphore_t signal = dispatch_semaphore_create(1); //传入值必须 >=0, 若传入为0则阻塞线程并等待timeout,时间到后会执行其后的语句 dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC); //线程1 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程1 等待ing %@",[NSThread currentThread]); dispatch_semaphore_wait(signal, overTime); //signal 值 -1 sleep(4); NSLog(@"线程1 执行 %@",[NSThread currentThread]); dispatch_semaphore_signal(signal); //signal 值 +1 NSLog(@"线程1 发送信号 %@",[NSThread currentThread]); NSLog(@"--------------------------------------------------------"); }); //线程2 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程2 等待ing %@",[NSThread currentThread]); dispatch_semaphore_wait(signal, overTime); sleep(4); NSLog(@"线程2 执行 %@",[NSThread currentThread]); dispatch_semaphore_signal(signal); NSLog(@"线程2 发送信号 %@",[NSThread currentThread]); }); }
执行结果:::
2017-12-21 15:49:55.355078+0800 DeadThread[12761:4592703] 线程1 等待ing <NSThread: 0x60c000076400>{number = 3, name = (null)} 2017-12-21 15:49:55.355079+0800 DeadThread[12761:4592705] 线程2 等待ing <NSThread: 0x60c00007d740>{number = 5, name = (null)} 2017-12-21 15:49:59.360317+0800 DeadThread[12761:4592703] 线程1 执行 <NSThread: 0x60c000076400>{number = 3, name = (null)} 2017-12-21 15:49:59.360482+0800 DeadThread[12761:4592703] 线程1 发送信号 <NSThread: 0x60c000076400>{number = 3, name = (null)} 2017-12-21 15:49:59.360568+0800 DeadThread[12761:4592703] -------------------------------------------------------- 2017-12-21 15:50:02.358932+0800 DeadThread[12761:4592705] 线程2 执行 <NSThread: 0x60c00007d740>{number = 5, name = (null)} 2017-12-21 15:50:02.359109+0800 DeadThread[12761:4592705] 线程2 发送信号 <NSThread: 0x60c00007d740>{number = 5, name = (null)}
结果分析:::
1.初始信号量大于0
可以发现,因为我们初始化信号量的时候是大于 0
的,所以并没有阻塞线程,而是直接执行了 线程1 线程2。
把信号量设置为 0 修改上面代码
dispatch_semaphore_t signal = dispatch_semaphore_create(0);
执行结果::::
2017-12-21 15:53:43.545606+0800 DeadThread[12804:4615777] 线程2 等待ing <NSThread: 0x604000076880>{number = 4, name = (null)} 2017-12-21 15:53:43.545651+0800 DeadThread[12804:4615776] 线程1 等待ing <NSThread: 0x60000026cbc0>{number = 5, name = (null)} 2017-12-21 15:53:50.553973+0800 DeadThread[12804:4615777] 线程2 执行 <NSThread: 0x604000076880>{number = 4, name = (null)} 2017-12-21 15:53:50.553973+0800 DeadThread[12804:4615776] 线程1 执行 <NSThread: 0x60000026cbc0>{number = 5, name = (null)} 2017-12-21 15:53:50.554140+0800 DeadThread[12804:4615777] 线程2 发送信号 <NSThread: 0x604000076880>{number = 4, name = (null)} 2017-12-21 15:53:50.554144+0800 DeadThread[12804:4615776] 线程1 发送信号 <NSThread: 0x60000026cbc0>{number = 5, name = (null)} 2017-12-21 15:53:50.554252+0800 DeadThread[12804:4615776] --------------------------------------------------------
结果分析:::