同步、异步、串行、并行的概念
同步/异步:指的是能否开启新的线程,同步不能开启新的线程,异步可以。
串行/并行:指的是任务的执行方式,串行是指有多个任务时,各个任务按顺序执行,完成一个之后才能进行下一个。并行指的是多个任务可以同时执行。
异步是多个任务并行的前提条件。
名称 | 特点 |
---|---|
同步执行 | 不具备开启新线程的能力, 任务创建后要执行完才能继续往下走 |
异步执行 | 具备开启新线程的能力, 任务创建后可以先绕过,然后再执行 |
串行队列 | 队列中的任务要按顺序执行 |
并行队列 | 队列中的任务同时执行 |
线程、任务、队列的概念
名称 | 特点 |
---|---|
线程 | 程序执行任务的最小调度单位 |
任务 | 说白了就是一段代码,在GCD中, 任务就是Block中要执行的内容 |
队列 | 用来存放“任务”的一个数组 |
所有组合
并行队列 | 串行队列 | 主队列 | |
---|---|---|---|
异步执行 | 开启多个新线程,任务同时执行 | 开启一个新线程,任务按顺序执行 | 不开启新的线程,任务按顺序执行 |
同步执行 | 不开启新线程,任务按顺序执行 | 不开启新线程,任务按顺序执行 | 死锁 |
死锁:两个(多个)线程都要等待对方完成某个操作才能进行下一步,这时就会发生死锁。
代码编程实现
获取队列(三种方式)
1、自定义队列
//自定义并行队列
-(dispatch_queue_t)createConcurrentQueue{
dispatch_queue_t queue = dispatch_queue_create("LN_Concurrent", DISPATCH_QUEUE_CONCURRENT);
return queue;
}
//自定义串行队列
-(dispatch_queue_t)createSerialQueue{
dispatch_queue_t queue = dispatch_queue_create("LN_Serial", DISPATCH_QUEUE_SERIAL);
return queue;
}
2、主线程串行队列
//获取主线程串行队列
-(dispatch_queue_t)getMainSerialQueue{
dispatch_queue_t queue = dispatch_get_main_queue();
return queue;
}
3、全局并发队列
//获取全局并发队列
-(dispatch_queue_t)getGlobalConcurrentQueue{
/*
* 第一个参数:优先级别
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_GACKGROUND
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
return queue;
}
为队列添加任务(两种方式)
1、异步添加任务
//异步
-(void)addTaskWithAsyncInQueue:(dispatch_queue_t)queue{
dispatch_async(queue, ^{
NSLog(@"任务1开始");
sleep(5);
NSLog(@"任务1结束");
});
dispatch_async(queue, ^{
NSLog(@"任务2开始");
sleep(2);
NSLog(@"任务2结束");
});
dispatch_async(queue, ^{
NSLog(@"任务3开始");
sleep(1);
NSLog(@"任务3结束");
});
}
2、同步添加任务
-(void)addTaskWithSyncInQueue:(dispatch_queue_t)queue{
dispatch_sync(queue, ^{
NSLog(@"任务1开始");
sleep(5);
NSLog(@"任务1结束");
});
dispatch_sync(queue, ^{
NSLog(@"任务2开始");
sleep(2);
NSLog(@"任务2结束");
});
dispatch_sync(queue, ^{
NSLog(@"任务3开始");
sleep(1);
NSLog(@"任务3结束");
});
}
组合执行
(一)异步+并行
//异步+并行
-(void)lnAsyncConcurrent{
dispatch_queue_t queue = [self createConcurrentQueue];
NSLog(@"======start=====");
[self addTaskWithAsyncInQueue:queue];
NSLog(@"======end=====");
}
执行输出结果:
2018-04-17 14:28:03.797234+0800 ThreadProject[1708:124655] ======start=====
2018-04-17 14:28:03.797451+0800 ThreadProject[1708:124655] ======end=====
2018-04-17 14:28:03.797510+0800 ThreadProject[1708:124714] 任务1开始
2018-04-17 14:28:03.797512+0800 ThreadProject[1708:124711] 任务3开始
2018-04-17 14:28:03.797512+0800 ThreadProject[1708:124713] 任务2开始
2018-04-17 14:28:04.802118+0800 ThreadProject[1708:124711] 任务3结束
2018-04-17 14:28:05.799360+0800 ThreadProject[1708:124713] 任务2结束
2018-04-17 14:28:08.801884+0800 ThreadProject[1708:124714] 任务1结束
在代码的任务3中设置断点,查看线程数
总结:
- 开了三个新线程
- 函数在执行时,先打印了start和end,再回头执行这三个任务
这是异步执行的结果,异步执行会开启新线程,任务可以先绕过不执行,回头再来执行。
- 三个任务同时开始
这是并发的结果
(二)异步+串行
//异步+串行
-(void)lnAsyncSerial{
dispatch_queue_t queue = [self createSerialQueue];
NSLog(@"======start=====");
[self addTaskWithAsyncInQueue:queue];
NSLog(@"======end=====");
}
执行输出结果:
2018-04-17 15:35:17.971527+0800 ThreadProject[2071:164583] ======start=====
2018-04-17 15:35:17.971778+0800 ThreadProject[2071:164583] ======end=====
2018-04-17 15:35:17.971823+0800 ThreadProject[2071:164636] 任务1开始
2018-04-17 15:35:22.974270+0800 ThreadProject[2071:164636] 任务1结束
2018-04-17 15:35:22.974649+0800 ThreadProject[2071:164636] 任务2开始
2018-04-17 15:35:24.978868+0800 ThreadProject[2071:164636] 任务2结束
2018-04-17 15:35:24.979185+0800 ThreadProject[2071:164636] 任务3开始
2018-04-17 15:35:25.983574+0800 ThreadProject[2071:164636] 任务3结束
总结:相比异步+并行,这个的任务执行顺序是一个一个来的,上一个任务结束了才开始下一个
任务。
这是串行的结果
(三)异步+主队列
//异步+主队列
-(void)lnAsyncMain{
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(@"======start=====");
[self addTaskWithAsyncInQueue:queue];
NSLog(@"======end=====");
}
执行输出结果:
2018-04-17 15:41:25.099769+0800 ThreadProject[2071:164583] ======start=====
2018-04-17 15:41:25.099955+0800 ThreadProject[2071:164583] ======end=====
2018-04-17 15:41:25.101869+0800 ThreadProject[2071:164583] 任务1开始
2018-04-17 15:41:30.103308+0800 ThreadProject[2071:164583] 任务1结束
2018-04-17 15:41:30.103591+0800 ThreadProject[2071:164583] 任务2开始
2018-04-17 15:41:32.104805+0800 ThreadProject[2071:164583] 任务2结束
2018-04-17 15:41:32.105079+0800 ThreadProject[2071:164583] 任务3开始
2018-04-17 15:42:34.792503+0800 ThreadProject[2071:164583] 任务3结束
总结:执行输出结果与异步+串行是一样的,这是因为主队列就是一个串行队列。
不同的是:不开启新的线程,而是在主线程上运行。
(四)同步+并行
//同步+并行
-(void)lnSyncConcurrent{
dispatch_queue_t queue = [self createConcurrentQueue];
NSLog(@"======start=====");
[self addTaskWithSyncInQueue:queue];
NSLog(@"======end=====");
}
执行输出结果:
2018-04-17 15:47:48.893351+0800 ThreadProject[2071:164583] ======start=====
2018-04-17 15:47:48.893553+0800 ThreadProject[2071:164583] 任务1开始
2018-04-17 15:47:53.894956+0800 ThreadProject[2071:164583] 任务1结束
2018-04-17 15:47:53.895313+0800 ThreadProject[2071:164583] 任务2开始
2018-04-17 15:47:55.896732+0800 ThreadProject[2071:164583] 任务2结束
2018-04-17 15:47:55.897079+0800 ThreadProject[2071:164583] 任务3开始
2018-04-17 15:47:56.898450+0800 ThreadProject[2071:164583] 任务3结束
2018-04-17 15:47:56.898782+0800 ThreadProject[2071:164583] ======end=====
总结:根据程序代码从上往下走,不开启新线程。
(五)同步+串行
输出结果与同步+并行是相同的。
总结:
同步+并行与同步+串行的区别:同步+并行使用嵌套调用不会产生死锁,同步+串行嵌套调用会产生死锁。
(六)同步+主队列
//同步+主队列
-(void)lnSyncMain{
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(@"======start=====");
[self addTaskWithSyncInQueue:queue];
NSLog(@"======end=====");
}
死锁
死锁产生原因:主队列上先有了一个lnSyncMain这个任务,在lnSyncMain方法中又在主队列上添加了任务。由于是串行,先要lnSyncMain这个任务完成,才执行后添加的任务。但是lnSyncMain这个任务的完成又依赖于添加的block。所以就出现了循环等待,导致死锁。
死锁测试:
//嵌套 同步+并行 (不会产生死锁)
-(void)testForLock{
dispatch_queue_t queue = [self createConcurrentQueue];
NSLog(@"======start=====");
dispatch_sync(queue, ^{
NSLog(@"任务1开始");
dispatch_sync(queue, ^{
NSLog(@"任务2开始");
NSLog(@"任务2结束");
});
NSLog(@"任务1结束");
});
NSLog(@"======end=====");
}
//嵌套 同步+串行(会产生死锁)
-(void)testForLockTwo{
dispatch_queue_t queue = [self createSerialQueue];
NSLog(@"======start=====");
dispatch_sync(queue, ^{
NSLog(@"任务1开始");
dispatch_sync(queue, ^{
NSLog(@"任务2开始");
NSLog(@"任务2结束");
});
NSLog(@"任务1结束");
});
NSLog(@"======end=====");
}
注意:不要嵌套使用同步执行的串行队列任务
GCD其他方法
- dispatch_once
保证在app运行期间,block中的代码只执行一次。常用于单例的初始化。 - dispatch_barrier_async
1、在并行队列中,等待在dispatch_barrier_async之前加入的任务全部执行完成之后(这些任务是并发执行的)
2、再执行dispatch_barrier_async中的任务
3、dispatch_barrier_async中的任务执行完成之后,再去执行在dispatch_barrier_async之后加入到队列中的任务(这些任务是并发执行的)。
使用场景:多读单写
//异步栅栏(多读单写场景)
-(void)lnAsyncBarrier{
dispatch_queue_t queue = [self createConcurrentQueue];
NSLog(@"======start=====");
[self addTaskWithAsyncInQueue:queue];
/*
*1、等待dispatch_barrier_async之前的任务全部执行完
*2、执行dispatch_barrier_async的任务
*3、执行dispatch_barrier_async之后的任务
*/
dispatch_barrier_async(queue, ^{
NSLog(@"栅栏方法");
});
dispatch_async(queue, ^{
NSLog(@"任务5开始");
sleep(3);
NSLog(@"任务5结束");
});
dispatch_async(queue, ^{
NSLog(@"任务6开始");
sleep(1);
NSLog(@"任务6结束");
});
NSLog(@"======end=====");
}
- dispatch_group_notify
结合dispatch_group_t一起使用,等待组里的任务全部完成后,调用dispatch_group_notify的block
使用场景:同时下载多个图片,所有图片下载完成之后去更新UI(回到主线程)
//group queue
-(void)lnGroupQueue{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = [self createConcurrentQueue];
//假设这个数组用于存放图片的下载地址
NSArray *arrayURLs = @[@"图片下载地址1",@"图片下载地址2",@"图片下载地址3"];
for(NSString *url in arrayURLs){
dispatch_group_async(group, queue, ^{
//根据url去下载图片
NSLog(@"%@",url);
});
}
//主线程上操作
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 当添加到组中的所有任务执行完成之后会调用该Block
NSLog(@"所有图片已全部下载完成");
});
}