1:iOS中的常见多线程方案
2:GCD的常用函数
3:GCD的队列
4:死锁问题
5:问题
5.1:问题1:
5.2:问题2:
5.3:问题3:
6:多线程的安全隐患
7:(安全问题)解决方案
7.1:iOS中的线程同步方案
7.2:iOS线程同步方案性能比较
7.3:atomic
7.4:7.4:iOS中的读写安全方案
8:线程间通信
GCD里不存在线程保活功能,保活是runloop的事情。
1:iOS中的常见多线程方案
这些东西的底层都是pthread。
这里做一下NSOperation和CGD的区别
- GCD是纯C语言的API,NSOperation是基于GCD的OC版本封装
- GCD只支持FIFO的队列,NSOperation可以很方便地调整执行顺序,设置最大并发数量
- NSOperationQueue可以轻松在operation间设置依赖关系,而GCD需要些很多代码才能实现
- NSOperationQueue支持KVO,可以检测operation是否正在执行(isExecuted),是否结束(isFinisn),是否取消(isCancel)(这里也可以是区别于gcd的最大亮点)
- GCD的执行速度比NSOperation快
2:GCD的常用函数
GCD中有2个用来执行任务的函数
用同步的方式执行任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:队列
block:任务
用异步的方式执行任务:想在子线程做某些事情
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
3:GCD的队列
那我们先来知道一个非常重要的事情:
------- 队列只是负责任务的调度,而不负责任务的执行 ---------
------- 任务是在线程中执行的 ---------
队列和任务的特点:
队列的特点:先进先出,排在前面的任务最先执行,
1:串行队列:任务按照顺序被调度,前一个任务不执行完毕,队列不会调度。自己创建的:dispatch_queue_t q = dispatch_queue_create(“....”, dispatch_queue_serial);。
2:并行队列:只要有空闲的线程,队列就会调度当前任务,交给线程去执行,不需要考虑前面是都有任务在执行,如果当前调度的任务是异步
执行的,同时底层线程池有可用的线程资源,会再新的线程调度后续任务的执行,自己创建的:dispatch_queue_t q = dispatch_queue_create("......", dispatch_queue_concurrent);
3:主队列:专门用来在主线程调度任务的队列,所以主队列的任务都要在主线程来执行,主队列会随着程序的启动一起创建,我们只需get即可。不会开启线程,以先进先出
的方式,在主线程空闲时
才会调度队列中的任务在主线程执行。如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度,系统的。:dispatch_queue_t q = dispatch_get_main_queue();
4:全局队列:是系统为了方便程序员开发提供的,其工作表现与并发队列一致,那么全局队列跟并发队列的区别是什么呢?(dispatch_queue_t q = dispatch_get_global_queue(dispatch_queue_priority_default, 0);)
a.全局队列:无论ARC还是MRC都不需要考录释放,因为系统提供的我们只需要get就可以了
b.并发队列:再MRC下,并发队列创建出来后,需要手动释放dispatch_release()
1---- 队列和线程的区别:
队列:是管理线程的,相当于线程池,能管理线程什么时候执行。
队列分为串行队列和并行队列:
串行队列:队列中的线程按顺序执行(不会同时执行)
并行队列:队列中的线程会并发执行,可能会有一个疑问,队列不是先进先出吗,如果后面的任务执行完了,怎么出去的了。这里需要强调下,任务执行完毕了,不一定出队列。只有前面的任务执行完了,才会出队列。
2----- 主线程队列和gcd创建的队列也是有区别的。
主线程队列和gcd创建的队列是不同的。在gcd中创建的队列优先级没有主队列高,所以在gcd中的串行队列开启同步任务里面没有嵌套任务是不会阻塞主线程,只有一种可能导致死锁,就是串行队列里,嵌套开启任务,有可能会导致死锁。
主线程队列中不能开启同步,会阻塞主线程。只能开启异步任务,开启异步任务也不会开启新的线程,只是降低异步任务的优先级,让cpu空闲的时候才去调用。而同步任务,会抢占主线程的资源,会造成死锁。
3----- 线程:里面有非常多的任务(同步,异步)
同步与异步的区别:
同步任务优先级高,在线程中有执行顺序,不会开启新的线程。
异步任务优先级低,在线程中执行没有顺序,看cpu闲不闲。在主队列中不会开启新的线程,其他队列会开启新的线程。
4----主线程队列注意:
在主队列开启异步任务,不会开启新的线程而是依然在主线程中执行代码块中的代码。为什么不会阻塞线程?
> 主队列开启异步任务,虽然不会开启新的线程,但是他会把异步任务降低优先级,等闲着的时候,就会在主线程上执行异步任务。
在主队列开启同步任务,为什么会阻塞线程?
> 在主队列开启同步任务,因为主队列是串行队列,里面的线程是有顺序的,先执行完一个线程才执行下一个线程,而主队列始终就只有一个主线程,主线程是不会执行完毕的,因为他是无限循环的,除非关闭应用开发程序。因此在主线程开启一个同步任务,同步任务会想抢占执行的资源,而主线程任务一直在执行某些操作,不肯放手。两个的优先级都很高,最终导致死锁,阻塞线程了。
容易混淆的术语
有4个术语比较容易混淆:同步、异步、并发、串行
同步和异步主要影响:能不能开启新的线程
同步:在当前线程中执行任务,不具备开启新线程的能力
异步:在新的线程中执行任务,具备开启新线程的能力 (传入的是主队列的情况下,就不会开启新线程)
并发和串行主要影响:任务的执行方式
并行:多个任务同时执行(并行是CPU的多核芯同时执行多个任务 并发是单核CPU交替执行两个任务)
并发:多个任务并发(同时)执行
串行:一个任务执行完毕后,再执行下一个任务
各种队列的执行效果 :(主队列就是一个串行队列)
队列的类型,决定了任务的执行方式(并发、串行)
1.并发队列 2.串行队列 3.主队列(也是一个串行队列)
4:死锁问题
队列的特点是 排队,FIFO,先进先出的概念
使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)
当然在主线程下自己创建的串行队列加入到同步执行,并不会发生死锁,因为自己创建的串行队列跟主队列不是一个队列。
/**
队列的类型,决定了任务的执行方式(并发、串行)
1.并发队列
2.串行队列
3.主队列(也是一个串行队列)
*/
- (void)interview01
{
// 队列的特点是 排队,FIFO,先进先出的概念
// 问题:以下代码是在主线程执行的,会不会产生死锁?会!
NSLog(@"执行任务1");
// 这个任务添加到主队列,而又是同步,当前线程执行,需要等上一个任务执行完,才执行下面这个,但是上个任务会一直等待下面这个任务执行完,也就是任务2在等任务3执行,任务3又在等任务2来执行完,所以就会发生死锁。
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
// dispatch_sync立马在当前线程同步执行任务. 执行完毕才能继续下去执行
}
- (void)interview02
{
// 问题:以下代码是在主线程执行的,会不会产生死锁?不会!
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
// dispatch_async不要求立马在当前线程同步执行任务
}
- (void)interview03
{
// 问题:以下代码是在主线程执行的,会不会产生死锁?会!
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 0
NSLog(@"执行任务2");
dispatch_sync(queue, ^{ // 1 这个1一直等0这个队列先执行完,但是0内部执行必须要等1执行完,所以相互等待,死锁
NSLog(@"执行任务3"); // 死锁在这里
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
}
- (void)interview04
{
// 问题:以下代码是在主线程执行的,会不会产生死锁?不会!
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
// dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_CONCURRENT); // 自己创建的并发队列
dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 0
NSLog(@"执行任务2");
dispatch_sync(queue2, ^{ // 1 这个queue2的话就不会发生死锁,因为队列不同(不论是并发,还是又创建了一个新的串行队列),这个是queue,就会发生死锁
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
}
- (void)interview05
{
// 问题:以下代码是在主线程执行的,会不会产生死锁?不会!
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{ // 0
NSLog(@"执行任务2");
dispatch_sync(queue, ^{ // 1
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
}
并发队列创建:
系统创建全局并发:dispatch_get_global_queue
自己创建的并发队列:dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT);
自己创建的串行队列:dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL);
全局并发队列和自己创建的队列的区别
- (void)viewDidLoad {
[super viewDidLoad];
// [self interview05];
dispatch_queue_t queue1 = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue2 = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue3 = dispatch_queue_create("queu3", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue4 = dispatch_queue_create("queu3", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue5 = dispatch_queue_create("queu5", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"%p %p %p %p %p", queue1, queue2, queue3, queue4, queue5);
}
2018-09-28 14:58:22.528129+0800 Interview04-gcd[4006:305503] 0x1074e7500 0x1074e7500 0x600000140e70 0x600000140f20 0x600000140fd0
全局并发队列:不论创建多少次,获取的都是同一个,但是自己创建的就不是了,最好名字是不同的,因为需要拿名字来获取。
5:问题
5.1:问题1
- (void)test
{
NSLog(@"2");
}
- (void)test2
{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
// 现在是在子线程:这句代码的本质是往Runloop中添加定时器
[self performSelector:@selector(test) withObject:nil afterDelay:.0]; // 如果这个在主线程,就不用唤醒了。
NSLog(@"3");
// [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
}
2018-09-28 15:21:19.594419+0800 Interview01-打印[4311:342241] 1
2018-09-28 15:21:19.594686+0800 Interview01-打印[4311:342241] 3
因为现在在子线程,子线程默认没有启动Runloop,而performSelector:withObject:afterDelay:的本质是往Runloop中添加定时器
所以不打印。如果想打印,直接把runloop那行打开即可。
- (void)test2
{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
// 现在是在子线程:这句代码的本质是往Runloop中添加定时器
[self performSelector:@selector(test) withObject:nil afterDelay:.0]; // 如果这个在主线程,就不用唤醒了。
NSLog(@"3");
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
}
2021-03-11 09:57:02.843856+0800 test[1550:1282766] 1
2021-03-11 09:57:02.843998+0800 test[1550:1282766] 3
2021-03-11 09:57:02.844044+0800 test[1550:1282766] 2
因为runloop源码不开源,所以可以用GNUstep看一下
GNUstep是GNU计划的项目之一,它将Cocoa的OC库重新开源实现了一遍
源码地址:GNUstep: Download
虽然GNUstep不是苹果官方源码,但还是具有一定的参考价值
看这个GNU的源码
- (void) performSelector: (SEL)aSelector
withObject: (id)argument
afterDelay: (NSTimeInterval)seconds
{
NSRunLoop *loop = [NSRunLoop currentRunLoop];
GSTimedPerformer *item;
item = [[GSTimedPerformer alloc] initWithSelector: aSelector
target: self
argument: argument
delay: seconds];
[[loop _timedPerformers] addObject: item];
RELEASE(item);
[loop addTimer: item->timer forMode: NSDefaultRunLoopMode];
}
这里没有调用run,所以需要自己开启runloop。
加入其它验证
- (void)test
{
NSLog(@"2");
}
- (void)test2
{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
// 现在是在子线程:这句代码的本质是往Runloop中添加定时器
[self performSelector:@selector(test) withObject:nil afterDelay:.0]; // 如果这个在主线程,就不用唤醒了。
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
NSLog(@"3");
});
}
// 结果
2021-04-10 13:53:08.155486+0800 TestOC[67573:2722889] 1
2021-04-10 13:53:08.156098+0800 TestOC[67573:2722889] 2
2021-04-10 13:53:08.156519+0800 TestOC[67573:2722889] 3
可以看到根加入的runloop位置有关
- (void)test
{
NSLog(@"2");
}
- (void)test2
{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
// 现在是在子线程:这句代码的本质是往Runloop中添加定时器
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
[self performSelector:@selector(test) withObject:nil afterDelay:.0]; // 如果这个在主线程,就不用唤醒了。
NSLog(@"3");
});
}
// 结果
2021-04-10 13:54:16.497133+0800 TestOC[67609:2725904] 1
2021-04-10 13:54:16.497547+0800 TestOC[67609:2725904] 3
在上面便不执行,runloop的启动是因为里面加入了time/port/abserver等事件。而放在上面的的runloop里并未有任何事件,所以并未开启。开启才有循环。
performSelector:withObject:此方法是发送方法,是nsobject的方法,运行时的方法,不局限于哪个线程
- (void)test
{
NSLog(@"2");
}
- (void)test2
{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
// 现在是在子线程:这句代码的本质是往Runloop中添加定时器
// [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
// [self performSelector:@selector(test) withObject:nil afterDelay:.0]; // 如果这个在主线程,就不用唤醒了。
[self performSelector:@selector(test) withObject:@""];
NSLog(@"3");
});
}
// 结果
2021-04-10 13:57:26.470345+0800 TestOC[67681:2731965] 1
2021-04-10 13:57:26.470802+0800 TestOC[67681:2731965] 2
2021-04-10 13:57:26.471193+0800 TestOC[67681:2731965] 3
5.2:问题2:
- (void)test
{
NSLog(@"2");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"1");
// [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
// [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
2018-09-28 15:39:15.179608+0800 Interview01-打印[4880:377767] 1
2018-09-28 15:39:15.304055+0800 Interview01-打印[4880:376373] *** Terminating app due to uncaught exception 'NSDestinationInvalidException', reason: '*** -[ViewController performSelector:onThread:withObject:waitUntilDone:modes:]: target thread exited while waiting for the perform'
*** First throw call stack:
(
0 CoreFoundation 0x0000000108c081e6 __exceptionPreprocess + 294
1 libobjc.A.dylib 0x000000010829d031 objc_exception_throw + 48
2 CoreFoundation 0x0000000108c7d975 +[NSException raise:format:] + 197
3 Foundation 0x0000000107ca572f -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:modes:] + 1086
4 Foundation 0x0000000107d154a4 -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:] + 120
5 Interview01-打印 0x000000010799c613 -[ViewController touchesBegan:withEvent:] + 179
6 UIKit 0x00000001092b2767 forwardTouchMethod + 340
7 UIKit 0x00000001092b2602 -[UIResponder touchesBegan:withEvent:] + 49
8 UIKit 0x00000001090fae1a -[UIWindow _sendTouchesForEvent:] + 2052
9 UIKit 0x00000001090fc7c1 -[UIWindow sendEvent:] + 4086
10 UIKit 0x00000001090a0310 -[UIApplication sendEvent:] + 352
11 UIKit 0x00000001099e16af __dispatchPreprocessedEventFromEventQueue + 2796
12 UIKit 0x00000001099e42c4 __handleEventQueueInternal + 5949
13 CoreFoundation 0x0000000108baabb1 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
14 CoreFoundation 0x0000000108b8f4af __CFRunLoopDoSources0 + 271
15 CoreFoundation 0x0000000108b8ea6f __CFRunLoopRun + 1263
16 CoreFoundation 0x0000000108b8e30b CFRunLoopRunSpecific + 635
17 GraphicsServices 0x000000010de1ba73 GSEventRunModal + 62
18 UIKit 0x0000000109085057 UIApplicationMain + 159
19 Interview01-打印 0x000000010799c6ef main + 111
20 libdyld.dylib 0x000000010c665955 start + 1
21 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
因为调用的是start,就会去子线程执行block里面的代码,同时主线程会继续往下走,也就是说执行test这个代码和打印1这个可能是同时进行的,因为两个都是在子线程中做的,子线程不能同时打印1又执行test,肯定二者先选择一个先执行,由于start是先的,所以先执行block里面的代码,但是一执行完block里面的代码,这个线程就退出了,因为任务做完了,那再去执行test,就崩溃了。所以想要正常就把runloop打开即可。
5.3:问题3
思考:如何用gcd实现以下功能
异步并发执行任务1、任务2
等任务1、任务2都执行完毕后,再回到主线程执行任务3
用队列组
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 创建并发队列
dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);
// 添加异步任务
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"任务1-%@", [NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"任务2-%@", [NSThread currentThread]);
}
});
// 等前面的任务执行完毕后,会自动执行这个任务
dispatch_group_notify(group, queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = 0; i < 3; i++) {
NSLog(@"任务3-%@", [NSThread currentThread]);
}
});
});
// 换到主线程做事情,也可以这么做
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// for (int i = 0; i < 3; i++) {
// NSLog(@"任务3-%@", [NSThread currentThread]);
// }
// });
//下面打开后 也是会等到上面执行完再交替执行下面两个
// dispatch_group_notify(group, queue, ^{
// for (int i = 0; i < 3; i++) {
// NSLog(@"任务3-%@", [NSThread currentThread]);
// }
// });
//
// dispatch_group_notify(group, queue, ^{
// for (int i = 0; i < 3; i++) {
// NSLog(@"任务4-%@", [NSThread currentThread]);
// }
// });
}
2018-09-29 14:18:40.626368+0800 Interview02-group[4242:205995] 任务1-<NSThread: 0x60c00007e680>{number = 3, name = (null)}
2018-09-29 14:18:40.626394+0800 Interview02-group[4242:205994] 任务2-<NSThread: 0x60400006ff80>{number = 4, name = (null)}
2018-09-29 14:18:40.626595+0800 Interview02-group[4242:205995] 任务1-<NSThread: 0x60c00007e680>{number = 3, name = (null)}
2018-09-29 14:18:40.626669+0800 Interview02-group[4242:205995] 任务1-<NSThread: 0x60c00007e680>{number = 3, name = (null)}
2018-09-29 14:18:40.626670+0800 Interview02-group[4242:205994] 任务2-<NSThread: 0x60400006ff80>{number = 4, name = (null)}
2018-09-29 14:18:40.626822+0800 Interview02-group[4242:205994] 任务2-<NSThread: 0x60400006ff80>{number = 4, name = (null)}
2018-09-29 14:18:40.626913+0800 Interview02-group[4242:205957] 任务3-<NSThread: 0x60c000067e40>{number = 1, name = main}
2018-09-29 14:18:40.627063+0800 Interview02-group[4242:205957] 任务3-<NSThread: 0x60c000067e40>{number = 1, name = main}
2018-09-29 14:18:40.627173+0800 Interview02-group[4242:205957] 任务3-<NSThread: 0x60c000067e40>{number = 1, name = main}
6:多线程的安全隐患
资源共享
a:1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
b:比如多个线程访问同一个对象、同一个变量、同一个文件
当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题
例子
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self saleTicket];
}
/**
存钱、取钱演示
*/
- (void)moneyTest
{
self.money = 100;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self saveMoney];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self drawMoney];
}
});
}
/**
存钱
*/
- (void)saveMoney
{
int oldMoney = self.money;
sleep(.2);
oldMoney += 50;
self.money = oldMoney;
NSLog(@"存50,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
}
/**
取钱
*/
- (void)drawMoney
{
int oldMoney = self.money;
sleep(.2);
oldMoney -= 20;
self.money = oldMoney;
NSLog(@"取20,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
}
/**
卖1张票
*/
- (void)saleTicket
{
int oldTicketsCount = self.ticketsCount;
sleep(.2);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
}
/**
卖票演示
*/
- (void)ticketTest
{
self.ticketsCount = 15;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
}
2018-09-29 14:42:46.339273+0800 Interview03-安全隐患[4493:243680] 还剩14张票 - <NSThread: 0x60400026c0c0>{number = 5, name = (null)}
2018-09-29 14:42:46.339299+0800 Interview03-安全隐患[4493:243678] 还剩14张票 - <NSThread: 0x608000265940>{number = 4, name = (null)}
2018-09-29 14:42:46.339323+0800 Interview03-安全隐患[4493:243679] 还剩14张票 - <NSThread: 0x600000263ec0>{number = 3, name = (null)}
2018-09-29 14:42:46.339533+0800 Interview03-安全隐患[4493:243678] 还剩13张票 - <NSThread: 0x608000265940>{number = 4, name = (null)}
2018-09-29 14:42:46.339538+0800 Interview03-安全隐患[4493:243679] 还剩13张票 - <NSThread: 0x600000263ec0>{number = 3, name = (null)}
2018-09-29 14:42:46.339552+0800 Interview03-安全隐患[4493:243680] 还剩13张票 - <NSThread: 0x60400026c0c0>{number = 5, name = (null)}
2018-09-29 14:42:46.339642+0800 Interview03-安全隐患[4493:243678] 还剩12张票 - <NSThread: 0x608000265940>{number = 4, name = (null)}
2018-09-29 14:42:46.340235+0800 Interview03-安全隐患[4493:243679] 还剩11张票 - <NSThread: 0x600000263ec0>{number = 3, name = (null)}
2018-09-29 14:42:46.340346+0800 Interview03-安全隐患[4493:243680] 还剩10张票 - <NSThread: 0x60400026c0c0>{number = 5, name = (null)}
2018-09-29 14:42:46.340383+0800 Interview03-安全隐患[4493:243678] 还剩9张票 - <NSThread: 0x608000265940>{number = 4, name = (null)}
2018-09-29 14:42:46.340430+0800 Interview03-安全隐患[4493:243680] 还剩8张票 - <NSThread: 0x60400026c0c0>{number = 5, name = (null)}
2018-09-29 14:42:46.340432+0800 Interview03-安全隐患[4493:243679] 还剩8张票 - <NSThread: 0x600000263ec0>{number = 3, name = (null)}
2018-09-29 14:42:46.340797+0800 Interview03-安全隐患[4493:243680] 还剩6张票 - <NSThread: 0x60400026c0c0>{number = 5, name = (null)}
2018-09-29 14:42:46.340690+0800 Interview03-安全隐患[4493:243678] 还剩7张票 - <NSThread: 0x608000265940>{number = 4, name = (null)}
2018-09-29 14:42:46.341530+0800 Interview03-安全隐患[4493:243679] 还剩5张票 - <NSThread: 0x600000263ec0>{number = 3, name = (null)}
一共是15张 应该一张不剩 但是现在还有5张
图解
7:解决方案
解决方案:使用线程同步技术(同步,就是协同步调,按预定的先后次序进行)
常见的线程同步技术是:加锁
7.1:iOS中的线程同步方案
OSSpinLock
os_unfair_lock
pthread_mutex
dispatch_semaphore
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSRecursiveLock
NSCondition
NSConditionLock
@synchronized
a:OSSpinLock :ios已经过时 会爆一大推警告
// High-level lock 高级锁
需要导入头文件#import <libkern/OSAtomic.h>
示例
#import "ViewController.h"
#import <libkern/OSAtomic.h>
@interface ViewController ()
@property (assign, nonatomic) int money;
@property (assign, nonatomic) int ticketsCount;
@property (assign, nonatomic) OSSpinLock lock;
@property (assign, nonatomic) OSSpinLock lock1;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化锁
self.lock = OS_SPINLOCK_INIT;
self.lock1 = OS_SPINLOCK_INIT;
[self ticketTest];
[self moneyTest];
}
/**
存钱、取钱演示
*/
- (void)moneyTest
{
self.money = 100;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self saveMoney];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self drawMoney];
}
});
}
/**
存钱
*/
- (void)saveMoney
{
// 加锁
OSSpinLockLock(&_lock1);
int oldMoney = self.money;
sleep(.2);
oldMoney += 50;
self.money = oldMoney;
NSLog(@"存50,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
// 解锁
OSSpinLockUnlock(&_lock1);
}
/**
取钱
*/
- (void)drawMoney
{
// 加锁 这个锁 不能是局部变量,需要所有线程都是同一把锁。
OSSpinLockLock(&_lock1);
int oldMoney = self.money;
sleep(.2);
oldMoney -= 20;
self.money = oldMoney;
NSLog(@"取20,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
// 解锁
OSSpinLockUnlock(&_lock1);
}
/*
thread1:优先级比较高
thread2:优先级比较低
thread3
线程的调度,10ms
时间片轮转调度算法(进程、线程)
线程优先级
*/
/**
卖1张票
*/
- (void)saleTicket
{
// 尝试加锁,这个不会阻塞线程
// if (OSSpinLockTry(&_lock)) {
// int oldTicketsCount = self.ticketsCount;
// sleep(.2);
// oldTicketsCount--;
// self.ticketsCount = oldTicketsCount;
// NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
//
// OSSpinLockUnlock(&_lock);
// }
// 加锁
OSSpinLockLock(&_lock);
int oldTicketsCount = self.ticketsCount;
sleep(.2);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
// 解锁
OSSpinLockUnlock(&_lock);
}
/**
卖票演示
*/
- (void)ticketTest
{
self.ticketsCount = 15;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
}
@end
2018-09-29 15:02:05.974185+0800 Interview04-线程同步[4708:278271] 存50,还剩150元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:05.974185+0800 Interview04-线程同步[4708:278270] 还剩14张票 - <NSThread: 0x604000268dc0>{number = 3, name = (null)}
2018-09-29 15:02:05.974357+0800 Interview04-线程同步[4708:278271] 存50,还剩200元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:05.974358+0800 Interview04-线程同步[4708:278270] 还剩13张票 - <NSThread: 0x604000268dc0>{number = 3, name = (null)}
2018-09-29 15:02:05.974436+0800 Interview04-线程同步[4708:278271] 存50,还剩250元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:05.974446+0800 Interview04-线程同步[4708:278270] 还剩12张票 - <NSThread: 0x604000268dc0>{number = 3, name = (null)}
2018-09-29 15:02:05.974518+0800 Interview04-线程同步[4708:278271] 存50,还剩300元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:05.974653+0800 Interview04-线程同步[4708:278270] 还剩11张票 - <NSThread: 0x604000268dc0>{number = 3, name = (null)}
2018-09-29 15:02:05.974943+0800 Interview04-线程同步[4708:278271] 存50,还剩350元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:05.975143+0800 Interview04-线程同步[4708:278270] 还剩10张票 - <NSThread: 0x604000268dc0>{number = 3, name = (null)}
2018-09-29 15:02:05.975629+0800 Interview04-线程同步[4708:278271] 存50,还剩400元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:05.976433+0800 Interview04-线程同步[4708:278274] 还剩9张票 - <NSThread: 0x600000265a00>{number = 5, name = (null)}
2018-09-29 15:02:05.976893+0800 Interview04-线程同步[4708:278271] 存50,还剩450元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:05.977055+0800 Interview04-线程同步[4708:278274] 还剩8张票 - <NSThread: 0x600000265a00>{number = 5, name = (null)}
2018-09-29 15:02:05.977319+0800 Interview04-线程同步[4708:278271] 存50,还剩500元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:05.977601+0800 Interview04-线程同步[4708:278274] 还剩7张票 - <NSThread: 0x600000265a00>{number = 5, name = (null)}
2018-09-29 15:02:05.977709+0800 Interview04-线程同步[4708:278271] 存50,还剩550元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:05.978001+0800 Interview04-线程同步[4708:278274] 还剩6张票 - <NSThread: 0x600000265a00>{number = 5, name = (null)}
2018-09-29 15:02:06.034495+0800 Interview04-线程同步[4708:278274] 还剩5张票 - <NSThread: 0x600000265a00>{number = 5, name = (null)}
2018-09-29 15:02:06.034495+0800 Interview04-线程同步[4708:278271] 存50,还剩600元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:06.042574+0800 Interview04-线程同步[4708:278272] 还剩4张票 - <NSThread: 0x6040002690c0>{number = 6, name = (null)}
2018-09-29 15:02:06.042752+0800 Interview04-线程同步[4708:278272] 还剩3张票 - <NSThread: 0x6040002690c0>{number = 6, name = (null)}
2018-09-29 15:02:06.043047+0800 Interview04-线程同步[4708:278272] 还剩2张票 - <NSThread: 0x6040002690c0>{number = 6, name = (null)}
2018-09-29 15:02:06.043382+0800 Interview04-线程同步[4708:278272] 还剩1张票 - <NSThread: 0x6040002690c0>{number = 6, name = (null)}
2018-09-29 15:02:06.043473+0800 Interview04-线程同步[4708:278272] 还剩0张票 - <NSThread: 0x6040002690c0>{number = 6, name = (null)}
2018-09-29 15:02:06.045421+0800 Interview04-线程同步[4708:278273] 取20,还剩580元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
2018-09-29 15:02:06.045557+0800 Interview04-线程同步[4708:278273] 取20,还剩560元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
2018-09-29 15:02:06.045654+0800 Interview04-线程同步[4708:278273] 取20,还剩540元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
2018-09-29 15:02:06.045824+0800 Interview04-线程同步[4708:278273] 取20,还剩520元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
2018-09-29 15:02:06.045899+0800 Interview04-线程同步[4708:278273] 取20,还剩500元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
2018-09-29 15:02:06.046032+0800 Interview04-线程同步[4708:278273] 取20,还剩480元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
2018-09-29 15:02:06.046162+0800 Interview04-线程同步[4708:278273] 取20,还剩460元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
2018-09-29 15:02:06.046266+0800 Interview04-线程同步[4708:278273] 取20,还剩440元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
2018-09-29 15:02:06.046338+0800 Interview04-线程同步[4708:278273] 取20,还剩420元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
2018-09-29 15:02:06.046485+0800 Interview04-线程同步[4708:278273] 取20,还剩400元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
不同业务用不同的锁,并且对同一个资源争夺的时候用同一把锁。刚进来时候,一旦发现加锁了,就会等待,如果发现没有加锁,就会进行加锁,相当于线程就会阻塞在这里(OSSpinLockLock(&_lock);)。
那阻塞线程有两种方式:
1:忙等(自旋锁:占用cpu资源,相当于写了while(锁还没被放开)循环)而这个就是忙等的这个状态)。
2:让线程睡觉(互斥锁:如果发现其他线程正在执行锁定的代码,线程会进入休眠(就绪状态),等其他线程时间片打开锁后,线程会被唤醒执行。不占用cpu资源。
OSSpinLock叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源
目前已经不再安全,可能会出现优先级反转问题(cpu可能会把时间多分配一些给优先级比较高的线程,但是很有可能线程低的线程先加锁了,但是它的时间不多,可能不够了,cpu又把时间给了优先级比较高的线程,但是优先级比较高的线程可能一直就把时间浪费在等待上了。)。( 线程调度,实现了多线程的方案,时间片轮转调度算法(进程、线程)、线程优先级)
如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁
b:os_unfair_lock
// Low-level lock
// ll lock
// lll
// Low-level lock的特点等不到锁就休眠
代替上面的
os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持
从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等
需要导入头文件#import <os/lock.h>
#import <Foundation/Foundation.h>
@interface MJBaseDemo : NSObject
- (void)moneyTest;
- (void)ticketTest;
- (void)otherTest;
#pragma mark - 暴露给子类去使用
- (void)__saveMoney;
- (void)__drawMoney;
- (void)__saleTicket;
@end
#import "MJBaseDemo.h"
@interface MJBaseDemo()
@property (assign, nonatomic) int money;
@property (assign, nonatomic) int ticketsCount;
@end
@implementation MJBaseDemo
- (void)otherTest {}
/**
存钱、取钱演示
*/
- (void)moneyTest
{
self.money = 100;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self __saveMoney];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self __drawMoney];
}
});
}
/**
存钱
*/
- (void)__saveMoney
{
int oldMoney = self.money;
sleep(.2);
oldMoney += 50;
self.money = oldMoney;
NSLog(@"存50,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
}
/**
取钱
*/
- (void)__drawMoney
{
int oldMoney = self.money;
sleep(.2);
oldMoney -= 20;
self.money = oldMoney;
NSLog(@"取20,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
}
/**
卖1张票
*/
- (void)__saleTicket
{
int oldTicketsCount = self.ticketsCount;
sleep(.2);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
}
/**
卖票演示
*/
- (void)ticketTest
{
self.ticketsCount = 15;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// for (int i = 0; i < 10; i++) {
// [[[NSThread alloc] initWithTarget:self selector:@selector(__saleTicket) object:nil] start];
// }
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self __saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self __saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self __saleTicket];
}
});
}
@end
#import "OSUnfairLockDemo.h"
#import <os/lock.h>
@interface OSUnfairLockDemo()
// Low-level lock
// ll lock
// lll
// Low-level lock的特点等不到锁就休眠
@property (assign, nonatomic) os_unfair_lock moneyLock;
@property (assign, nonatomic) os_unfair_lock ticketLock;
@end
@implementation OSUnfairLockDemo
- (instancetype)init
{
if (self = [super init]) {
self.moneyLock = OS_UNFAIR_LOCK_INIT;
self.ticketLock = OS_UNFAIR_LOCK_INIT;
}
return self;
}
// 死锁:永远拿不到锁
- (void)__saleTicket
{
os_unfair_lock_lock(&_ticketLock);
[super __saleTicket];
os_unfair_lock_unlock(&_ticketLock);
}
- (void)__saveMoney
{
os_unfair_lock_lock(&_moneyLock);
[super __saveMoney];
os_unfair_lock_unlock(&_moneyLock);
}
- (void)__drawMoney
{
os_unfair_lock_lock(&_moneyLock);
[super __drawMoney];
os_unfair_lock_unlock(&_moneyLock);
}
@end
如果我忘记解锁了,那么会一直睡觉,进不去了。这种现象称之为 死锁
c:pthread_mutex
// Low-level lock的特点等不到锁就休眠
带有pthread的 都是跨平台的lniux、unix、windows、ios。
mutex叫做”互斥锁”,等待锁的线程会处于休眠状态
这个不用的时候需要销毁锁
需要导入头文件#import <pthread.h>
#import "MutexDemo.h"
#import <pthread.h>
@interface MutexDemo()
@property (assign, nonatomic) pthread_mutex_t ticketMutex;
@property (assign, nonatomic) pthread_mutex_t moneyMutex;
@end
@implementation MutexDemo
- (void)__initMutex:(pthread_mutex_t *)mutex
{
// 静态初始化
// pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// // 初始化属性
// pthread_mutexattr_t attr;
// pthread_mutexattr_init(&attr);
// pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// // 初始化锁
// pthread_mutex_init(mutex, &attr);
// // 销毁属性
// pthread_mutexattr_destroy(&attr);
// 初始化属性
// pthread_mutexattr_t attr;
// pthread_mutexattr_init(&attr);
// pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// 初始化锁
pthread_mutex_init(mutex, NULL);
// 销毁属性
// pthread_mutexattr_destroy(&attr);
}
- (instancetype)init
{
if (self = [super init]) {
[self __initMutex:&_ticketMutex];
[self __initMutex:&_moneyMutex];
}
return self;
}
// 死锁:永远拿不到锁
- (void)__saleTicket
{
pthread_mutex_lock(&_ticketMutex);
[super __saleTicket];
pthread_mutex_unlock(&_ticketMutex);
}
- (void)__saveMoney
{
pthread_mutex_lock(&_moneyMutex);
[super __saveMoney];
pthread_mutex_unlock(&_moneyMutex);
}
- (void)__drawMoney
{
pthread_mutex_lock(&_moneyMutex);
[super __drawMoney];
pthread_mutex_unlock(&_moneyMutex);
}
- (void)dealloc
{
pthread_mutex_destroy(&_moneyMutex);
pthread_mutex_destroy(&_ticketMutex);
}
@end
结构体基本语法:不可以把结构体直接赋值给(已经定义宝的)结构体变量,只能是定义变量的时候赋值。
递归锁
// PTHREAD_MUTEX_DEFAULT:普通锁
// PTHREAD_MUTEX_RECURSIVE :递归锁
递归锁:允许同一个线程对一把锁进行重复加锁
#import "MutexDemo2.h"
#import <pthread.h>
@interface MutexDemo2()
@property (assign, nonatomic) pthread_mutex_t mutex;
@end
@implementation MutexDemo2
- (void)__initMutex:(pthread_mutex_t *)mutex
{
// 递归锁:允许同一个线程对一把锁进行重复加锁
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
// PTHREAD_MUTEX_DEFAULT:普通锁
// PTHREAD_MUTEX_RECURSIVE :递归锁
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化锁
pthread_mutex_init(mutex, &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);
}
- (instancetype)init
{
if (self = [super init]) {
[self __initMutex:&_mutex];
}
return self;
}
- (void)otherTest
{
pthread_mutex_lock(&_mutex);
NSLog(@"%s", __func__);
static int count = 0;
if (count < 3) {
count++;
// 递归 自己调用自己 加锁
[self otherTest];
}
pthread_mutex_unlock(&_mutex);
}
- (void)otherTest2
{
pthread_mutex_lock(&_mutex);
NSLog(@"%s", __func__);
pthread_mutex_unlock(&_mutex);
}
- (void)dealloc
{
pthread_mutex_destroy(&_mutex);
}
@end
线程1:1:otherTest(+-)
2:otherTest(+-)
3:otherTest(+-)
线程2:otherTest(等待)
第一次调用otherTest方法,发现没有人调用,然后给它加锁,然后++,走到otherTest方法,又尝试对它加锁,由于这个锁是递归的,所以就允许又加了一次锁,也就是重复加锁,一次又一次,直到count结束,走到pthread_mutex_unlock这里,解锁完毕,就相当于当前otherTest调用完毕,然后又会来到pthread_mutex_unlock,又解锁一次,一次又一次,直到解锁完毕。
那如果是不同线程同时调用这一把锁呢 那就似乎没有达到这样保护的目的,但是递归锁的概念是:允许统一个线程对一把锁进行重复加锁。
比方说线程1 调用otherTest (第一步)加了一把锁,紧接着,又加载otherTest,同一条线程,允许又加锁(第二步),接着又加载otherTest,同一条线成,允许又加锁(第三步),如果总共就调用了三次,那么在最后一次otherTest调用完了之后,会来到pthread_mutex_unlock进行解锁, 所以otherTest(第三步)解锁 也就是-。然后otherTest它返回,紧接着otherTest的(第二步)解锁,最后第一步也会解锁。这样线程1加锁后,接下来线程2调用时,发现已经有一个不同的线程已经加锁了,那线程2就不能加锁了,那么它就会在pthread_mutex_lock这里等待,直到上面的锁所有都解锁完毕,才会加锁开始。
所以这个是可以解决多线程的。
pthread_mutex – 条件
使用场景:线程等待(多线程之间的依赖问题)、生产者-消费者模式。
多线程下:两条线程同时启动:假设线程1先进来,会先加锁,等下线程2 进来,发现已经这把锁已经被别人加过了,就会等待,线程1先进去,发现数组为0, 就会调用pthread_cond_wait方法,而这个方法会把这把锁_mutex 放开,同时也会传入条件_cond,也会等待这个条件来唤醒这个线程,所以当前线程就在wait这里堵住了。
当然线程1放开了这把锁,所以线程2就会把这把锁加起来,然后加元素,紧接着发送唤醒操作pthread_cond_signal,唤醒上面pthread_cond_wait的那条线程,而线程2会继续往下走,进行解锁。唤醒操作因为条件一致,并且线程1的这把锁重新加锁, 继续往下走, 删除元素,然后再解开这把锁。
#import "MutexDemo3.h"
#import <pthread.h>
@interface MutexDemo3()
@property (assign, nonatomic) pthread_mutex_t mutex;
@property (assign, nonatomic) pthread_cond_t cond; // 条件
@property (strong, nonatomic) NSMutableArray *data;
@end
@implementation MutexDemo3
- (instancetype)init
{
if (self = [super init]) {
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化锁
pthread_mutex_init(&_mutex, &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);
// 初始化条件
pthread_cond_init(&_cond, NULL);
self.data = [NSMutableArray array];
}
return self;
}
- (void)otherTest
{
[[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
[[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}
// 线程1
// 删除数组中的元素
- (void)__remove
{
pthread_mutex_lock(&_mutex);
NSLog(@"__remove - begin");
if (self.data.count == 0) {
// 等待 线程在这里不做事情 睡觉把这把锁放开
pthread_cond_wait(&_cond, &_mutex);
}
[self.data removeLastObject];
NSLog(@"删除了元素");
pthread_mutex_unlock(&_mutex);
}
// 线程2
// 往数组中添加元素
- (void)__add
{
pthread_mutex_lock(&_mutex);
sleep(1);
[self.data addObject:@"Test"];
NSLog(@"添加了元素");
// 信号 :唤醒pthread_cond_wait这个线程
pthread_cond_signal(&_cond);
// 广播
// pthread_cond_broadcast(&_cond);
pthread_mutex_unlock(&_mutex);
}
- (void)dealloc
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
@end
f:NSLock
NSLock是对mutex(pthread_mutex)普通锁的OC版本的封装
@interface NSLock : NSObject <NSLocking> {
@private
void *_priv;
}
// 尝试加锁,能加就加,不能加就返回NO,不会堵塞
- (BOOL)tryLock;
// 在传入的limit时间内加锁成功就返回YES,如果到了时间还没加锁成功,就返回NO,这个会堵塞,在时间等待前
- (BOOL)lockBeforeDate:(NSDate *)limit;
@end
g:NSRecursiveLock
NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致
h:NSCondition
NSCondition是对mutex和cond的OC版本的封装
@interface NSCondition : NSObject <NSLocking> {
@private
void *_priv;
}
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
#import "NSConditionDemo.h"
@interface NSConditionDemo()
@property (strong, nonatomic) NSCondition *condition;
@property (strong, nonatomic) NSMutableArray *data;
@end
@implementation NSConditionDemo
- (instancetype)init
{
if (self = [super init]) {
self.condition = [[NSCondition alloc] init];
self.data = [NSMutableArray array];
}
return self;
}
- (void)otherTest
{
[[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
[[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}
// 生产者-消费者模式
// 线程1
// 删除数组中的元素
- (void)__remove
{
[self.condition lock];
NSLog(@"__remove - begin");
if (self.data.count == 0) {
// 等待
[self.condition wait];
}
[self.data removeLastObject];
NSLog(@"删除了元素");
[self.condition unlock];
}
// 线程2
// 往数组中添加元素
- (void)__add
{
[self.condition lock];
sleep(1);
[self.data addObject:@"Test"];
NSLog(@"添加了元素");
// 信号
[self.condition signal];
// 广播
// [self.condition broadcast];
[self.condition unlock];
}
@end
2018-09-30 15:24:18.947043+0800 Interview04-线程同步[19485:584126] __remove - begin
2018-09-30 15:24:19.949639+0800 Interview04-线程同步[19485:584127] 添加了元素
2018-09-30 15:24:19.949949+0800 Interview04-线程同步[19485:584126] 删除了元素
这个里有一个注意点:signal
我们现在做的是signal放在解锁前面,这个跟放在后面是有点不同的。
现在放在前面的讲解:当执行到signal时候,会唤醒wait,但是现在线程2的锁还没有解开,无法立刻给线程1加锁,所以这个会一直等待给线程1加锁,当执行到线程2的解锁完毕的时候,才会重新加锁,正常往下走。
signal放在解锁后面的:这个先解锁了,所以执行到signal的时候,会直接唤醒wait的线程,因为没有锁了已经,所以直接加锁,不用等待,往下执行。
做验证的话 可以在两者之间sleep();睡上几秒钟
i:NSConditionLock
NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值
@interface NSConditionLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
看例子
// 线程按顺序执行
#import "NSConditionLockDemo.h"
@interface NSConditionLockDemo()
@property (strong, nonatomic) NSConditionLock *conditionLock;
@end
@implementation NSConditionLockDemo
- (instancetype)init
{
if (self = [super init]) { // 现在这把锁的条件值是1
self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
// 如果这里没有设置条件值的话,这个条件值为0
self.conditionLock = [[NSConditionLock alloc] init];
}
return self;
}
- (void)otherTest
{
// 这三个方法都是在子线程操作 而且都是耗时操作,并且还有顺序执行,
[[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
[[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
[[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
}
- (void)__one
{
// [self.conditionLock lockWhenCondition:1];
// 这个只管加锁,不用管条件值是什么,只要锁被放开,就直接加锁。
[self.conditionLock lock];
NSLog(@"__one");
sleep(1);
[self.conditionLock unlockWithCondition:2];
}
- (void)__two
{
// 当条件成立的时候加锁
[self.conditionLock lockWhenCondition:2];
NSLog(@"__two");
sleep(1);
// 设置这把锁的条件值是3 并且把这把锁放开
[self.conditionLock unlockWithCondition:3];
}
- (void)__three
{
// 当条件成立的时候加锁
[self.conditionLock lockWhenCondition:3];
NSLog(@"__three");
[self.conditionLock unlock];
}
@end
k:@synchronized
l:dispatch_queue
直接使用GCD的串行队列,也是可以实现线程同步的, 当然上面的条件也是可以实现线程同步的。
例子
#import "SerialQueueDemo.h"
@interface SerialQueueDemo()
@property (strong, nonatomic) dispatch_queue_t ticketQueue;
@property (strong, nonatomic) dispatch_queue_t moneyQueue;
@end
@implementation SerialQueueDemo
- (instancetype)init
{
if (self = [super init]) {
self.ticketQueue = dispatch_queue_create("ticketQueue", DISPATCH_QUEUE_SERIAL);
self.moneyQueue = dispatch_queue_create("moneyQueue", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)__drawMoney
{
dispatch_sync(self.moneyQueue, ^{
[super __drawMoney];
});
}
- (void)__saveMoney
{
dispatch_sync(self.moneyQueue, ^{
[super __saveMoney];
});
}
- (void)__saleTicket
{
dispatch_sync(self.ticketQueue, ^{
[super __saleTicket];
});
}
@end
m:dispatch_semaphore
semaphore叫做”信号量”
1 : 信号量的初始值,可以用来控制线程并发访问的最大数量:可以给gcd做最大并发数处理,类似NSOperation的最大并发数
2:用在:让异步变同步。
栗子:打印顺序:ABC
//信号量初始化必须大于等于0, 因为dispatch_semaphore_wait 执行的是-1操作。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
//创建异步队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"执行任务: A"); // -1 ③
//让信号量+1
dispatch_semaphore_signal(semaphore); // 0 ④
});
//当当前的信号量值为0时候会阻塞线,如果大于0的话,信号量-1,不阻塞线程.(相当于加锁)
NSLog(@"-----后A---前B----1"); // 0 ①
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // -1 ②
NSLog(@"-----后A---前B----2"); // 0 ⑤
dispatch_async(queue, ^{
sleep(1);
NSLog(@"执行任务: B"); // -1 ⑧
//让信号量+1(相当于解锁)
dispatch_semaphore_signal(semaphore); // 0 ⑨
});
NSLog(@"-----后B---前C----1"); // 0 ⑥
//当当前的信号量值为0时候会阻塞线,如果大于0的话,信号量-1,不阻塞线程
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // -1 ⑦
NSLog(@"-----后B---前C----2"); // 0 ⑩
dispatch_async(queue, ^{
sleep(1);
NSLog(@"执行任务: C"); // 0 十一
dispatch_semaphore_signal(semaphore);
});
2022-10-24 12:09:04.840256+0800 TestOC[47939:2218376] -----后A---前B----1
2022-10-24 12:09:05.845494+0800 TestOC[47939:2218814] 执行任务: A
2022-10-24 12:09:05.845841+0800 TestOC[47939:2218376] -----后A---前B----2
2022-10-24 12:09:05.846055+0800 TestOC[47939:2218376] -----后B---前C----1
2022-10-24 12:09:06.851362+0800 TestOC[47939:2218814] 执行任务: B
2022-10-24 12:09:06.851790+0800 TestOC[47939:2218376] -----后B---前C----2
2022-10-24 12:09:07.859921+0800 TestOC[47939:2218814] 执行任务: C
信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
看例子:同样可以实现票一张一张卖,设置并发数为1即可。
#import "SemaphoreDemo.h"
@interface SemaphoreDemo()
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
@property (strong, nonatomic) dispatch_semaphore_t ticketSemaphore;
@property (strong, nonatomic) dispatch_semaphore_t moneySemaphore;
@end
@implementation SemaphoreDemo
- (instancetype)init
{
if (self = [super init]) {
self.semaphore = dispatch_semaphore_create(5); // 初始化创建最大并发数是5
self.ticketSemaphore = dispatch_semaphore_create(1);
self.moneySemaphore = dispatch_semaphore_create(1);
}
return self;
}
- (void)__drawMoney
{
dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);
[super __drawMoney];
dispatch_semaphore_signal(self.moneySemaphore);
}
- (void)__saveMoney
{
dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);
[super __saveMoney];
dispatch_semaphore_signal(self.moneySemaphore);
}
- (void)__saleTicket
{
dispatch_semaphore_wait(self.ticketSemaphore, DISPATCH_TIME_FOREVER);
[super __saleTicket];
dispatch_semaphore_signal(self.ticketSemaphore);
}
- (void)otherTest
{
for (int i = 0; i < 20; i++) {
[[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
}
}
// 线程10、7、6、9、8
- (void)test
{
// wait:如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
// 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
sleep(2);
NSLog(@"test - %@", [NSThread currentThread]);
// 让信号量的值+1
dispatch_semaphore_signal(self.semaphore);
}
@end
如果刚开始最大并发数是5 那么这里限制为5 那么这五条线程也是一条一条执行的,这五条中每进入一条 信号量的值都减1,直到减到0,就用dispatch_semaphore_signal给信号量值+1, 然后每条线程出去就+1,紧接着又会有一条线程进来,所以wait这里会又-1,而-1后就又变成0了,所以就会继续等待。所以有一条线程出去(+1),就会有一条线程进来(-1)。
DISPATCH_TIME_FOREVER:的意思,就是一直等self.moneySemaphore值>0为止。
n:@synchronized(最简单的一种方案,苹果不推荐使用,没有提示,因为性能比较差)
@synchronized是对mutex递归锁的封装
源码查看:objc4中的objc-sync.mm文件
@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作
- (void)__saveMoney
{
@synchronized([self class]) { // objc_sync_enter :进入
[super __saveMoney];
} // objc_sync_exit // 退出 结束
}
objc_sync_enter :进入
// Begin synchronizing on 'obj'.
// Allocates recursive mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) { // 因为内部用的是哈希表,用传进入的对象为key,一个key一个value,也就是一把锁,所以与之对应。
SyncData* data = id2data(obj, ACQUIRE); // 传进去一个对象
assert(data);
data->mutex.lock(); // 得到一把锁
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
return result;
}
typedef struct SyncData {
struct SyncData* nextData;
DisguisedPtr<objc_object> object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex;
} SyncData;
static SyncData* id2data(id object, enum usage why)
{
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
SyncData **listp = &LIST_FOR_OBJ(object);
SyncData* result = NULL;
}
#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
#define LIST_FOR_OBJ(obj) sDataLists[obj].data // 这里是obj为key
static StripedMap<SyncList> sDataLists; // 这里用的是map,就是哈希表,也就是obj为key,一个key一个value。
objc_sync_exit // 退出 结束
// End synchronizing on 'obj'.
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, RELEASE);
if (!data) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
} else {
bool okay = data->mutex.tryUnlock();
if (!okay) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
} else {
// @synchronized(nil) does nothing
}
return result;
}
template <bool Debug>
class recursive_mutex_tt : nocopy_t {
pthread_mutex_t mLock;
public:
recursive_mutex_tt() : mLock(PTHREAD_RECURSIVE_MUTEX_INITIALIZER) {
lockdebug_remember_recursive_mutex(this);
}
recursive_mutex_tt(const fork_unsafe_lock_t unsafe)
: mLock(PTHREAD_RECURSIVE_MUTEX_INITIALIZER)
{ }
void lock()
{
lockdebug_recursive_mutex_lock(this);
int err = pthread_mutex_lock(&mLock);
if (err) _objc_fatal("pthread_mutex_lock failed (%d)", err);
}
void unlock()
{
lockdebug_recursive_mutex_unlock(this);
int err = pthread_mutex_unlock(&mLock);
if (err) _objc_fatal("pthread_mutex_unlock failed (%d)", err);
}
void forceReset()
{
lockdebug_recursive_mutex_unlock(this);
bzero(&mLock, sizeof(mLock));
mLock = pthread_mutex_t PTHREAD_RECURSIVE_MUTEX_INITIALIZER;
}
bool tryUnlock()
{
int err = pthread_mutex_unlock(&mLock);
if (err == 0) {
lockdebug_recursive_mutex_unlock(this);
return true;
} else if (err == EPERM) {
return false;
} else {
_objc_fatal("pthread_mutex_unlock failed (%d)", err);
}
}
void assertLocked() {
lockdebug_recursive_mutex_assert_locked(this);
}
void assertUnlocked() {
lockdebug_recursive_mutex_assert_unlocked(this);
}
};
因为内部用的是哈希表,用传进入的对象为key,一个key一个value,也就是一把锁,所以与之对应。
7.2:iOS线程同步方案性能比较
性能从高到低排序 (后面的顺序是讲解的顺序)
os_unfair_lock 替代下面的自旋锁,会处于休眠,相当于是互斥锁,ios10才开始 2
OSSpinLock 自旋锁 While盲等,不推荐使用1
dispatch_semaphore 信号量:最大并发数量,可以实现按顺序执行。ios8、ios9也支持。推荐。7
pthread_mutex 跨平台方案,互斥锁:休眠(默认、递归锁,条件)初始化完就要销毁 推荐。3
dispatch_queue(DISPATCH_QUEUE_SERIAL) gcd的效率比较高
NSLock 对默认mutex的封装,更加面向对象4
NSCondition 对mutex中pthread_cond_init的封装,把pthread_cond_init和mutex同时包装起来 (执行到中途一半的时候都可以等着别人执行。)(条件、锁)6
pthread_mutex(recursive) 递归锁,稍微耗性能。
NSRecursiveLock 对(上面)递归锁mutex的封装,包装成oc。5
NSConditionLock 条件锁,按顺序执行(实现线程同步了:GCD串行队列、 NSConditionLock、semaphore)6
@synchronized 最简单的用法,哈希表,通过对象找到锁,性能最差,包装太多,操作复杂8
可以宏定义下使用
#define SemaphoreBegin \
static dispatch_semaphore_t semaphore; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
semaphore = dispatch_semaphore_create(1); \
}); \
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#define SemaphoreEnd \
dispatch_semaphore_signal(semaphore);
//dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
@interface ViewController ()
@property (strong, nonatomic) MJBaseDemo *demo;
@property (strong, nonatomic) NSThread *thread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)test
{
NSLog(@"2");
}
- (void)test1
{
SemaphoreBegin;
// .....
SemaphoreEnd;
}
- (void)test2
{
SemaphoreBegin;
// .....
SemaphoreEnd;
}
- (void)test3
{
SemaphoreBegin;
// .....
SemaphoreEnd;
}
@end
7.3:atomic
atomic基本上在mac上使用,在oc上不常用。
atomic用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁
可以参考源码objc4的objc-accessors.mm
它并不能保证使用属性的过程是线程安全的 看下面例子
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *p = [[MJPerson alloc] init];
p.data = [NSMutableArray array];
[p.data addObject:@"1"]; // 这个过程中仅仅p.data是线程安全的,在get方法获取的对象添加新对象的过程就不是线程安全的了
NSMutableArray *array = p.data;
// 如果希望都安全这里需要:加锁
[array addObject:@"1"];
[array addObject:@"2"];
[array addObject:@"3"];
// 如果希望都安全这里需要:解锁
}
return 0;
}
nonatomic和atomic
atom:原子,不可再分割的单位
atomic:原子性:太耗性能,而且对同一个对象很少多线程的调用
比方说
for (int i = 0; i < 10; i++) {
dispatch_async(NULL, ^{
// 加锁
p.data = [NSMutableArray array];
// 解锁
});
}
而且可以在需要加锁的地方在外部加锁
给属性加上atomic修饰,可以保证属性的setter和getter都是原子性操作,也就是保证setter和gette内部是线程同步的
// 原子性操作,把下面加锁的的整体操作完,才能执行别的
// 加锁
int a = 10;
int b = 20;
int c = a + b;
// 解锁
源码
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
using spinlock_t = mutex_tt<LOCKDEBUG>;
7.4:iOS中的读写安全方案
一种情况:文件(IO)操作. :比上面更优化
1:从文件中读取内容 (很多线程可以同时读取)
2:往文件中写入内容 (只允许一条线程写入)
多读单写。
多线程抢占资源之所以出问题,就是在于有一条线程进行了写的操作。如果大家都在做读的操作,没有人做写的操作,基本没有什么问题,就牵扯不到多线程安全问题。
思考如何实现以下场景
a:同一时间,只能有1个线程进行写的操作
b:同一时间,允许有多个线程进行读的操作
c:同一时间,不允许既有写的操作,又有读的操作
上面的场景就是典型的“多读单写”,经常用于文件等数据的读写操作,iOS中的实现方案有
a:pthread_rwlock:读写锁 :跨平台的
b:dispatch_barrier_async:异步栅栏调用
pthread_rwlock
等待锁的线程会进入休眠
#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@property (assign, nonatomic) pthread_rwlock_t lock;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化锁
pthread_rwlock_init(&_lock, NULL);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
[self read];
});
dispatch_async(queue, ^{
[self write];
});
}
}
- (void)read {
// 读取的加锁
pthread_rwlock_rdlock(&_lock);
sleep(1);
NSLog(@"%s", __func__);
pthread_rwlock_unlock(&_lock);
}
- (void)write
{
// 写的加锁
thread_rwlock_wrlock(&_lock);
sleep(1);
NSLog(@"%s", __func__);
pthread_rwlock_unlock(&_lock);
}
- (void)dealloc
{
pthread_rwlock_destroy(&_lock);
}
@end
dispatch_barrier_async
这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的,如果不是自己创建的则跟不同同步没有区别
如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果
#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@property (strong, nonatomic) dispatch_queue_t queue;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
[self read];
[self read];
[self read];
[self write];
}
}
- (void)read {
dispatch_async(self.queue, ^{
sleep(1);
NSLog(@"read");
});
}
- (void)write
{
// 一旦调用这个,就建立了一个栅栏在写的前后,完成写的操作
dispatch_barrier_async(self.queue, ^{
sleep(1);
NSLog(@"write");
});
}
@end
2018-10-08 15:13:15.159130+0800 Interview02-读写安全[8307:471799] read
2018-10-08 15:13:15.159130+0800 Interview02-读写安全[8307:471800] read
2018-10-08 15:13:15.159130+0800 Interview02-读写安全[8307:471798] read
2018-10-08 15:13:16.163343+0800 Interview02-读写安全[8307:471799] write
2018-10-08 15:13:17.168406+0800 Interview02-读写安全[8307:471800] read
2018-10-08 15:13:17.168403+0800 Interview02-读写安全[8307:471799] read
2018-10-08 15:13:17.168403+0800 Interview02-读写安全[8307:471798] read
2018-10-08 15:13:18.173080+0800 Interview02-读写安全[8307:471800] write
看时间
8:线程间通信
NSPort.
面试题
你理解的多线程?
多条线程同时做事情,好处,坏处
你在项目中用过 GCD 吗?
说一下 OperationQueue 和 GCD 的区别,以及各自的优势
线程安全的处理手段有哪些?
查看--iOS中的线程同步方案--
OC你了解的锁有哪些?在你回答基础上进行二次提问;
追问一:自旋和互斥对比?
追问二:使用以上锁需要注意哪些?
追问三:用C/OC/C++,任选其一,实现自旋或互斥?口述即可!
什么情况使用自旋锁比较划算?
a:预计线程等待锁的时间很短
b:加锁的代码(临界区:lock和unlock之间的代码)经常被调用,但竞争情况很少发生
c:CPU资源不紧张
d:多核处理器
什么情况使用互斥锁比较划算?
a:预计线程等待锁的时间较长
b:单核处理器
c:临界区有IO操作(比较占用cpu资源) 文件操作、
d:临界区代码复杂或者循环量大
e:临界区竞争非常激烈