一 iOS多线程介绍
二 线程同步方案
一 iOS多线程介绍
首先我们先了解一下关于线程的几个概念:
1 什么是进程
- 在系统正在运行的一个应用程序
- 每个进程之间是独立的,每个进程均运行在其专用受保护的内存空间内
2 什么是线程
- 进程任务执行的最小单位,一个进程想要执行任务,必须要有一个线程
- 进程的所有任务都在线程中执行
3 什么是多线程
多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。多线程是在同一时间需要完成多项任务的时候实现的
1.2 iOS中的线程
1 主线程
一个iOS程序运行后,默认会开启的一条线程,称为:主线程或UI线程,
- 显示\刷新界面
- 处理UI事件,比如点击事件、滚动事件、拖拽事件等等
2 子线程
耗时的操作会卡住主线程,严重影响流畅度,所以我们要手动创建子线程,来处理耗时任务,子线程的任务执行,不会卡住界面刷新
1.3 iOS中的多线程实现方案有哪些
技术方案 | 简介 | 语言 | 线程生命周期 | 使用频率 |
---|---|---|---|---|
pthread | 1 一套通用的多线程API 2 适用于Unix\Linux\Windows等系统 3 跨平台\可移植 4 使用难度大 |
C | 程序员管理 | 几乎不用 |
NSThread | 1 使用更加面向对象 简单易用 2 可直接操作线程对象 |
OC | 程序员管理 | 偶尔使用 |
GCD | 1旨在替代NSThread等线程技术 2充分利用设备的多核 |
C | 自动管理 | 经常使用 |
NSOperation | 1 基于GCD(底层是GCD) 2 比GCD多了一些更简单实用的功能 3 使用更加面向对象 |
OC | 自动管理 | 经常使用 |
下面让我们详细讲解这几个方案的具体使用。
1.3.1 pthread
#import "ViewController.h"
#import
@interface ViewController ()
@end
@implementation ViewController
void *run(){
NSLog(@"pthread_t method");
return NULL;
}
- (void)viewDidLoad {
[super viewDidLoad];
pthread_t thread = NULL;
pthread_create(&thread,NULL,run,(__bridge void *)(@"pthread"));
}
@end
pthread的创建执行其实也是比较简单的,不过实现过程是通过C语言进行的,要注意的一点是在使用Pthreads的时候一定要手动把当前线程结束掉。因为我们经常使用的GCD和NSOperation已经被苹果封装过了,所以我们想要定制进行线程的操作就会有一定限制,pthread难用,我们几乎开发中用不到,这里简单介绍下,不做过多赘述。
1.3.2 NSThread
NSThread由苹果进行了封装,并且完全面向对象。所以可以直接使用OC方法操控线程对象,非常直观和方便。可以说对于ios开发人员而言,使用NSThread就开始了真正的多线程开发。所以,通过NSThread我们具体讨论一些线程相关的问题,包括如下内容:
- 使用NSThread创建线程
- 线程状态
- 线程间通信
1.3.2 (1)使用NSThread创建线程
使用NSThread创建线程有以下几种方式:
- 使用NSThread的init方法显式创建
- 使用NSThread类方法显式创建并启动线程
- 隐式创建并启动线程
注意:只有使用NSThread的init方法创建的线程才会返回具体的线程实例。也就是说如果想要对线程做更多的控制,比如添加线程的名字、更改优先级等操作,要使用第一种方式来创建线程。但是此种方法需要使用start方法来手动启动线程。
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self createforgroundThread];
[self createBackgroudThread];
[self createforgroundThreadwithInit];
}
/**
显示创建线程并启动
*/
- (void)createforgroundThread {
[NSThread detachNewThreadSelector:@selector(thread1) toTarget:self withObject:NULL];
}
/**
显示创建线程并启动
*/
- (void)createBackgroudThread{
[self performSelectorInBackground:@selector(thread2) withObject:NULL];
}
/**
init方法显示创建线程并启动
*/
- (void)createforgroundThreadwithInit{
NSThread *myThread = [[NSThread alloc] initWithTarget:self selector:@selector(thread3) object:NULL];
// 设置线程名
[myThread setName:@"thread3"];
// 设置优先级 优先级从0到1 1最高
[myThread setThreadPriority:1];
// 启动线程
[myThread start];
}
- (void)thread1{
NSLog(@"thread1");
}
- (void)thread2{
NSLog(@"thread2");
}
- (void)thread3{
NSLog(@"thread3");
}
@end
output:
2019-01-12 13:59:29.331850+0800 runloop[12157:971766] thread3
2019-01-12 13:59:29.332072+0800 runloop[12157:971764] thread1
2019-01-12 13:59:29.401398+0800 runloop[12157:971765] thread2
1.3.2(2) 线程状态
线程状态分为:启动线程, 阻塞线程,结束线程
启动线程:
// 线程启动
- (void)start;
阻塞线程:
// 线程休眠到某一时刻
+ (void)sleepUntilDate:(NSDate *)date;
// 线程休眠多久
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
结束线程
// 结束线程
+ (void)exit;
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self createforgroundThreadwithInit];
}
/**
init方法显示创建线程并启动
*/
- (void)createforgroundThreadwithInit{
NSThread *myThread = [[NSThread alloc] initWithTarget:self selector:@selector(thread3) object:NULL];
// 设置线程名
[myThread setName:@"thread3"];
// 设置优先级 优先级从0到1 1最高
[myThread setThreadPriority:1];
// 启动线程
[myThread start];
}
- (void)thread3{
NSLog(@"thread is create -- the name is: \"%@\"", [NSThread currentThread].name);
NSLog(@"sleep");
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
NSLog(@"sleep end");
NSLog(@"sleep again");
[NSThread sleepForTimeInterval:2];
NSLog(@"sleep again end");
int i = 0;
while (i < 30) {
i++;
NSLog(@"thread working");
if(i == 10) {
NSLog(@"thread will dead");
[[NSThread currentThread] cancel];
}
if([[NSThread currentThread] isCancelled]) {
NSLog(@"thread cancelled");
[NSThread exit];
}
}
}
@end
output:
2019-01-12 14:18:05.552029+0800 runloop[12299:983386] thread is create -- the name is: "thread3"
2019-01-12 14:18:05.552132+0800 runloop[12299:983386] sleep
2019-01-12 14:18:08.553445+0800 runloop[12299:983386] sleep end
2019-01-12 14:18:08.553848+0800 runloop[12299:983386] sleep again
2019-01-12 14:18:10.554802+0800 runloop[12299:983386] sleep again end
2019-01-12 14:18:10.555158+0800 runloop[12299:983386] thread working
2019-01-12 14:18:10.555443+0800 runloop[12299:983386] thread working
2019-01-12 14:18:10.555662+0800 runloop[12299:983386] thread working
2019-01-12 14:18:10.555841+0800 runloop[12299:983386] thread working
2019-01-12 14:18:10.555978+0800 runloop[12299:983386] thread working
2019-01-12 14:18:10.556073+0800 runloop[12299:983386] thread working
2019-01-12 14:18:10.556175+0800 runloop[12299:983386] thread working
2019-01-12 14:18:10.556275+0800 runloop[12299:983386] thread working
2019-01-12 14:18:10.556439+0800 runloop[12299:983386] thread working
2019-01-12 14:18:10.556812+0800 runloop[12299:983386] thread working
2019-01-12 14:18:10.557139+0800 runloop[12299:983386] thread will dead
2019-01-12 14:18:10.557494+0800 runloop[12299:983386] thread cancelled
1.3.2(3) 线程间通信
线程间通信我们最常用的就是开启子线程进行耗时操作,操作完毕后回到主线程,进行数据赋值以及刷新主线程UI。主要有一下方法:
//与主线程通信
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes
//与其他子线程通信
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self createforgroundThreadwithInit];
}
/**
init方法显示创建线程并启动
*/
- (void)createforgroundThreadwithInit{
NSThread *myThread = [[NSThread alloc] initWithTarget:self selector:@selector(thread3) object:NULL];
// 设置线程名
[myThread setName:@"thread3"];
// 设置优先级 优先级从0到1 1最高
[myThread setThreadPriority:1];
// 启动线程
[myThread start];
}
- (void)thread3{
NSLog(@"current thread:%@",[NSThread currentThread]);
[self performSelectorOnMainThread:@selector(thread2) withObject:NULL waitUntilDone:false];
}
- (void)thread2{
NSLog(@"current thread:%@",[NSThread currentThread]);
}
output:
2019-01-12 14:26:40.765486+0800 runloop[12371:988455] current thread:{number = 6, name = thread3} // number 6子线程
2019-01-12 14:26:40.779501+0800 runloop[12371:988246] current thread:{number = 1, name = main} // number 1 主线程
NSThread在平时使用很少,最常用到的无非就是 [NSThread currentThread]获取当前线程。接下来让我们了解一下常用的多线程技术GCD。
1.3.3 GCD
- GCD 简介
- GCD 任务和队列
- GCD 的使用步骤
- GCD 的基本使用
- GCD 线程间的通信
- GCD 的其他方法
1.3.3 (1) GCD 简介
Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。
GCD的优点:
1 GCD 可用于多核的并行运算,C语言实现执行效率高
2 GCD 会自动利用更多的 CPU 内核(比如双核、四核)
3 GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。
1.3.3(2) GCD 任务和队列
任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:『同步执行』 和 『异步执行』。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
1 同步执行(sync):
同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
只能在当前线程中执行任务,不具备开启新线程的能力。
2 异步执行(async):
异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
可以在新的线程中执行任务,具备开启新线程的能力。
队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。
1 串行队列(Serial Dispatch Queue):
每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
2 并发队列(Concurrent Dispatch Queue):
可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
3 主队列(Main Dispatch Queue)
所有放在主队列中的任务,都会放到主线程中执行。
可使用 dispatch_get_main_queue() 方法获得主队列。
并发功能只有在异步(dispatch_async)函数下才有效
各种队列的执行效果让我们用下面一张表格来来展示一下:
任务 | 并发队列 | 手动创建的串行队列 | 主队列 |
---|---|---|---|
同步(sync) | 1 没有开启新线程 2 串行执行任务 |
1没有开启新线程 2串行执行任务 |
1没有开启新线程 2 串行执行任务 |
异步(async) | 1 有开启新线程 2 并发执行任务 |
1有开启新线程 2串行执行任务 |
1没有开启新线程 2 串行执行任务 |
由上表我们再着重强调以下内容:
同步和异步主要影响:能不能开启新的线程
同步:在当前线程中执行任务,不具备开启新线程的能力
异步:在新的线程中执行任务,具备开启新线程的能力
并发和串行主要影响:任务的执行方式
并发:多个任务并发(同时)执行
串行:一个任务执行完毕后,再执行下一个任务
使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)
1.3.3(3) GCD 的使用步骤
GCD 的使用步骤只有两步:
1 创建一个队列(串行队列或并发队列);
2 将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)。
下边来看看队列的创建方法 / 获取方法,以及任务的创建方法
队列 :可以使用 dispatch_queue_create 方法来创建队列。该方法需要传入两个参数:
第一个参数表示队列的唯一标识符,用于 DEBUG,可为空。多个队列的名称不要重复。
第二个参数用来识别是串行队列还是并发队列
DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列。
// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
主队列: 可使用 dispatch_get_main_queue() 方法获得主队列
// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();
主队列的实质上就是一个普通的串行队列,只是因为默认情况下,当前代码是放在主队列中的,然后主队列中的代码,有都会放到主线程中去执行,所以才造成了主队列特殊的现象。
全局并发队列 : 对于并发队列,GCD 默认提供了 全局并发队列(Global Dispatch Queue)。
可以使用 dispatch_get_global_queue 方法来获取全局并发队列。需要传入两个参数。第一个参数表示队列优先级,一般用 DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用 0 即可。
// 全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
任务的创建方式
GCD 提供了同步执行任务的创建方法 dispatch_sync 和异步执行任务创建方法 dispatch_async
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
});
三种队列两种任务执行方式,那么我们就有六种组合方式
- 同步执行 + 并发队列
- 异步执行 + 并发队列
- 同步执行 + 串行队列
- 异步执行 + 串行队列
- 同步执行 + 主队列
- 异步执行 + 主队列
接下来我们代码讲解下每种组合方式。
1.3.3(3) GCD 的基本使用
下面我们将以一段代码来岩石各个组合
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// [self test1];
// [self test2];
// [self test3];
// [self test4];
// [self test5];
[self test6];
}
/**
同步执行+并发队列
特点:在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
output :2019-01-12 16:45:32.596207+0800 runloop[13079:1054357] 任务1 current thread{number = 1, name = main}
2019-01-12 16:45:34.597730+0800 runloop[13079:1054357] 任务2 current thread{number = 1, name = main}
2019-01-12 16:45:36.598840+0800 runloop[13079:1054357] 任务3 current thread{number = 1, name = main}
打印结果说明:虽然是并发队列,但是是同步执行,不会产生新的线程,只有一个线程在执行,所以结果是按顺序执行的
*/
-(void)test1 {
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务1 current thread%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务2 current thread%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务3 current thread%@",[NSThread currentThread]);
});
}
/**
异步执行+并发队列
特点:可以开启多个线程,任务交替(同时)执行
output :2019-01-12 16:52:35.852066+0800 runloop[13153:1059160] 任务1 current thread{number = 6, name = (null)}
2019-01-12 16:52:35.852066+0800 runloop[13153:1059152] 任务3 current thread{number = 8, name = (null)}
2019-01-12 16:52:35.852153+0800 runloop[13153:1059158] 任务2 current thread{number = 5, name = (null)}
打印结果说明:因为是异步,所以会开新线程,这里开了3个新线程,因为是并发执行,所以他们的打印时间很相近,虽然我们加了两秒的延时。
*/
-(void)test2 {
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务1 current thread%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务2 current thread%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务3 current thread%@",[NSThread currentThread]);
});
}
/**
同步执行+串行队列
不会开启新线程,在当前线程执行任务,任务是串行的,执行完一个任务,再执行下一个任务
output :2019-01-12 16:59:11.040463+0800 runloop[13213:1063254] 任务1 current thread{number = 1, name = main}
2019-01-12 16:59:13.042049+0800 runloop[13213:1063254] 任务2 current thread{number = 1, name = main}
2019-01-12 16:59:15.043721+0800 runloop[13213:1063254] 任务3 current thread{number = 1, name = main}
打印结果说明:我们可以看出,任务都在一个线程里执行的,并且都是每间隔两秒打印下一条
*/
-(void)test3 {
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务1 current thread%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务2 current thread%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务3 current thread%@",[NSThread currentThread]);
});
}
/**
异步执行+串行队列
会开启新线程,因为任务是串行的,执行完一个任务,接着才会执行下一个任务
output :2019-01-12 17:03:59.001504+0800 runloop[13275:1067188] 任务1 current thread{number = 7, name = (null)}
2019-01-12 17:04:01.005210+0800 runloop[13275:1067188] 任务2 current thread{number = 7, name = (null)}
2019-01-12 17:04:03.006034+0800 runloop[13275:1067188] 任务3 current thread{number = 7, name = (null)}
打印结果说明:我们可以看出,任务都在一个线程里执行的,并且都是每间隔两秒打印下一条,但是线程不是当前线程,而是新起了一个子线程7
*/
-(void)test4 {
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务1 current thread%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务2 current thread%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务3 current thread%@",[NSThread currentThread]);
});
}
/**
同步执行 + 主队列
互相等待卡住不可行
output : 1: EXC_BAD_INSTRUCTION
打印结果说明:这是因为我们在主线程中执行 test5 方法,相当于把 test5 任务放到了主线程的队列中。而 同步执行 会等待当前队列中的任务执行完毕,才会接着执行。那么当我们把 任务 1 追加到主队列中,任务 1 就在等待主线程处理完 test5 任务。而test5 任务需要等待 任务 1 执行完毕,才能接着执行。
那么,现在的情况就是 test5 任务和 任务 1 都在等对方执行完毕。这样大家互相等待,所以就卡住了,所以我们的任务执行不了
*/
-(void)test5 {
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务1 current thread%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务2 current thread%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务3 current thread%@",[NSThread currentThread]);
});
}
/**
异步执行 + 主队列
只在主线程中执行任务,执行完一个任务,再执行下一个任务。
output : 1: 2019-01-12 17:10:45.245042+0800 runloop[13369:1071898] 任务1 current thread{number = 1, name = main}
2019-01-12 17:10:47.246585+0800 runloop[13369:1071898] 任务2 current thread{number = 1, name = main}
2019-01-12 17:10:49.247665+0800 runloop[13369:1071898] 任务3 current thread{number = 1, name = main}
打印结果说明:所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(虽然 异步执行 具备开启线程的能力,但因为是主队列,所以所有任务都在主线程中
*/
-(void)test6 {
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务1 current thread%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务2 current thread%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任务3 current thread%@",[NSThread currentThread]);
});
}
@end
上面就是任务和队列组合执行情况,下面我们来看一下线程间通信,其实就是主子线程之间的切换。
1.3.3(4)GCD 线程间的通信
GCD 线程间的通信就是主线程和子线程之间的切换,我们通过一段代码来看一下:
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self threadCheck];
}
/**
2019-01-17 14:59:49.584452+0800 test[7700:111372] 子线程操作后:{number = 6, name = (null)}
2019-01-17 14:59:49.808802+0800 test[7700:111154] 回到主线程:{number = 1, name = main}
*/
- (void)threadCheck{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"子线程操作后:%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主线程:%@",[NSThread currentThread]);
});
});
}
@end
就是如此简单。最后我们简单介绍下GCD其他一些api
1.3.3(5)GCD 的其他方法
1 GCD 延时执行方法:dispatch_after
dispatch_after 方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after 方法是很有效的
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self dispatchAfterTest];
}
/**
2019-01-17 15:19:29.142163+0800 test[9301:397146] currentThread---{number = 1, name = main}
2019-01-17 15:19:32.428245+0800 test[9301:397146] currentThread---{number = 1, name = main}
由上面打印的时间可以看出,大致是延时3秒,但不是太精确
*/
- (void)dispatchAfterTest{
NSLog(@"currentThread---%@",[NSThread currentThread]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"currentThread---%@",[NSThread currentThread]);
});
}
@end
2 GCD 一次性代码(只执行一次):dispatch_once
我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 方法。使用 dispatch_once 方法能保证某段代码在程序运行过程中只被执行 1 次,并且即使在多线程的环境下,dispatch_once 也可以保证线程安全。
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)oneceTest{
static dispatch_once_t oncet;
dispatch_once(&oncet, ^{
NSLog(@"执行我了");
});
}
/**
output:2019-01-17 15:25:40.511911+0800 test[9468:400527] 开始执行once
2019-01-17 15:25:40.512050+0800 test[9468:400527] 执行我了
2019-01-17 15:25:42.248688+0800 test[9468:400527] 开始执行once
2019-01-17 15:25:42.768524+0800 test[9468:400527] 开始执行once
2019-01-17 15:25:43.056465+0800 test[9468:400527] 开始执行once
我们可以看到当第一次点击会执行dispatch_once,以后再怎么点击就不执行了
*/
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(@"开始执行once");
[self oneceTest];
}
@end
3 GCD 队列组:dispatch_group
有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。
- 调用队列组的
dispatch_group_async
先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的dispatch_group_enter
、dispatch_group_leave
组合来实现dispatch_group_async
。 - 调用队列组的
dispatch_group_notify
回到指定线程执行任务。或者使用dispatch_group_wait
回到当前线程继续向下执行(会阻塞当前线程)。
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// [self dispatchGroupNotify];
// [self dispatchGroupWait];
[self groupEnterAndLeave];
}
/**
队列组 dispatch_group_notify
监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务
output: 2019-01-17 15:42:24.175299+0800 test[9895:409178] 执行任务1
2019-17 15:42:24.175299+0800 test[9895:409181] 执行任务2
2019-01-17 15:42:27.175883+0800 test[9895:409114] 执行任务执行完成
多执行几次,你就会发现任务1任务2有时候顺序有变化,但完成任务总是在最后执行
*/
- (void)dispatchGroupNotify{
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"执行任务1");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"执行任务2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"执行任务执行完成");
});
}
/**
队列组 dispatch_group_wait
暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
output:2019-01-17 15:50:44.448640+0800 test[10156:414897] 执行任务2
2019-01-17 15:50:44.448640+0800 test[10156:414890] 执行任务1
2019-01-17 15:50:47.450183+0800 test[10156:414807] 执行任务执行完成
执行结果同dispatch_group_notify
*/
- (void)dispatchGroupWait{
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"执行任务1");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"执行任务2");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
[NSThread sleepForTimeInterval:3];
NSLog(@"执行任务执行完成");
}
/**
队列组 dispatch_group_enter、dispatch_group_leave
dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1
dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。
当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait 解除阻塞,以及执行追加到 dispatch_group_notify 中的任务。
output:2019-01-17 15:58:09.503040+0800 test[10351:419225] 执行任务1
2019-01-17 15:58:09.503040+0800 test[10351:419231] 执行任务2
2019-01-17 15:58:12.503621+0800 test[10351:419074] 执行任务执行完成
*/
- (void)groupEnterAndLeave{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"执行任务1");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"执行任务2");
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"执行任务执行完成");
});
}
@end
OK GCD就讲这么多,下面我们看一下最后一个多线程方案NSOperation,它其实就是对GCD的封装,使用起来更加面相对象。
1.3.4 NSOperation
- NSOperation、NSOperationQueue 简介、操作和操作队列、使用步骤和基本使用方法
- 控制串行/并发执行、NSOperation 操作依赖和优先级、线程间的通信.
- NSOperation、NSOperationQueue 常用属性和方法归纳
1.3.4(1) NSOperation、NSOperationQueue 简介、操作和操作队列、使用步骤和基本使用方法
NSOperation、NSOperationQueue 是苹果提供给我们的一套多线程解决方案。实际上 NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。但是比 GCD 更简单易用、代码可读性也更高
1 NSOperation、NSOperationQueue有哪些优点?
1 可添加完成的代码块,在操作完成后执行。
2 添加操作之间的依赖关系,方便的控制执行顺序。
3 设定操作执行的优先级。
4 可以很方便的取消一个操作的执行。
5 使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled
2 NSOperation、NSOperationQueue 操作和操作队列
由于NSOperation是封装的GCD,那么基本CGD里的概念,NSOperation里面也有。CGD里的任务就是NSOperation里 操作 ,CGD里的操作就是NSOperation里的操作队列,
操作(Operation):
操作的意思,就是你在线程中执行的那段代码。
在 NSOperation 中,我们使用 NSOperation 子类 NSInvocationOperation、NSBlockOperation,或者自定义子类来封装操作。
操作队列(Operation Queues):
1 这里的队列指操作队列,即用来存放操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。NSOperationQueue 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)
2 操作队列通过设置最大并发操作数(maxConcurrentOperationCount)来控制并发
(maxConcurrentOperationCount >1)、串行
(maxConcurrentOperationCount = 1)
3 NSOperationQueue 为我们提供了两种不同类型的队列:主队列
和自定义队列
。主队列运行在主线程之上,而自定义队列在后台执行
3 NSOperation、NSOperationQueue 使用步骤
NSOperation 实现多线程的使用步骤分为三步:
1 创建操作:先将需要执行的操作封装到一个 NSOperation 对象中。
2 创建队列:创建 NSOperationQueue 对象。
3 将操作加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 对象中。
#import
NS_ASSUME_NONNULL_BEGIN
@interface MyNSOperation : NSOperation
@end
NS_ASSUME_NONNULL_END
#import "MyNSOperation.h"
@implementation MyNSOperation
- (void)main{
if (!self.isCancelled) {
for (int i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"%s---%@",__func__, [NSThread currentThread]);
}
}
}
@end
#import "ViewController.h"
#import "MyNSOperation.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// [self useInvocationOperation];
// [self useBlockOperation];
// [self useMyOperation];
// [self addOperationToQueue];
[self addOperationWithBlockToQueue];
}
//--------------------------------无队列情况下----------------------------
/**
NSOperation 是个抽象类,不能用来封装操作。我们只有使用它的子类来封装操作。我们有三种方式来封装操作
1 使用子类 NSInvocationOperation
2 使用子类 NSBlockOperation
3 自定义继承自 NSOperation 的子类,通过实现内部相应的方法来封装操作
*/
/**
使用子类NSInvocationOperation
output: 2019-01-20 14:22:05.521479+0800 test[4206:359857] -[ViewController task1]:{number = 1, name = main}
在主线程中单独使用使用子类 NSInvocationOperation 执行一个操作的情况下,操作是在当前线程执行的,没有开启新线程.
*/
- (void)useInvocationOperation {
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
[operation start];
}
- (void)task1{
NSLog(@"%s:%@",__func__,[NSThread currentThread]);
}
/*
使用子类NSBlockOperation
output:2019-01-20 14:30:53.433236+0800 test[4219:362114] -[ViewController useBlockOperation]_block_invoke_2:{number = 6, name = (null)}
2019-01-19 14:30:53.433236+0800 test[4219:362097] -[ViewController useBlockOperation]_block_invoke:{number = 1, name = main}
2019-01-19 14:30:53.433445+0800 test[4219:362114] -[ViewController useBlockOperation]_block_invoke_4:{number = 6, name = (null)}
2019-01-19 14:30:53.433447+0800 test[4219:362097] -[ViewController useBlockOperation]_block_invoke_3:{number = 1, name = main}
NSBlockOperation操作并不是在当前线程执行的,当添加任务多的时候,会开启新的线程
*/
- (void)useBlockOperation {
NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%s:%@",__func__,[NSThread currentThread]);
}];
[bop addExecutionBlock:^{
NSLog(@"%s:%@",__func__,[NSThread currentThread]);
}];
[bop addExecutionBlock:^{
NSLog(@"%s:%@",__func__,[NSThread currentThread]);
}];
[bop addExecutionBlock:^{
NSLog(@"%s:%@",__func__,[NSThread currentThread]);
}];
[bop start];
}
/**
使用自定义operation
output: 2019-01-20 14:37:50.206775+0800 test[4226:363426] -[MyNSOperation main]---{number = 1, name = main}
2019-01-20 14:37:51.207355+0800 test[4226:363426] -[MyNSOperation main]---{number = 1, name = main}
2019-01-20 14:37:52.208759+0800 test[4226:363426] -[MyNSOperation main]---{number = 1, name = main}
在主线程单独使用自定义继承自 NSOperation 的子类的情况下,是在主线程执行操作,并没有开启新线程
*/
- (void)useMyOperation{
MyNSOperation *myop = [[MyNSOperation alloc] init];
[myop start];
}
//--------------------------------有队列情况下----------------------------
/**
创建队列
NSOperationQueue 一共有两种队列:
主队列 :NSOperationQueue *queue = [NSOperationQueue mainQueue];
自定义队列:串行,并行 :NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation 需要配合 NSOperationQueue 来实现多线程
我们需要将创建好的操作加入到队列中去 共有两种方法
1 - (void)addOperation:(NSOperation *)op;
2 - (void)addOperationWithBlock:(void (^)(void))block;
*/
/** 使用 (void)addOperation:(NSOperation *)op;
output: 2019-01-20 14:55:47.521236+0800 test[4240:366463] -[ViewController task1]:{number = 3, name = (null)}
2019-01-20 14:55:47.521293+0800 test[4240:366465] -[ViewController task1]:{number = 5, name = (null)}
2019-01-20 14:55:47.521561+0800 test[4240:366463] -[ViewController addOperationToQueue]_block_invoke:{number = 3, name = (null)}
2019-01-20 14:55:47.521578+0800 test[4240:366465] -[ViewController addOperationToQueue]_block_invoke_2:{number = 5, name = (null)}
使用 NSOperation 子类创建操作,并使用 addOperation: 将操作加入到操作队列后能够开启新线程,进行并发执行
*/
- (void)addOperationToQueue {
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //创建自定义队列
NSInvocationOperation *op1 =[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil]; //创建一个任务1
NSInvocationOperation *op2 =[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil]; //创建一个任务2
NSBlockOperation *bop1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%s:%@",__func__,[NSThread currentThread]);
}];
[bop1 addExecutionBlock:^{
NSLog(@"%s:%@",__func__,[NSThread currentThread]);
}];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:bop1];
}
/**
* 使用 addOperationWithBlock: 将操作加入到操作队列中
* output: 2019-01-20 14:58:57.810714+0800 test[4250:367498] -[ViewController addOperationWithBlockToQueue]_block_invoke_2:{number = 5, name = (null)}
2019-01-20 14:58:57.811031+0800 test[4250:367500] -[ViewController addOperationWithBlockToQueue]_block_invoke:{number = 6, name = (null)}
2019-01-20 14:58:57.811232+0800 test[4250:367498] -[ViewController addOperationWithBlockToQueue]_block_invoke_3:{number = 5, name = (null)}
使用 addOperationWithBlock: 将操作加入到操作队列后能够开启新线程,进行并发执行。
*/
- (void)addOperationWithBlockToQueue {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
NSLog(@"%s:%@",__func__,[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"%s:%@",__func__,[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"%s:%@",__func__,[NSThread currentThread]);
}];
}
@end
队列及任务基本使用方法代码都实现了,详解都在注释里,下面我们介绍下如何控制任务的并发和串行执行,依赖,优先级,以及线程间通信。
1.3.4(2) 控制串行/并发执行、NSOperation 操作依赖和优先级、线程间的通信
#import "ViewController.h"
#import "MyNSOperation.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// [self setMaxConcurrentOperationCountDefault];
// [self setMaxConcurrentOperationCountOne];
// [self setMaxConcurrentOperationCountMore];
// [self addDependency];
[self threadQueuePriority];
// [self threadToMain];
}
//-------------------------NSOperationQueue 控制串行执行、并发执行------------------------------------
/**
NSOperationQueue 创建的自定义队列同时具有串行、并发功能他通过maxConcurrentOperationCount(最大并发操作数)
来控制在一个指定的队列中有几个操作参与并发执行,maxConcurrentOperationCount控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数
它的值情况分以下三种:
maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。
maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行。
maxConcurrentOperationCount 大于1时,队列为并发队列。操作并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为 min{自己设定的值,系统设定的默认最大值}。
下面我们来分别j看一下这三种值的时候任务执行情况
*/
/**
maxConcurrentOperationCount为-1
不设置maxConcurrentOperationCount的值,就默认为-1,它可以进行并发执行
output:: 2019-01-20 15:12:22.207107+0800 test[4270:369467] -[ViewController setMaxConcurrentOperationCountDefault]_block_invoke---{number = 6, name = (null)}
2019-01-20 15:12:22.207473+0800 test[4270:369467] -[ViewController setMaxConcurrentOperationCountDefault]_block_invoke_3---{number = 6, name = (null)}
2019-01-20 15:12:22.207645+0800 test[4270:369466] -[ViewController setMaxConcurrentOperationCountDefault]_block_invoke_2---{number = 5, name = (null)}
通过输出结果它开启了两个线程,虽然我们每个任务都让线程延迟两秒但是它们的执行时间非常相近,所以是并发执行
*/
- (void)setMaxConcurrentOperationCountDefault{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
NSLog(@"%s---%@",__func__, [NSThread currentThread]); // 打印当前线程
[NSThread sleepForTimeInterval:2];
}];
[queue addOperationWithBlock:^{
NSLog(@"%s---%@",__func__, [NSThread currentThread]); // 打印当前线程
[NSThread sleepForTimeInterval:2];
}];
[queue addOperationWithBlock:^{
NSLog(@"%s---%@",__func__, [NSThread currentThread]); // 打印当前线程
[NSThread sleepForTimeInterval:2];
}];
}
/**
maxConcurrentOperationCount为1
maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行
output:2019-01-20 15:16:48.279971+0800 test[4285:370966] -[ViewController setMaxConcurrentOperationCountOne]_block_invoke---{number = 6, name = (null)}
2019-01-20 15:16:50.285585+0800 test[4285:370967] -[ViewController setMaxConcurrentOperationCountOne]_block_invoke_2---{number = 3, name = (null)}
2019-01-20 15:16:52.291242+0800 test[4285:370967] -[ViewController setMaxConcurrentOperationCountOne]_block_invoke_3---{number = 3, name = (null)}
通过输出结果它虽然开启了两个线程,但执行时间都是间隔两l秒才执行下一个任务,也就是串行执行
*/
- (void)setMaxConcurrentOperationCountOne{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
[queue addOperationWithBlock:^{
NSLog(@"%s---%@",__func__, [NSThread currentThread]); // 打印当前线程
[NSThread sleepForTimeInterval:2];
}];
[queue addOperationWithBlock:^{
NSLog(@"%s---%@",__func__, [NSThread currentThread]); // 打印当前线程
[NSThread sleepForTimeInterval:2];
}];
[queue addOperationWithBlock:^{
NSLog(@"%s---%@",__func__, [NSThread currentThread]); // 打印当前线程
[NSThread sleepForTimeInterval:2];
}];
}
/**
maxConcurrentOperationCount为-1
maxConcurrentOperationCount 大于1时,队列为并行队列。能并行执行
output:2019-01-20 15:21:10.212660+0800 test[4288:371657] -[ViewController setMaxConcurrentOperationCountMore]_block_invoke---{number = 4, name = (null)}
2019-01-20 15:21:10.212779+0800 test[4288:371659] -[ViewController setMaxConcurrentOperationCountMore]_block_invoke_3---{number = 3, name = (null)}
2019-01-20 15:21:10.212650+0800 test[4288:371661] -[ViewController setMaxConcurrentOperationCountMore]_block_invoke_2---{number = 5, name = (null)}
通过输出结果它开启了3个线程,虽然我们每个任务都让线程延迟两秒但是它们的执行时间非常相近,所以是并发执行
*/
- (void)setMaxConcurrentOperationCountMore{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"%s---%@",__func__, [NSThread currentThread]); // 打印当前线程
}];
[queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"%s---%@",__func__, [NSThread currentThread]); // 打印当前线程
}];
[queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"%s---%@",__func__, [NSThread currentThread]); // 打印当前线程
}];
}
// ------------------------------- NSOperation 操作依赖 ----------------------
/**
操作依赖就是我们可以控制操作之间的执行先后顺序。NSOperation 提供了3个接口供我们管理和查看依赖。
- (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。
- (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。
@property (readonly, copy) NSArray *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。
*/
/**
比如说有 A、B 两个操作,其中 A 执行完操作,B 才能执行操作,那就需要让操作 B 依赖于操作 A
output:2019-01-20 15:29:08.208398+0800 test[4298:373334] A :-[ViewController addDependency]_block_invoke---{number = 6, name = (null)}
2019-01-20 15:29:08.208842+0800 test[4298:373333] B :-[ViewController addDependency]_block_invoke_2---{number = 5, name = (null)}
不管运行几次我们都发现它都是先执行A 再执行B
*/
- (void)addDependency {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *A = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"A :%s---%@",__func__, [NSThread currentThread]); // 打印当前线程
}];
NSBlockOperation *B = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"B :%s---%@",__func__, [NSThread currentThread]); // 打印当前线程
}];
[B addDependency:A];
[queue addOperation:A];
[queue addOperation:B];
}
//--------------------NSOperation 优先级----------------------------------
/**
NSOperation提供了queuePriority(优先级)属性,queuePriority属性适用于同一操作队列中的操作,不适用于不同操作队列中的操作。默认情况下,所有新创建的操作对象优先级都是NSOperationQueuePriorityNormal。但是我们可以通过setQueuePriority:方法来改变当前操作在同一队列中的执行优先级
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)
什么是就绪:当一个操作的所有依赖都已经完成时,操作对象通常会进入准备就绪状态,等待执行
queuePriority 属性决定了进入准备就绪状态下的操作之间的开始执行顺序。并且,优先级不能取代依赖关系
1 如果一个队列中既包含高优先级操作,又包含低优先级操作,并且两个操作都已经准备就绪,那么队列先执行高优先级操作。比如上例中,如果 op1 和 op4 是不同优先级的操作,那么就会先执行优先级高的操作。
2 如果,一个队列中既包含了准备就绪状态的操作,又包含了未准备就绪的操作,未准备就绪的操作优先级比准备就绪的操作优先级高。那么,虽然准备就绪的操作优先级低,也会优先执行。优先级不能取代依赖关系。如果要控制操作间的启动顺序,则必须使用依赖关系
output:
2019-01-20 15:50:57.086306+0800 test[4314:376454] A :-[ViewController threadQueuePriority]_block_invoke---{number = 5, name = (null)}
2019-01-20 15:50:57.086649+0800 test[4314:376457] C :-[ViewController threadQueuePriority]_block_invoke_3---{number = 3, name = (null)}
2019-01-20 15:50:57.089095+0800 test[4314:376457] D :-[ViewController threadQueuePriority]_block_invoke_4---{number = 3, name = (null)}
2019-01-20 15:50:57.089278+0800 test[4314:376457] B :-[ViewController threadQueuePriority]_block_invoke_2---{number = 3, name = (null)}
通过输出结果我们可以看到:执行顺序为A->C->D->B
虽然B的优先级大于C和D,但是B有依赖,B依赖于A,那么A,C,D没有依赖,那么他们会先进入就绪状态,上面的概念2我们可以知道一个队列中既包含了准备就绪状态的操作,又包含了未准备就绪的操作,未准备就绪的操作优先级比准备就绪的操作优先级高。那么,虽然准备就绪的操作优先级低,也会优先执行。优先级不能取代依赖关系。如果要控制操作间的启动顺序,则必须使用依赖关系
*/
- (void)threadQueuePriority{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *A = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"A :%s---%@",__func__, [NSThread currentThread]); // 打印当前线程
}];
A.queuePriority = NSOperationQueuePriorityVeryHigh;
NSBlockOperation *B = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"B :%s---%@",__func__, [NSThread currentThread]); // 打印当前线程
}];
B.queuePriority = NSOperationQueuePriorityHigh;
NSBlockOperation *C = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"C :%s---%@",__func__, [NSThread currentThread]); // 打印当前线程
}];
C.queuePriority = NSOperationQueuePriorityNormal;
NSBlockOperation *D = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"D :%s---%@",__func__, [NSThread currentThread]); // 打印当前线程
}];
D.queuePriority = NSOperationQueuePriorityNormal;
[B addDependency:A];
[queue addOperation:A];
[queue addOperation:B];
[queue addOperation:C];
[queue addOperation:D];
}
//------------------------------ NSOperation、NSOperationQueue 线程间的通信----------------------
/**
线程通信就是主子线程之间的切换
output:2019-01-20 15:44:33.532722+0800 test[4305:375194] -[ViewController threadToMain]_block_invoke---{number = 6, name = (null)}
2019-01-20 15:44:33.541935+0800 test[4305:375181] -[ViewController threadToMain]_block_invoke_2---{number = 1, name = main}
在子任务执行中 再把任务添加到主队列NSOperationQueue mainQueue,就是切换到主线程了
*/
-(void)threadToMain{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
NSLog(@"%s---%@",__func__, [NSThread currentThread]); // 打印当前线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"%s---%@",__func__, [NSThread currentThread]); // 打印当前线程
}];
}];
}
@end
上面我们通过代码演示了NSOperation 控制串行/并发执行、NSOperation 操作依赖和优先级、线程间的通信等内容,最后我们再讲一下NSOperation、NSOperationQueue 常用属性和方法。
1.3.4(3) NSOperation、NSOperationQueue 常用属性和方法归纳
NSOperation 常用属性和方法
取消操作方法
- (void)cancel;
可取消操作,实质是标记 isCancelled 状态。
判断操作状态方法
- (BOOL)isFinished;
判断操作是否已经结束。
- (BOOL)isCancelled;
判断操作是否已经标记为取消。
- (BOOL)isExecuting;
判断操作是否正在在运行。
- (BOOL)isReady;
判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关。
操作同步
- (void)waitUntilFinished;
阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步。
- (void)setCompletionBlock:(void (^)(void))block; completionBlock
会在当前操作执行完毕时执行 completionBlock。
- (void)addDependency:(NSOperation *)op;
添加依赖,使当前操作依赖于操作 op 的完成。
- (void)removeDependency:(NSOperation *)op;
移除依赖,取消当前操作对操作 op 的依赖。
@property (readonly, copy) NSArray
在当前操作开始执行之前完成执行的所有操作对象数组。
NSOperationQueue 常用属性和方法
取消/暂停/恢复操作
- (void)cancelAllOperations;
可以取消队列的所有操作。
- (BOOL)isSuspended;
判断队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态。
- (void)setSuspended:(BOOL)b;
可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列。
操作同步
- (void)waitUntilAllOperationsAreFinished;
阻塞当前线程,直到队列中的操作全部执行完毕。
添加/获取操作
- (void)addOperationWithBlock:(void (^)(void))block
; 向队列中添加一个 NSBlockOperation 类型操作对象。
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait
; 向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束
- (NSArray *)operations
; 当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除)。
- (NSUInteger)operationCount
; 当前队列中的操作数。
获取队列
+ (id)currentQueue
; 获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil。
+ (id)mainQueue
; 获取主队列。
这里的暂停和取消(包括操作的取消和队列的取消)并不代表可以将当前的操作立即取消,而是当当前的操作执行完毕之后不再执行新的操作。
暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作
OK到这里iOS多线程的实现几个方案我们就讲完了,接下来我们讲下iOS开发中的线程同步方案。
二 线程同步方案
多线程并不是安全的,尤其是在以下情况下:
- 资源共享
一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
比如多个线程访问同一个对象、同一个变量、同一个文件 - 当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题
那么我们如何解决多线程的问题呢,下面就是我们要讲的线程同步技术,
线程同步 :同步,就是协同步调,按预定的先后次序进行
常见的线程同步技术是:加锁
iOS多线程同步方案有以下这些:
同步技术 | 描述 |
---|---|
os_unfair_lock | os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持 |
OSSpinLock | OSSpinLock 叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源,目前已经不再安全,可能会出现优先级反转问题 |
dispatch_semaphore | semaphore叫做”信号量” 信号量的初始值,可以用来控制线程并发访问的最大数量信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步 |
pthread_mutex | 叫做”互斥锁”,等待锁的线程会处于休眠状态 |
dispatch_queue (DISPATCH_QUEUE_SERIAL) |
直接使用GCD的串行队列,也是可以实现线程同步的 |
NSLock | NSLock是对mutex普通锁的封装 |
NSCondition | NSCondition是对mutex和cond的封装 |
pthread_mutex(recursive) | 递归锁,同一个线程可以对同一把锁重复加锁 |
NSRecursiveLock | NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致 |
NSConditionLock | NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值 |
@synchronized | @synchronized是对mutex递归锁的封装 |
下面让我们来逐一讲解这些线程同步技术
2.1 OSSpinLock
OSSpinLock叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源 ,自旋锁就是类似一个while死循环一样,遇到锁就是循环执行一段代码,直到while循环条件不成立时,再往下执行
目前已经不再安全,可能会出现优先级反转问题
如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁,它的常用API有如下:
//初始化锁
OSSpinLock lock = OS_SPINLOCK_INIT;
//尝试加锁(如果需要等待就不加锁,直接返回false;如果不需要等待就加锁,返回true)
bool result = OSSpinLockTry(&lock);
//加锁
OSSpinLockLock(&lock);
//解锁
OSSpinLockUnlock(&lock);
需要导入头文件#import
我们先建立一个通用类,就会买票和存取钱的例子,以后每讲一个锁都会使用到这些公用例子
BaseDemo.h
#import
@interface BaseDemo : NSObject
- (void)moneyTest;
- (void)ticketTest;
- (void)otherTest;
#pragma mark -给子类去使用
- (void)__saveMoney;
- (void)__drawMoney;
- (void)__saleTicket;
@end
BaseDemo.m
#import "BaseDemo.h"
@interface BaseDemo()
@property (assign, nonatomic) int money;
@property (assign, nonatomic) int ticketsCount;
@end
@implementation BaseDemo
- (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
上面买票卖票,存钱取钱都是放在并发队列里执行的,多个线程可以同时进行读写,不加任何线程锁的话,数据都会造成读写错乱,下面是OSSpinLock锁的使用示例:
OSSpinLockDemo.m
#import "OSSpinLockDemo.h"
#import
@interface OSSpinLockDemo()
// High-level lock
@property (assign, nonatomic) OSSpinLock moneyLock;
@property (assign, nonatomic) OSSpinLock ticketLock;
@end
@implementation OSSpinLockDemo
- (instancetype)init
{
if (self = [super init]) {
self.moneyLock = OS_SPINLOCK_INIT;
self.ticketLock = OS_SPINLOCK_INIT;
}
return self;
}
- (void)__drawMoney
{
OSSpinLockLock(&_moneyLock);
[super __drawMoney];
OSSpinLockUnlock(&_moneyLock);
}
- (void)__saveMoney
{
OSSpinLockLock(&_moneyLock);
[super __saveMoney];
OSSpinLockUnlock(&_moneyLock);
}
- (void)__saleTicket
{
OSSpinLockLock(&_ticketLock);
[super __saleTicket];
OSSpinLockUnlock(&_ticketLock);
}
@end
@interface ViewController ()
@property (strong, nonatomic) BaseDemo *demo;
@property (strong, nonatomic) NSThread *thread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
BaseDemo *demo = [[OSSpinLockDemo alloc] init];
[demo ticketTest];
[demo moneyTest];
}
@end
output:
2019-01-24 17:34:22.984792+0800 Interview04-线程同步[1968:66100] 还剩14张票 - {number = 4, name = (null)}
2019-01-24 17:34:22.984897+0800 Interview04-线程同步[1968:66102] 存50,还剩150元 - {number = 3, name = (null)}
2019-01-24 17:34:22.984989+0800 Interview04-线程同步[1968:66100] 还剩13张票 - {number = 4, name = (null)}
2019-01-24 17:34:22.984990+0800 Interview04-线程同步[1968:66102] 存50,还剩200元 - {number = 3, name = (null)}
2019-01-24 17:34:22.985083+0800 Interview04-线程同步[1968:66100] 还剩12张票 - {number = 4, name = (null)}
2019-01-24 17:34:22.985132+0800 Interview04-线程同步[1968:66102] 存50,还剩250元 - {number = 3, name = (null)}
2019-01-24 17:34:22.985180+0800 Interview04-线程同步[1968:66100] 还剩11张票 - {number = 4, name = (null)}
2019-01-24 17:34:22.985220+0800 Interview04-线程同步[1968:66102] 存50,还剩300元 - {number = 3, name = (null)}
2019-01-24 17:34:22.985283+0800 Interview04-线程同步[1968:66100] 还剩10张票 - {number = 4, name = (null)}
2019-01-24 17:34:22.986138+0800 Interview04-线程同步[1968:66102] 存50,还剩350元 - {number = 3, name = (null)}
2019-01-24 17:34:22.986479+0800 Interview04-线程同步[1968:66102] 存50,还剩400元 - {number = 3, name = (null)}
2019-01-24 17:34:22.986828+0800 Interview04-线程同步[1968:66102] 存50,还剩450元 - {number = 3, name = (null)}
2019-01-24 17:34:22.986871+0800 Interview04-线程同步[1968:66104] 还剩9张票 - {number = 6, name = (null)}
2019-01-24 17:34:22.987310+0800 Interview04-线程同步[1968:66102] 存50,还剩500元 - {number = 3, name = (null)}
2019-01-24 17:34:22.987327+0800 Interview04-线程同步[1968:66104] 还剩8张票 - {number = 6, name = (null)}
2019-01-24 17:34:22.987625+0800 Interview04-线程同步[1968:66102] 存50,还剩550元 - {number = 3, name = (null)}
2019-01-24 17:34:22.987785+0800 Interview04-线程同步[1968:66104] 还剩7张票 - {number = 6, name = (null)}
2019-01-24 17:34:22.988025+0800 Interview04-线程同步[1968:66102] 存50,还剩600元 - {number = 3, name = (null)}
2019-01-24 17:34:22.993630+0800 Interview04-线程同步[1968:66104] 还剩6张票 - {number = 6, name = (null)}
2019-01-24 17:34:22.993818+0800 Interview04-线程同步[1968:66104] 还剩5张票 - {number = 6, name = (null)}
2019-01-24 17:34:22.996694+0800 Interview04-线程同步[1968:66101] 还剩4张票 - {number = 7, name = (null)}
2019-01-24 17:34:22.996812+0800 Interview04-线程同步[1968:66101] 还剩3张票 - {number = 7, name = (null)}
2019-01-24 17:34:22.996974+0800 Interview04-线程同步[1968:66101] 还剩2张票 - {number = 7, name = (null)}
2019-01-24 17:34:22.997117+0800 Interview04-线程同步[1968:66107] 取20,还剩580元 - {number = 8, name = (null)}
2019-01-24 17:34:22.997226+0800 Interview04-线程同步[1968:66101] 还剩1张票 - {number = 7, name = (null)}
2019-01-24 17:34:22.997632+0800 Interview04-线程同步[1968:66107] 取20,还剩560元 - {number = 8, name = (null)}
2019-01-24 17:34:22.997956+0800 Interview04-线程同步[1968:66101] 还剩0张票 - {number = 7, name = (null)}
2019-01-24 17:34:22.998136+0800 Interview04-线程同步[1968:66107] 取20,还剩540元 - {number = 8, name = (null)}
2019-01-24 17:34:23.012055+0800 Interview04-线程同步[1968:66107] 取20,还剩520元 - {number = 8, name = (null)}
2019-01-24 17:34:23.012573+0800 Interview04-线程同步[1968:66107] 取20,还剩500元 - {number = 8, name = (null)}
2019-01-24 17:34:23.013042+0800 Interview04-线程同步[1968:66107] 取20,还剩480元 - {number = 8, name = (null)}
2019-01-24 17:34:23.013500+0800 Interview04-线程同步[1968:66107] 取20,还剩460元 - {number = 8, name = (null)}
2019-01-24 17:34:23.013916+0800 Interview04-线程同步[1968:66107] 取20,还剩440元 - {number = 8, name = (null)}
2019-01-24 17:34:23.014248+0800 Interview04-线程同步[1968:66107] 取20,还剩420元 - {number = 8, name = (null)}
2019-01-24 17:34:23.014652+0800 Interview04-线程同步[1968:66107] 取20,还剩400元 - {number = 8, name = (null)}
存取必须是同一把锁才能起到加锁的目的。OSSpinLock在iOS10之后就已经被废弃了,他有优先级反转的问题,多线程中有线程优先级的概念,cpu执行多线程任务时,使用时间片轮转来分配任务执行时间,线程优先级越高,cpu给线程执行的时间越多,如果一个低优先级线程先加锁,那么优先级高的就会处于忙等状态,因为优先级高,cpu会把大量时间分配给优先级高的线程,那么优先级低的线程就获取不到执行时间,也就无法释放锁。还好OSSpinLock已经有了一个替代方案,os_unfair_lock。
2.2 os_unfair_lock
os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持
从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等
需要导入头文件 #import
它的常用API如下:
//初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//尝试加锁
os_unfair_lock_trylock(&lock);
//加锁
os_unfair_lock_lock(&lock);
//解锁
os_unfair_lock_unlock(&lock);
使用示例如下
#import "BaseDemo.h"
@interface OSUnfairLockDemo : BaseDemo
@end
#import "OSUnfairLockDemo.h"
#import
@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
output:
2019-01-26 15:22:41.936370+0800 Interview04-线程同步[26173:2095071] 还剩14张票 - {number = 5, name = (null)}
2019-01-26 15:22:41.936396+0800 Interview04-线程同步[26173:2095083] 存50,还剩150元 - {number = 6, name = (null)}
2019-01-26 15:22:41.936517+0800 Interview04-线程同步[26173:2095071] 还剩13张票 - {number = 5, name = (null)}
2019-01-26 15:22:41.936524+0800 Interview04-线程同步[26173:2095083] 存50,还剩200元 - {number = 6, name = (null)}
2019-01-26 15:22:41.936625+0800 Interview04-线程同步[26173:2095071] 还剩12张票 - {number = 5, name = (null)}
2019-01-26 15:22:41.936641+0800 Interview04-线程同步[26173:2095083] 存50,还剩250元 - {number = 6, name = (null)}
2019-01-26 15:22:41.936711+0800 Interview04-线程同步[26173:2095071] 还剩11张票 - {number = 5, name = (null)}
2019-01-26 15:22:41.936737+0800 Interview04-线程同步[26173:2095083] 存50,还剩300元 - {number = 6, name = (null)}
2019-01-26 15:22:41.936804+0800 Interview04-线程同步[26173:2095071] 还剩10张票 - {number = 5, name = (null)}
2019-01-26 15:22:41.937041+0800 Interview04-线程同步[26173:2095083] 存50,还剩350元 - {number = 6, name = (null)}
2019-01-26 15:22:41.937285+0800 Interview04-线程同步[26173:2095075] 还剩9张票 - {number = 3, name = (null)}
2019-01-26 15:22:42.070559+0800 Interview04-线程同步[26173:2095083] 存50,还剩400元 - {number = 6, name = (null)}
2019-01-26 15:22:42.070559+0800 Interview04-线程同步[26173:2095073] 还剩8张票 - {number = 4, name = (null)}
2019-01-26 15:22:42.070688+0800 Interview04-线程同步[26173:2095083] 存50,还剩450元 - {number = 6, name = (null)}
2019-01-26 15:22:42.070700+0800 Interview04-线程同步[26173:2095073] 还剩7张票 - {number = 4, name = (null)}
2019-01-26 15:22:42.070785+0800 Interview04-线程同步[26173:2095083] 存50,还剩500元 - {number = 6, name = (null)}
2019-01-26 15:22:42.070803+0800 Interview04-线程同步[26173:2095073] 还剩6张票 - {number = 4, name = (null)}
2019-01-26 15:22:42.070890+0800 Interview04-线程同步[26173:2095083] 存50,还剩550元 - {number = 6, name = (null)}
2019-01-26 15:22:42.071142+0800 Interview04-线程同步[26173:2095073] 还剩5张票 - {number = 4, name = (null)}
2019-01-26 15:22:42.071332+0800 Interview04-线程同步[26173:2095083] 存50,还剩600元 - {number = 6, name = (null)}
2019-01-26 15:22:42.138339+0800 Interview04-线程同步[26173:2095073] 还剩4张票 - {number = 4, name = (null)}
2019-01-26 15:22:42.138351+0800 Interview04-线程同步[26173:2095084] 取20,还剩580元 - {number = 7, name = (null)}
2019-01-26 15:22:42.138467+0800 Interview04-线程同步[26173:2095075] 还剩3张票 - {number = 3, name = (null)}
2019-01-26 15:22:42.138474+0800 Interview04-线程同步[26173:2095084] 取20,还剩560元 - {number = 7, name = (null)}
2019-01-26 15:22:42.138568+0800 Interview04-线程同步[26173:2095075] 还剩2张票 - {number = 3, name = (null)}
2019-01-26 15:22:42.138583+0800 Interview04-线程同步[26173:2095084] 取20,还剩540元 - {number = 7, name = (null)}
2019-01-26 15:22:42.138660+0800 Interview04-线程同步[26173:2095075] 还剩1张票 - {number = 3, name = (null)}
2019-01-26 15:22:42.138675+0800 Interview04-线程同步[26173:2095084] 取20,还剩520元 - {number = 7, name = (null)}
2019-01-26 15:22:42.138902+0800 Interview04-线程同步[26173:2095075] 还剩0张票 - {number = 3, name = (null)}
2019-01-26 15:22:42.139398+0800 Interview04-线程同步[26173:2095084] 取20,还剩500元 - {number = 7, name = (null)}
2019-01-26 15:22:42.139553+0800 Interview04-线程同步[26173:2095084] 取20,还剩480元 - {number = 7, name = (null)}
2019-01-26 15:22:42.139818+0800 Interview04-线程同步[26173:2095084] 取20,还剩460元 - {number = 7, name = (null)}
2019-01-26 15:22:42.140407+0800 Interview04-线程同步[26173:2095084] 取20,还剩440元 - {number = 7, name = (null)}
2019-01-26 15:22:42.140684+0800 Interview04-线程同步[26173:2095084] 取20,还剩420元 - {number = 7, name = (null)}
2019-01-26 15:22:42.140949+0800 Interview04-线程同步[26173:2095084] 取20,还剩400元 - {number = 7, name = (null)}
2.3 pthread_mutex
mutex叫做”互斥锁”,等待锁的线程会处于休眠状态
需要导入头文件#import
它是一个跨平台的多线程实现方案,常用API如下:
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
//初始化锁
pthread_mutex_t mutext;
pthread_mutex_init(&mutext, &attr);
//尝试加锁
pthread_mutex_trylock(&mutext);
//加锁
pthread_mutex_lock(&mutext);
//解锁
pthread_mutex_unlock(&mutext);
//销毁相关资源
pthread_mutexattr_destroy(&attr);
pthread_mutex_destroy(&mutext)
#define PTHREAD_MUTEX_NORMAL 0
#define PTHREAD_MUTEX_ERRORCHECK 1
#define PTHREAD_MUTEX_RECURSIVE 2
#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
pthread_mutex代码实例
#import "BaseDemo.h"
@interface MutexDemo : BaseDemo
@end
#import "MutexDemo.h"
#import
@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); //传null就相当于 PTHREAD_MUTEX_DEFAULT一把普通的锁
// 销毁属性
// 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 ,PTHREAD_MUTEX_DEFAULT属性生成一把普通的锁,跟os_unfair_lock用法作用类似,有时候这种简单的锁,并不满足我们所有需求比如在锁嵌套锁的情况下,像上面的普通锁很可能会出现死锁现象,尤其是在递归调用加锁代码的时候,,如何对一把锁重复加锁,这就用我们要讲的递归锁,允许同一个线程对一把锁进行重复加锁,注意是同一个线程,不同线程是不能对同一把锁进行加锁的。如果一个线程在递归调用,那么另一个线程需要等待的。
2.4 pthread_mutex(recursive)
#import "BaseDemo.h"
@interface MutexDemoRecursive : BaseDemo
@end
#import "MutexDemoRecursive.h"
#import
@interface MutexDemoRecursive()
@property (assign, nonatomic) pthread_mutex_t mutex;
@end
@implementation MutexDemoRecursive
- (void)__initMutex:(pthread_mutex_t *)mutex
{
// 递归锁:允许同一个线程对一把锁进行重复加锁
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
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;
}
/**
线程1:otherTest(+-)
otherTest(+-)
otherTest(+-)
线程2:otherTest(等待)
*/
- (void)otherTest
{
pthread_mutex_lock(&_mutex);
NSLog(@"%s", __func__);
static int count = 0;
if (count < 10) {
count++;
[self otherTest];
}
pthread_mutex_unlock(&_mutex);
}
//- (void)otherTest2
//{
// pthread_mutex_lock(&_mutex2);
//
// NSLog(@"%s", __func__);
//
// pthread_mutex_unlock(&_mutex2);
//}
- (void)dealloc
{
pthread_mutex_destroy(&_mutex);
}
@end
递归锁就是把pthread_mutex锁类型指定为PTHREAD_MUTEX_RECURSIVE就可以了,这里要注意的递归锁是允许同一个线程对一把锁进行重复加锁,注意是同一个线程,不同线程是不能对同一把锁进行加锁的。如果一个线程在递归调用,那么另一个线程需要等待的。所以递归锁虽然能重复加锁,但不会出现多线程同步问题。
2.5 pthread_mutex条件锁
我们有线程锁嵌套的情况,那如果有一个锁需要等待另一个锁任务完成后再接着执行怎么办呢,类似于生产者-》消费者模式,那么接下来我们讲的这个条件锁就可以实现,它的常用API有:
// 初始化锁
pthread_mutex_t mutex;
// Null代表属性默认值
pthread_mutex_init(&mutex, NULL);
//初始化条件
pthread_cond_t condition;
pthread_cond_init(&condition, NULL);
//等待条件(进入睡眠,放开mutex锁,被唤醒后,会再次对mutext加锁)
pthread_cond_wait(&condition, &mutex);
//激活一个等待该条件的线程
pthread_cond_signal(&condition);
//激活所有等待该条件的线程
pthread_cond_broadcast(&condition);
//销毁资源
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&condition);
使用实例:
#import "BaseDemo.h"
@interface MutexIfDemo : BaseDemo
@end
#import "MutexIfDemo.h"
#import
@interface MutexIfDemo()
@property (assign, nonatomic) pthread_mutex_t mutex;
@property (assign, nonatomic) pthread_cond_t cond;
@property (strong, nonatomic) NSMutableArray *data;
@end
@implementation MutexIfDemo
- (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_signal(&_cond);
// 广播
// pthread_cond_broadcast(&_cond);
pthread_mutex_unlock(&_mutex);
}
- (void)dealloc
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
@end
讲完这些C语言实现的锁,我们来讲一下,OC 封装的一些锁,这些锁的大致实现源码GNUstep
2.6 NSLock
NSLock是对mutex普通锁的封装,它的常用API如下:
@interface NSLock : NSObject {
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@end
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
NSLock lock = [NSLock alloc] init]; //初始化锁
使用示例:
#import "BaseDemo.h"
@interface NSLockDemo : BaseDemo
@end
#import "NSLockDemo.h"
@interface NSLockDemo()
@property (strong, nonatomic) NSLock *ticketLock;
@property (strong, nonatomic) NSLock *moneyLock;
@end
@implementation NSLockDemo
- (instancetype)init
{
if (self = [super init]) {
self.ticketLock = [[NSLock alloc] init];
self.moneyLock = [[NSLock alloc] init];
}
return self;
}
// 死锁:永远拿不到锁,如果不解锁的话
- (void)__saleTicket
{
[self.ticketLock lock];
[super __saleTicket];
[self.ticketLock unlock];
}
- (void)__saveMoney
{
[self.moneyLock lock];
[super __saveMoney];
[self.moneyLock unlock];
}
- (void)__drawMoney
{
[self.moneyLock lock];
[super __drawMoney];
[self.moneyLock unlock];
}
@end
NSLock用法很简单,跟mutex普通锁功能一样,就是封装的它。pthread_mutex还有递归锁那OC版的是什么呢
2.7 NSRecursiveLock
NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致,其实就是一摸一样,只不过它是递归锁,跟 pthread_mutex(recursive)功能一样, NSRecursiveLock跟NSLockAPI一样那么使用demo也差不多。
@interface NSRecursiveLockDemo : BaseDemo
@end
#import "NSRecursiveLockDemo.h"
@interface NSRecursiveLockDemo()
@property (strong, nonatomic) NSRecursiveLock *recursiveLock;
@end
@implementation NSRecursiveLockDemo
- (instancetype)init
{
if (self = [super init]) {
self.recursiveLock = [[NSRecursiveLock alloc] init];
}
return self;
}
/**
线程1:otherTest(+-)
otherTest(+-)
otherTest(+-)
线程2:otherTest(等待)
*/
- (void)otherTest
{
[self.recursiveLock lock];
NSLog(@"%s", __func__);
static int count = 0;
if (count < 10) {
count++;
[self otherTest];
}
[self.recursiveLock unlock];
}
@end
pthread_mutex还有条件锁,相对应的OC也有
2.8 NSCondition
NSCondition是对mutex和cond的封装,它主要的API有:
@interface NSCondition : NSObject {
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@end
使用示例:
#import "BaseDemo.h"
@interface NSConditionDemo : BaseDemo
@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];
sleep(2);
[self.condition unlock];
}
@end
NSCondition 是有条件等待但是还不够具体,下面就有一个可以设置具体条件的锁
2.9 NSConditionLock
NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值它的API主要有
@interface NSConditionLock : NSObject {
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
- (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;
@end
使用示例:
#import "BaseDemo.h"
@interface NSConditionLockDemo : BaseDemo
@end
#import "NSConditionLockDemo.h"
@interface NSConditionLockDemo()
@property (strong, nonatomic) NSConditionLock *conditionLock;
@end
@implementation NSConditionLockDemo
- (instancetype)init
{
if (self = [super init]) {
self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];//默认值为0
}
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 lock];
NSLog(@"__one");
sleep(1);
[self.conditionLock unlockWithCondition:2];//设置内部条件值为2,并且释放这把锁
}
- (void)__two
{
[self.conditionLock lockWhenCondition:2]; //等待条件值为2时获得锁,并加锁
NSLog(@"__two");
sleep(1);
[self.conditionLock unlockWithCondition:3];//设置内部条件值为3,并且释放这把锁
}
- (void)__three
{
[self.conditionLock lockWhenCondition:3];//等待条件值为3时获得锁,并加锁
NSLog(@"__three");
[self.conditionLock unlock];
}
@end
我们可以通过[self.conditionLock lockWhenCondition:number];
设置具体的值来控制线程执行顺序。
2.10 dispatch_queue
直接使用GCD的串行队列,也是可以实现线程同步的,方法很简单直接看代码:
#import "BaseDemo.h"
@interface SerialQueueDemo : BaseDemo
@end
#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
线程同步就是为了防止多个线程同时访问一个资源,那么在串行队列同步执行的情况下也能保证同一份资源在同一时间,只能被一个线程访问。
2.11 dispatch_semaphore
semaphore叫做”信号量”
信号量的初始值,可以用来控制线程并发访问的最大数量
信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
//信号量的初始值
int value = 1;
//初始化信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(value);
//如果信号量的值<=0,当前线程就会进入休眠等待(直到信号量的值>0)
//如果信号量的值>0,就减1,然后往下执行x后面的代码
dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
//让信号量的值加1
dispatch_semaphore_signal(semaphore);
使用示例:
#import "BaseDemo.h"
@interface SemaphoreDemo : BaseDemo
@end
#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);
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
{
// 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
// 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); //forever就会一直等信号
sleep(2);
NSLog(@"test - %@", [NSThread currentThread]);
// 让信号量的值+1
dispatch_semaphore_signal(self.semaphore);
}
@end
dispatch_semaphore属于GCD里面的线程同步方案,信号量可以控制线程最大并发数量,当我们设置为1的时候,就可以做线程同步。下面我们讲最后一个线程同步方案
2.12 @synchronized
@synchronized是对mutex递归锁的封装
源码查看:objc4中的objc-sync.mm
文件
@synchronized(obj)
内部会生成obj对应的递归锁,然后进行加锁、解锁操作
@synchronized(obj) {
}
示例代码:
#import "BaseDemo.h"
@interface SynchronizedDemo : BaseDemo
@end
#import "SynchronizedDemo.h"
@implementation SynchronizedDemo
- (void)__drawMoney
{
@synchronized([self class]) {
[super __drawMoney];
}
}
- (void)__saveMoney
{
@synchronized([self class]) { // objc_sync_enter
[super __saveMoney];
} // objc_sync_exit
}
- (void)__saleTicket
{
static NSObject *lock;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
lock = [[NSObject alloc] init];
});
@synchronized(lock) {
[super __saleTicket];
}
}
- (void)otherTest
{
@synchronized([self class]) {
NSLog(@"123");
[self otherTest];
}
}
@end
@synchronized是最简单的一种实现方案,很多语言中也有,比如Java等。讲完了线程同步方案,那么我们在实际开发过程中该用那种方案呢,那么我们来对比一下他们的性能排名:
性能从高到低排序
1 os_unfair_lock
2 OSSpinLock
3 dispatch_semaphore
4 pthread_mutex
5 dispatch_queue(DISPATCH_QUEUE_SERIAL)
6 NSLock
7 NSCondition
8 pthread_mutex(recursive)
9 NSRecursiveLock
10 NSConditionLock
11 @synchronized
由上面的排序我们可知@synchronized虽然使用最简单,但是性能确实最差的,通常iOS 开发中我们建议使用os_unfair_lock,dispatch_semaphore,虽然OSSpinLock性能也很高,但是在iOS10以后他就被弃用了。到此几种线程同步方案我们都介绍完了,下面我们再补充一些其他的内容。
2.13 自旋锁、互斥锁比较
我们可以看到上面的线程同步方案不是属于自旋锁、就是属于互斥锁 ,那么我们该如何抉择使用它们呢,让我们来做一个对比,根据实际需求我们根据它们各自特点,自行选择最适合的那种
什么情况使用自旋锁比较划算?
预计线程等待锁的时间很短
加锁的代码(临界区)经常被调用,但竞争情况很少发生
CPU资源不紧张
多核处理器
什么情况使用互斥锁比较划算?
预计线程等待锁的时间较长
单核处理器
临界区有IO操作
临界区代码复杂或者循环量大
临界区竞争非常激烈
2.14 atomic
atomic用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁
可以参考源码objc4的objc-accessors.mm
它并不能保证使用属性的过程是线程安全的 ,我们看下下面使用的例子
#import
@interface Person : NSObject
@property (assign, nonatomic) int age;
@property (copy, atomic) NSString *name;
@property (strong, atomic) NSMutableArray *data;
@end
/*
nonatomic和atomic
atom:原子,不可再分割的单位
atomic:原子性
给属性加上atomic修饰,可以保证属性的setter和getter都是原子性操作,也就是保证setter和gette内部是线程同步的
// 加锁
int a = 10;
int b = 20;
int c = a + b;
// 解锁
*/
#import "Person.h"
@implementation Person
//- (void)setName:(NSString *)name
//{
// // 加锁
// _name = name;
// // 解锁
//}
//- (NSString *)name
//{
// 加锁
// return _name;
// 解锁
//}
@end
我们来看一下atomic内部实现源码:
objc-accessors.mm
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);
}
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);
}
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);
}
我们可以看到如果一个属性atomic = true他就会通过spinlock_t进行加锁,那么spinlock_t是什么我们来看一下:
using spinlock_t = mutex_tt;
class mutex_tt : nocopy_t {
os_unfair_lock mLock;
.......
}
我们可以看到atomic底层使用的是os_unfair_lock锁进行同步访问控制的。
最后我们讲下atomic并不能保证使用属性的过程是线程安全的
MJPerson *p = [[MJPerson alloc] init];
NSMutableArray *array = p.data; //线程安全
[array addObject:@"1"]; //线程不安全
[array addObject:@"2"];
[array addObject:@"3"];
atomic只能保证调用get,set方法过程是安全的,并不能保证在使用属性的时候是安全的,比如它有一个数组属性,它这个属性添加内容的时候就是不安全的。所以一般实际用的时候根据实际情况我们大都是写nonatomic,因为没有线程同步问题的时候atomic加解锁也是会损耗性能的。
2.15 iOS中的读写安全方案
思考如何实现以下场景
- 同一时间,只能有1个线程进行写的操作
- 同一时间,允许有多个线程进行读的操作
- 同一时间,不允许既有写的操作,又有读的操作
上面的场景就是典型的“多读单写”,经常用于文件等数据的读写操作,iOS中的实现方案有:
pthread_rwlock:
读写锁
dispatch_barrier_async:
异步栅栏调用
pthread_rwlock
它的等待锁的线程会进入休眠 ,常用API有如下:
//初始化锁
pthread_rwlock_t lock;
pthread_rwlock_init(&_lock, NULL);
//读数据-加锁
pthread_rwlock_rdlock(&lock);
//读数据-尝试加锁
pthread_rwlock_tryrdlock(&lock);
//写数据-加锁
pthread_rwlock_wrlock(&lock);
//写数据-尝试加锁
pthread_rwlock_trywrlock(&lock);
//解锁
pthread_rwlock_unlock(&lock);
//销毁
pthread_rwlock_destroy(&lock);
示例代码:
#import "ViewController.h"
#import
@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
{
pthread_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函数的效果,常用API如下:
dispatch_queue_t queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
//读数据
dispatch_async(self.queue, ^{
[self read];
});
//写数据
dispatch_barrier_async(self.queue, ^{
[self write];
});
使用示例:
#import "ViewController.h"
#import
@interface ViewController ()
@property (strong, nonatomic) dispatch_queue_t queue;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// queue.maxConcurrentOperationCount = 5;
// dispatch_semaphore_create(5);
self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(self.queue, ^{
[self read];
});
dispatch_async(self.queue, ^{
[self read];
});
dispatch_async(self.queue, ^{
[self read];
});
dispatch_barrier_async(self.queue, ^{
[self write];
});
}
}
- (void)read {
sleep(1);
NSLog(@"read");
}
- (void)write
{
sleep(1);
NSLog(@"write");
}
@end
栅栏方法像一个隔离门一样,一旦遇到写操作会把所有读操作隔离起来,写操作完成后,再把栅栏放开,所有读操作继续,依次类推
OK多线程的方方面面我们基本介绍完了。