多线程的概念
一个运行着的程序叫做一个进程,一个进程至少包含一个线程,线程是程序的执行流。在iOS中程序一旦启动就有一个线程一直运行,这个线程叫主线程。主线程是其他所 有线程的父线程。
系统中的所有县城共享进程的地址空间,所以能够自由的访问进程的空间变量。多线程访问的变量称之为“共享变量”。共享变量主要是“全局变量”和“静态变量”。
各个线程都有自己的栈,线程不能访问其他线程的的栈内变量。
进程是操作系统分配资源和调度的基本单位,线程是进程基本执行单位,一个进程对应多个线程。
线程又分为主线程和非主线程:
一般来说:主线程用于处理UI,所有的UI操作都要在主线程进行,耗时操作放在非主线程。
其实,CPU在一个单位时间只能处理一个线程(单CPU),但是可以通过快速的切换线程,就像同时在处理多个线程一样。
网上有一个比喻:
进程就是一列火车,线程是车厢,一列火车至少要有一节车厢,就如一个进程至少要有一个线程,车厢离开火车就无法运行,多个车厢可以提高运行效率。
多线程的目的就是提高系统的运行效率,将耗时操作都放在后台。
互斥
在多线程的环境下,不同的线程可以同时操作某个资源,例如某些线程在读取某个数,而另外一些线程在写这个数,这个时候就很容易产生难以预料的结果,使得线程不安全,解决这个问题的方式就是让线程的操作互斥,主要使用互斥锁。
NSLock
NSLock是基于信号量的,他只允许单个的线程获得并使用。
使用方法:
-(void)addNumber:(int)number{
// 初始化一个锁
NSLock *aLock = [[NSLock alloc]init];
// 加锁
[aLock lock];
number ++; // 临界区
// 解锁
[aLock unlock];
}
NSConditionLock
条件锁持有整形数值,根据这个值获得锁或者等待。
-(void)addNumber2:(int)number{
// 初始化一个锁
NSConditionLock *conditonLock = [[NSConditionLock alloc]initWithCondition:1];
// 加锁
[conditonLock lockWhenCondition:1];
number ++; // 临界区
// 解锁
[conditonLock unlockWithCondition:0];
}
NSRecrusiveLock
递归锁。某线程得到锁之后,想要获得该锁的线程就会进入休眠,使用NSLock时,如果获得锁的线程在没有释放的情况下,又再次获得这个锁,就会造成死锁。例如:
// 加锁
[aLock lock];
[aLock lock];
number ++; // 临界区
// 解锁
[aLock unlock];
[aLock unlock];
使用递归锁,同一个线程多次获得同一个锁,也不会产生死锁,只要最后获得的次数和释放的次数一致,就会释放锁。
synchronized
程序块内的代码不能被多线程同时使用。
@synchronized (objct) {
// 想要执行的代码
}
运行这一段代码时,运行时系统就创建排斥的执行该段代码的锁。
@synchronized(obj)指令使用的obj为该锁的唯一标识,只有当标识相同时,才为满足互斥, 如果线程2中的@synchronized(obj)改为@synchronized(self),那么线程2就不会被阻塞。
线程的状态、转换和生命周期
线程的生命周期包括:新建–就绪–运行–阻塞–死亡,他们之间的转化如下图所示:
新建:实例化对象
就绪:调用线程对象的start函数,线程被假如到可调度池,等待CPU调度
运行:CPU调度可调度池的线程。线程执行的过程中,可能会在就绪和运行状态之间切换。这些都是由CPU控制的。
阻塞:在满足某中条件的时候,正在运行的线程会从运行状态转化成阻塞状态,主要包括:sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥锁)
死亡:线程运行完,正常结束。第二种是满足某种条件的时候,线程内部终止。
四中多线程方案
包括pthread、NSthread、GCD、NSOperation
借用网友的一张图片进行概述:
多线程安全
在多线程环境下,如果多个线程同时对某个资源进行读写操作,可能会导致数据错乱,出现难以预料的结果。
解决这个问题的方式就是给线程加锁。
(1)互斥锁
前面已经说过了,包括NSLock、NSConditionLock、@synchronized.
(2) 自旋锁
加了自旋锁,当新线程当问加锁的代码时,如果发现该段代码已经加锁,就会一直的等待这段代码执行完毕,解锁。相当于一直尝试执行这段代码,很耗性能。
atomic和nonatomic
定义属性的时候经常用到。
noatomic:非原子属性,同时可被多个线程读写,线程不安全,效率高 atomic:原子性,线程安全,同时只能被一个线程访问。
被atomic修饰的属性,会自动的在set方法中调用@synchronized(self)。所以效率很低,因为其实@synchronized会将他修饰的代码块加入到一个队列中,而同一时间,线程只能执行队列中的一个代码块,所以效率很低。
NSThread
NSThread创建
(1)初始化方式,然后调用start函数
// 创建一个线程,处于创建状态
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(test) object:nil];
// 调用线程的start方法,线程处于可运行状态,被加入到可调度线程池等待CPU的调度。
[thread start];
// 创建一个线程,处于创建状态
NSThread *thread2 = [[NSThread alloc]initWithBlock:^{
// 线程中要运行的代码
}];
// 调用线程的start方法,线程处于可运行状态,被加入到可调度线程池等待CPU的调度。
[thread2 start];
(2)类方法创建线程
2.1 detachNewThreadSelector
这个类方法,直接创建好线程,并且将线程放到可调度池,线程自动启动。
// 线程创建好之后,自动启动
[NSThread detachNewThreadSelector:@selector(test) toTarget:self withObject:nil];
[NSThread detachNewThreadWithBlock:^{
// 线程创建好之后,自动启动
}];
2.2 detachNewThreadSelector
这个类方法,直接创建好线程,并且将线程放到可调度池,线程自动启动。
// 线程创建好之后,自动启动
[NSThread detachNewThreadSelector:@selector(test) toTarget:self withObject:nil];
[NSThread detachNewThreadWithBlock:^{
// 线程创建好之后,自动启动
}];
2.3 performSelectorInBackground
这个方法现在用的不多,隐式创建一个线程,直接启动
// 隐式创建,现在用的不多,直接创建好就启动
[self performSelectorInBackground:@selector(test) withObject:nil];
NSThread类方法
(1)[NSThread exit]; 终止线程
(2)[NSThread currentThread]; 返回当前线程的线程号和名字,线程号1表示主线程。
(3)[NSThread sleepForTimeInterval:10]; 线程休眠10秒
(4)[NSThread sleepUntilDate:[NSData data]]; 线程休眠到某个指定时间
(5)[NSThread isMainThread]; 当前线程是不是主线程
(6)[NSThread isMultiThreaded]; 当前情况是都存在多线程
(7)NSThread *mainThread = [NSThread mainThread]; 取得主线程
NSThread属性
(1)thread.isExecuting; 线程是否在执行
(2)thread.isCancelled; 线程是否被取消
(3)thread.isFinished; 线程是否完成
(4)thread.isMainThread; 是否是主线程
(5)thread.threadPriority; 线程的优先级,取值范围0.0到1.0,默认优先级0.5,1.0表示最高优先级,优先级高,CPU调度的频率高
GCD
GCD是异步执行任务的技术之一,开发者只需要定义要执行的任务,然后追加这些任务到GCD的队列,GCD就能生成必要的线程执行相应的任务。
线程作为管理作为系统的一部分,深入内核,所以效率非常高。
GCD中的概念说明
(1)任务
任务就是程序员想要完成的操作或者功能,在GCD中,我们一般都将任务封装成一个block(也就是一个匿名函数,或者理解成代码段)。然后程序员自行决定添加到串行队列或者并行队列,程序员的工作就完成了,接着等待CPU的调度。
(2)同步(sync)和异步(asycn)
同步和异步决定了是否要新开线程。
同步:不开新的线程,任务一个执行完之后执行队列中的下一个任务;
异步:开启新的线程,多个任务可以在多个线程中一起执行;
(3)串行队列和并行队列
串行队列:我理解的是任务像珠子一样被穿在一起,只有一个一个的顺序执行
并行队列:队列中的任务会被多个线程同时执行
(4)主队列和全局队列
主队列:一个应用中只有一个主队列,是串行队列
全局队列:并行队列,系统分配,无需创建
我们在使用GCD的时候,只需要将任务添加到队列,然后指定任务执行的方式是同步还是异步,就OK了。
队列的创建
(1)并行队列
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
(2)串行队列
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
全局队列和主队列不能创建,只能取得。
(3)全局队列
全局队列的优先级包括: #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高优先级 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)优先级 #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低优先级 #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台优先级
// 第一个参数是优先级
//iOS8开始使用服务质量,现在获取全局并发队列时,可以直接传0
dispatch_queue_t globleQueue = dispatch_get_global_queue(0, 0);
(4)主队列
主队列一般只能用于更新UI
dispatch_queue_t mainQueue = dispatch_get_main_queue();
同步和异步任务的创建
- 同步的创建
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
// 任务
NSLog(@"dslfhl");
});
- 异步的创建
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 任务
NSLog(@"dslfhl");
});
GCD中同步/异步和并行/串行的组合
一共有四种队列和两种执行方式,可以进行组合:
- 串行同步
只要有同步就不会产生新的线程,串行会一个一个顺序执行,因此串行同步不产生新线程,在当前线程中顺序执行。
例子如下:
// 创建串行队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"串行同步--任务1--线程是:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"串行同步--任务2--线程是:%@",[NSThread currentThread]);
});
输出结果是:
2017-08-16 17:08:59.912 TestNew[2972:1293325] 串行同步--任务1--线程是:<NSThread: 0x608000261dc0>{number = 1, name = main}
2017-08-16 17:08:59.912 TestNew[2972:1293325] 串行同步--任务2--线程是:<NSThread: 0x608000261dc0>{number = 1, name = main}
2017-08-16 17:08:59.912 TestNew[2972:1293325] 串行同步--任务3--线程是:<NSThread: 0x608000261dc0>{number = 1, name = main}
观察结果可以发现,虽然我们创建了一个线程,但是最后还是使用的主线程,没有创建新的线程,并且顺序执行。
- 串行异步
串行队列中的任务只能一个一个执行,而异步可以开启多个线程。所以串行异步的结果是开启新的线程,任务顺序执行
// 创建串行队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"串行异步--任务1--线程是:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"串行异步--任务2--线程是:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"串行异步--任务3--线程是:%@",[NSThread currentThread]);
});
输出结果:
2017-08-16 17:15:49.653 TestNew[3051:1325440] 串行异步--任务1--线程是:<NSThread: 0x60800006ed80>{number = 3, name = (null)}
2017-08-16 17:15:49.654 TestNew[3051:1325440] 串行异步--任务2--线程是:<NSThread: 0x60800006ed80>{number = 3, name = (null)}
2017-08-16 17:15:49.654 TestNew[3051:1325440] 串行异步--任务3--线程是:<NSThread: 0x60800006ed80>{number = 3, name = (null)}
可以看到创建了一个number = 3的新线程,任务顺序执行。
- 并行同步
同步表示不开启新的线程,因此执行结果是任务顺序执行,不开启新的线程
// 创建并行队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"并行同步--任务1--线程是:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"并行同步--任务2--线程是:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"并行同步--任务3--线程是:%@",[NSThread currentThread]);
});
输出结果是:
2017-08-16 17:25:46.613 TestNew[3142:1360637] 并行同步--任务1--线程是:<NSThread: 0x60800006d280>{number = 1, name = main}
2017-08-16 17:25:46.613 TestNew[3142:1360637] 并行同步--任务2--线程是:<NSThread: 0x60800006d280>{number = 1, name = main}
2017-08-16 17:25:46.613 TestNew[3142:1360637] 并行同步--任务3--线程是:<NSThread: 0x60800006d280>{number = 1, name = main}
- 并行异步
并行表示多个任务可以同时被执行,异步会开启新的线程,所以并行异步会开启多线程,并且异步执行
// 创建并行队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"并行异步--任务1--线程是:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"并行异步--任务2--线程是:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"并行异步--任务3--线程是:%@",[NSThread currentThread]);
});
输出结果是:
2017-08-16 19:54:07.557 TestNew[3498:1450488] 并行异步--任务2--线程是:<NSThread: 0x60800007a100>{number = 4, name = (null)}
2017-08-16 19:54:07.557 TestNew[3498:1450485] 并行异步--任务3--线程是:<NSThread: 0x60000007dd00>{number = 5, name = (null)}
2017-08-16 19:54:07.557 TestNew[3498:1450517] 并行异步--任务1--线程是:<NSThread: 0x60000007e080>{number = 3, name = (null)}
可以看到开启了多个线程,任务执行顺序也不一定。
- 主队列同步
主队列本来就是一个串行队列,而同步不开启新的线程,正好这个组合就会发生死锁,程序会崩溃
// 取得主队列(穿行队列)
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"主队列同步--任务1--线程是:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"主队列同步--任务2--线程是:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"主队列同步--任务3--线程是:%@",[NSThread currentThread]);
});
输出结果是:程序崩溃
为什么会崩溃呢?
搜了一下,网上说
// 取得主队列(穿行队列)
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"主队列同步--任务1--线程是:%@",[NSThread currentThread]);
});
dispatch_sync这个函数正在主线程中执行,然后又在主队列中加入了block这个任务,dispatch_sync要等待block执行完再返回,而block又要等 dispatch_sync执行完,再顺序的执行,所以形成了死锁。
- 主队列异步
主队列是串行队列,因此顺序执行,异步开启多线程,但是同一时间只能执行一个任务,因此任务在主队列中顺序执行。
这里不阻塞的原因是:asych函数添加了block就返回,不会等待block执行完再返回
// 取得主队列(穿行队列)
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"主队列同步--任务1--线程是:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"主队列同步--任务2--线程是:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"主队列同步--任务3--线程是:%@",[NSThread currentThread]);
});
输出结果是:
2017-08-16 20:41:34.006 TestNew[3937:1710027] 主队列同步--任务1--线程是:<NSThread: 0x60000007e180>{number = 1, name = main}
2017-08-16 20:41:34.006 TestNew[3937:1710027] 主队列同步--任务2--线程是:<NSThread: 0x60000007e180>{number = 1, name = main}
2017-08-16 20:41:34.007 TestNew[3937:1710027] 主队列同步--任务3--线程是:<NSThread: 0x60000007e180>{number = 1, name = main}
至此,我们已经把同步异步、并行队列串行队列的组合说完了。
GCD线程通信
在实际开发中,我们会将耗时的操作在后台线程中进行,例如下载图片,图片下载完成后,后台线程就应该通知主线程去更新页面了。这个时候就要用到线程之间的通信。
线程中的通信主要是:
(1)一个线程传递数据给另一线程;
(2)一个线程执行完任务后,通知另一个线程开始执行
下面举例,线程之间的通信:
UIImageView *iamgeView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 64, [UIScreen mainScreen].bounds.size.width, 200)];
[self.view addSubview:iamgeView];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//开启多线程下载图片
NSURL *url = [[NSURL alloc]initWithString:@"https://www.baidu.com/img/bd_logo1.png"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 图片下载太快,睡一会
[NSThread sleepForTimeInterval:2];
// 图片下载完毕,在主线程跟新UI
dispatch_async(dispatch_get_main_queue(), ^{
iamgeView.image = image;
});
});
这段代码先开一个线程下载图片,下载好之后,睡两秒,通知主线程进行UI跟新。
dispatch_after
上面的例子,我们使用了sleep让当前线程休眠2S,我们也可以使用dispatch_after。
需要注意的是dispatch_after并不是2S之后执行任务,而是2S之后将任务加入到队列中。如果有严格的时间要求,就不能用这个方法,大概的时间可以使用这个方法。
UIImageView *iamgeView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 64, [UIScreen mainScreen].bounds.size.width, 200)];
[self.view addSubview:iamgeView];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//开启多线程下载图片
NSURL *url = [[NSURL alloc]initWithString:@"https://www.baidu.com/img/bd_logo1.png"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 图片下载太快,睡一会
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 5ull *NSEC_PER_SEC);
dispatch_after(time,dispatch_get_main_queue(), ^{
iamgeView.image = image;
});
ull是C语言的数值字面量,显示的表明类型时使用的字符串(unsigned long long)。
在上面的例子中,主线程是在主线程的runloop中执行,假如这个runloop每隔1/50秒循环一次,那么这个block最少在3秒后执行,最多再3秒+1/50秒后执行。
dispatch_barrier_async GCD栅栏
dispatch_barrier_async一定是要和并行队列一起使用的。当队列中的任务需要分成两组,而第二组需要在第一组完成之后在进行,那么就可以使用dispacth_barrier_async.
举个例子,这次我们要读取两张图片,然后再去计算图片的大小,很明显,我们应该在把下载图片这两个任务完成,再去完成计算图片大小
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
//下载第一张图片
// 异步执行
dispatch_async(queue, ^{
NSLog(@"栅栏:并发异步--下载第一张图片 %@",[NSThread currentThread]);
NSURL *url = [[NSURL alloc]initWithString:@"https://www.baidu.com/img/bd_logo1.png"];
NSData *data = [NSData dataWithContentsOfURL:url];
_firstImage = [UIImage imageWithData:data];
});
//下载第二张图片
// 异步执行
dispatch_async(queue, ^{
NSLog(@"栅栏:并发异步--下载第二张图片 %@",[NSThread currentThread]);
NSURL *url = [[NSURL alloc]initWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1502964291259&di=e24fedeaab6ba7342deb01d83c24f627&imgtype=0&src=http%3A%2F%2Fimg1.cache.netease.com%2Fcatchpic%2FE%2FED%2FED1C89F9BBADDC32C1283530A47E910B.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
_secondImage = [UIImage imageWithData:data];
});
// 栅栏
dispatch_barrier_async(queue, ^{
NSLog(@"-----------------栅栏------------------");
NSLog(@"栅栏: %@",[NSThread currentThread]);
});
// 第二部分任务
dispatch_async(queue, ^{
NSLog(@"模拟计算图片大小");
NSLog(@"模拟计算图片大小的线程: %@",[NSThread currentThread]);
});
输出结果是:
2017-08-17 15:17:12.800 TestNew[2374:764761] 栅栏:并发异步--下载第二张图片 <NSThread: 0x60000007e580>{number = 4, name = (null)}
2017-08-17 15:17:12.800 TestNew[2374:764762] 栅栏:并发异步--下载第一张图片 <NSThread: 0x60000007e4c0>{number = 3, name = (null)}
2017-08-17 15:17:13.222 TestNew[2374:764761] -----------------栅栏------------------
2017-08-17 15:17:13.222 TestNew[2374:764761] 栅栏: <NSThread: 0x60000007e580>{number = 4, name = (null)}
2017-08-17 15:17:13.223 TestNew[2374:764761] 模拟计算图片大小
2017-08-17 15:17:13.223 TestNew[2374:764761] 模拟计算图片大小的线程: <NSThread: 0x60000007e580>{number = 4, name = (null)}
上面的例子中,我们只用的自己创建的并行队列,那么如果使用globalQueue呢?有人觉得global-queue也是一个并行队列。
//下载第一张图片
// 异步执行
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"栅栏:并发异步--下载第一张图片 %@",[NSThread currentThread]);
NSURL *url = [[NSURL alloc]initWithString:@"https://www.baidu.com/img/bd_logo1.png"];
NSData *data = [NSData dataWithContentsOfURL:url];
_firstImage = [UIImage imageWithData:data];
});
//下载第二张图片
// 异步执行
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"栅栏:并发异步--下载第二张图片 %@",[NSThread currentThread]);
NSURL *url = [[NSURL alloc]initWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1502964291259&di=e24fedeaab6ba7342deb01d83c24f627&imgtype=0&src=http%3A%2F%2Fimg1.cache.netease.com%2Fcatchpic%2FE%2FED%2FED1C89F9BBADDC32C1283530A47E910B.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
_secondImage = [UIImage imageWithData:data];
});
// 栅栏
dispatch_barrier_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"-----------------栅栏------------------");
NSLog(@"栅栏: %@",[NSThread currentThread]);
});
// 第二部分任务
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"模拟计算图片大小");
NSLog(@"模拟计算图片大小的线程: %@",[NSThread currentThread]);
});
输出结果是:
2017-08-17 15:21:18.602 TestNew[2436:789411] 模拟计算图片大小
2017-08-17 15:21:18.602 TestNew[2436:789424] -----------------栅栏------------------
2017-08-17 15:21:18.602 TestNew[2436:789408] 栅栏:并发异步--下载第二张图片 <NSThread: 0x60000026b640>{number = 4, name = (null)}
2017-08-17 15:21:18.602 TestNew[2436:789409] 栅栏:并发异步--下载第一张图片 <NSThread: 0x608000269240>{number = 3, name = (null)}
2017-08-17 15:21:18.602 TestNew[2436:789411] 模拟计算图片大小的线程: <NSThread: 0x60000026ba80>{number = 5, name = (null)}
2017-08-17 15:21:18.602 TestNew[2436:789424] 栅栏: <NSThread: 0x608000269a00>{number = 6, name = (null)}
很不幸,并没有按照我们的预想,所以我们可以说使用栅栏一定要用自己创建的并行队列。
综上,栅栏在访问数据库时,可以让读写分开,让读操作并行,在栅栏中进行写操作,这样会大大的提高数据库的访问速度。
dispatch_apply GCD快速迭代
dispatch_apply将一个相同的任务按指定的数量加入到队列中,并等待这些任务全部执行完毕。
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"%zu",index);
});
输出结果是:
2017-08-17 15:34:04.028 TestNew[2520:827569] 0
2017-08-17 15:34:04.028 TestNew[2520:827737] 1
2017-08-17 15:34:04.028 TestNew[2520:827736] 3
2017-08-17 15:34:04.028 TestNew[2520:827743] 2
2017-08-17 15:34:04.029 TestNew[2520:827569] 4
2017-08-17 15:34:04.029 TestNew[2520:827737] 5
2017-08-17 15:34:04.029 TestNew[2520:827736] 6
2017-08-17 15:34:04.029 TestNew[2520:827569] 8
2017-08-17 15:34:04.029 TestNew[2520:827743] 7
2017-08-17 15:34:04.029 TestNew[2520:827737] 9
dispatch_once 执行一次
保证任务只被执行一次。可以用于设计单例。
static dispatch_once_t once;
dispatch_once(&once, ^{
NSLog(@"只执行一次");
});
变更已生成队列的优先级 dispatch_set_target_queue
我们用dispatch_queue_creat生成的队列都是默认优先级,与global_dispatch_queue的优先级一样,我们可以使用dispatch_set_target_queue变更队列的优先级。
// 需要改变优先级的队列
dispatch_queue_t myQueue = dispatch_queue_create("queuq", DISPATCH_QUEUE_CONCURRENT);
// 作为参数的global_queue,用这个global_queue的优先级去赋值给myQueue
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
dispatch_set_target_queue(myQueue, globalQueue);
dispatch_suspend/dispatch_resume
当追加了大量的任务到queue中时,在追加的过程中,希望不执行刚追加到队列中的任务,就可以先将队列挂起
dispatch_suspend(myQueue);
之后就可以恢复:
dispatch_resume(myQueue);
以下还有两个GCD中比较重要的概念
dispatch_group 队列组
我们异步执行了几个耗时的操作,然后在这些操作都完成之后,我们想执行最后的处理。例如我们有数据需要计算,在这些数据都计算请求完成之后,进行求和。我们就可以使用队列组。
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"-------任务1--------");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"-------任务2--------");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"-------任务3--------");
});
// 三个任务都执行完毕之后,进行下一步
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"---------执行完任务1、2、3之后,执行此任务");
});
执行结果如下:
2017-08-17 16:53:39.392 TestNew[2997:1105112] -------任务3--------
2017-08-17 16:53:39.392 TestNew[2997:1105109] -------任务2--------
2017-08-17 16:53:41.397 TestNew[2997:1105110] -------任务1--------
2017-08-17 16:53:41.398 TestNew[2997:1105110] ---------执行完任务1、2、3之后,执行此任务
尽管第一个任务休眠了2秒,但是最后一个任务依然会等待第一个任务完成再进行。
dispatch_semaphore GCD信号量
如果我们需要等到两个任务完成再去完成一个任务,其实我们用前面的dispatch_group就可以轻松的完成,但是加入我们的前两个任务是通过网络去获取数据,然后再执行第三个任务,这个时候就会出问题,因为我们将网络请求的代码放在一个block中作为一个任务,这个任务在网络连接时,又会开启新的线程。而任务1和任务2会继续往下执行,不会等待网络请求完成。所以我们需要细粒度更高的信号量去完成这个功能。
我们先看看与dispatch_semaphore相关的三个方法:
// 创建一个信号量,初始值是1
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
//等待semaphore的值>=1,否则一直等待,或者semaphore的值大于1时,进行-1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//将semaphore的值+1
dispatch_semaphore_signal(semaphore);
如果semaphore计数大于等于1,计数-1,返回,程序继续运行。如果计数为0,则等待。
假如我们不用dispatch_semaphore,就会出现在网络请求还未完成的时候就去进行最后的处理:
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 1 获得管理者
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSString *urlString = @"https://xxxxx.us/api/v1/banners";
[manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"任务1---取得网络数据成功----%@",[NSThread currentThread]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"任务1---取得网络数据失败----%@",[NSThread currentThread]);
}];
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSString *strUrl = @"https://xxxxx.us/api/v1/articles/with_position?page=1&position=home_bottom";
[manager GET:strUrl parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"任务2---取得网络数据成功----%@",[NSThread currentThread]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"任务2---取得网络数据成功----%@",[NSThread currentThread]);
}];
});
// 三个任务都执行完毕之后,进行下一步
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"执行完任务1和2,执行最后一个任务----%@",[NSThread currentThread]);
});
输出结果为:
2017-08-17 19:20:28.099 TestNew[3612:1358331] 执行完任务1和2,执行最后一个任务----<NSThread: 0x60800006c780>{number = 3, name = (null)}
2017-08-17 19:20:31.119 TestNew[3612:1357843] 任务1---取得网络数据成功----<NSThread: 0x608000063540>{number = 1, name = main}
2017-08-17 19:20:31.805 TestNew[3612:1357843] 任务2---取得网络数据成功----<NSThread: 0x608000063540>{number = 1, name = main}
可以看到耗时操作在未完成的情况下,就执行了最后一个操作,这就是前面所说的,网络请求又新开了一个线程。
下面利用dispatch_semaphore来解决这个问题:
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 1 获得管理者
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSString *urlString = @"https://XXXX.us/api/v1/banners";
// 创建信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
// 信号量+1,此时信号量>=1,继续执行
dispatch_semaphore_signal(semaphore);
NSLog(@"任务1---取得网络数据成功----%@",[NSThread currentThread]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// 信号量+1,此时信号量>=1,继续执行
dispatch_semaphore_signal(semaphore);
NSLog(@"任务1---取得网络数据失败----%@",[NSThread currentThread]);
}];
// 如果没有请求成功,就一直等待,此时semaphore信号量是0
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSString *strUrl = @"https://XXXX.us/api/v1/articles/with_position?page=1&position=home_bottom";
// 创建信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[manager GET:strUrl parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
// 信号量+1,此时信号量>=1,继续执行
dispatch_semaphore_signal(semaphore);
NSLog(@"任务2---取得网络数据成功----%@",[NSThread currentThread]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// 信号量+1,此时信号量>=1,继续执行
dispatch_semaphore_signal(semaphore);
NSLog(@"任务2---取得网络数据成功----%@",[NSThread currentThread]);
}];
// 如果没有请求成功,就一直等待,此时semaphore信号量是0
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
// 三个任务都执行完毕之后,进行下一步
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"执行完任务1和2,执行最后一个任务----%@",[NSThread currentThread]);
});
输出结果为:
2017-08-17 19:48:26.303 TestNew[3787:1494537] 任务2---取得网络数据成功----<NSThread: 0x60800007e900>{number = 1, name = main}
2017-08-17 19:48:26.952 TestNew[3787:1494537] 任务1---取得网络数据成功----<NSThread: 0x60800007e900>{number = 1, name = main}
2017-08-17 19:48:26.953 TestNew[3787:1494989] 执行完任务1和2,执行最后一个任务----<NSThread: 0x60800026a540>{number = 5, name = (null)}
此时就可以争取的输出了。
至此,GCD的主要功能就完了。
NSOperation
NSOperation是对GCD的封装,需要和NSOperationQueue配合实现多线程。
NSOperation使用步骤
(1)创建一个任务(操作)NSOperation
(2)创建一个队列(主队列、其他队列)NSOperationQueue
(3)将操作放到队列中
NSOperation
NSOperation一共有三种:
(1)NSInvocationOperation,创建后,需要start
(2)NSBlockOperation
(3)自定义的NSOperation
NSOperationQueue
一共有两种NSOperationQueue:
(1)主队列 :主队列只能串行,并且在主线程上运行。
(2)非主队列:既包括串行队列,又包括并行队列
需要注意的是:
(1)非主队列有一个属性maxConcurrentOperationCount(最大并发数),默认为-1.默认开启多线程
(2)人为设置maxConcurrentOperationCount=1,不开启多线程,任务顺序串行执行
(3)人为设置人为设置maxConcurrentOperationCount>1,开启多线程
(4)并不是人为设置maxConcurrentOperationCount越大越好,系统会调整。
NSInvocationOperation/NSBlockOperation + 非主队列
开启新的线程,异步并发。
// 创建NSInvocationOperation
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
// 创建NSInvocationOperation
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation + 非主队列---%@",[NSThread currentThread]);
}];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:operation];
[queue addOperation:blockOperation];
输出结果为:
2017-08-17 20:54:18.338 TestNew[4027:1658946] NSInvocationOperation + 非主队列---<NSThread: 0x608000079400>{number = 4, name = (null)}
2017-08-17 20:54:18.338 TestNew[4027:1658948] blockOperation + 非主队列---<NSThread: 0x6080000793c0>{number = 3, name = (null)}
NSBlockOperation + addExecutionBlock方法
NSBlockOperation有一个addExecutionBlock方法,会让开启多线程,但是初始化NSBlockOperation的任务必须在主线程完成
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation + 主队列---%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"blockOperation + 非主队列---%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"blockOperation + 非主队列---%@",[NSThread currentThread]);
}];
[blockOperation start];
输出结果为:
2017-08-17 21:00:27.138 TestNew[4089:1692473] blockOperation + 主队列---<NSThread: 0x600000069f00>{number = 1, name = main}
2017-08-17 21:00:27.138 TestNew[4089:1692947] blockOperation + 非主队列---<NSThread: 0x600000074f80>{number = 4, name = (null)}
2017-08-17 21:00:27.138 TestNew[4089:1692965] blockOperation + 非主队列---<NSThread: 0x60000006d140>{number = 3, name = (null)}
继承NSOperation
NSOperation的第三种实现方式,继承NSOperation,复写他的main函数;
DHOperation.m 代码如下:
#import "DHOperation.h"
@implementation DHOperation
-(void)main{
NSLog(@"自定义NSOperation-----%@",[NSThread currentThread]);
}
使用NSOperation代码如下:
// 创建NSInvocationOperation
DHOperation *operation1 = [[DHOperation alloc]init];
// 创建NSInvocationOperation
DHOperation *operation2 = [[DHOperation alloc]init];
// 创建NSInvocationOperation
DHOperation *operation3 = [[DHOperation alloc]init];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
运行结果如下:
2017-08-19 15:21:27.976 TestNew[1277:200770] 自定义NSOperation-----<NSThread: 0x608000267cc0>{number = 3, name = (null)}
2017-08-19 15:21:27.980 TestNew[1277:200801] 自定义NSOperation-----<NSThread: 0x600000269000>{number = 5, name = (null)}
2017-08-19 15:21:27.980 TestNew[1277:200773] 自定义NSOperation-----<NSThread: 0x600000264940>{number = 4, name = (null)}
最大并发数实现串行
前面我们说了,队列有一个最大并发数,如果我们把这个并发数设置为1,就可以让队列中的任务一个一个顺序执行,也就是串行。
// 创建NSInvocationOperation
NSInvocationOperation *operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
// 创建NSInvocationOperation
NSInvocationOperation *operation2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
// 创建NSInvocationOperation
NSInvocationOperation *operation3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
queue.maxConcurrentOperationCount = 1;
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
test函数为:
-(void)test{
NSLog(@"NSInvocationOperation + 非主队列---%@",[NSThread currentThread]);
}
输出结果为:
2017-08-19 14:55:06.689 TestNew[1128:130681] NSInvocationOperation + 非主队列---<NSThread: 0x600000069680>{number = 3, name = (null)}
2017-08-19 14:55:06.692 TestNew[1128:130681] NSInvocationOperation + 非主队列---<NSThread: 0x600000069680>{number = 3, name = (null)}
2017-08-19 14:55:06.693 TestNew[1128:130681] NSInvocationOperation + 非主队列---<NSThread: 0x600000069680>{number = 3, name = (null)}
NSOperation操作的依赖
试想一个情况,一个操作,需要在前一个操作的数据,就必须等前一个操作取得数据后再进行本操作,那么操作2就必须依赖于操作1.NSOperation提供了依赖这个方法来实现这个功能。
GCD和NSOperation的关系
(1)GCD的底层是语言API构成的,而NSOperation是OC对象构成的。GCD中任务是在block 中构成,NSOperation直接是一个对象构成;
(2) GCD中无法停止加入到队列中的任务,NSOperation中的任务可以停止;
(3)KVO可以应用在NSOperation中,可以监听一个operation是否执行完毕或者取消;
(4)NSOperation可以设置优先级,可以让同一个队列中的任务先后执行,而GCD只能区别队列的优先级;
(5)可以继承NSOperation。添加更多的属性,增加灵活度。