当处理一些复杂且耗时的任务时,因为任务需要花费比较长的时间,如果是在主线程执行这些任务,有可能会造成造作卡顿,不流畅。所以把一些耗时的任务放到别的线程中会好一点。
但是开线程也是有代价的,不要随便乱开线程。只在需要的时候使用。
先来介绍一些概念性的东西,对使用GCD会有帮助。
串行,并发
串行(serial):一次只能执行一个任务。
并发(concurrent):同意时间可以有多个任务被执行。
就好比去银行办理业务,如果只开了一个窗口,那么一段时间能只能有一个客户办理业务,这就是串行。如果银行同事开了多个窗口,那么就可以有多个人同时办理业务,这就是并发。
临界区
一段代码不能被并发执行,两个线程不能同时执行这段代码。
上下文切换
当单个进程切换执行不同线程时存储与恢复执行状态的过程,在编写多任务应用时很普遍,但是会带来一些额外的开销。
线程安全 Thread safe
线程安全的代码能在多线程或者并发任务中被安全的调用,而不会导致任何问题。线程不安全的代码在某个时刻只能在一个上下文中运行。举个例子,NSArray,因为只能读取,所以可以在多个线程中同时使用。NSMutableArray就不是线程安全的,如果在读取的过程中,另一个线程刚好添加了一个对象到数组里,那么也许读取的结果就会出错。
队列 queue
dispatch_queue 使用FIFO(first in first out:先入先出)的顺序来执行这些任务,保证了第一个开始的任务就是第一个被添加到队列里的任务
main_queue :主队列,主队列是唯一可用于刷新UI的线程,这个队列就是用于发生消息给UIView,发送通知的。
global_queue:有4个不同的优先级:background,low,default,high。苹果的API也会使用这个队列,所以你添加的任务可能不会马上被执行。
- 开启一个线程
//dispatch_async(<#dispatch_queue_t queue#>, <#^(void)block#>)
//第一个参数 获取队列:全局队列 或者主线程队列
//第二个参数 要执行的Block代码
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
self.view.backgroundColor =
[UIColor redColor];
[self qwert];
dispatch_async(dispatch_get_main_queue(), ^{
//在主线程中更新UI
self.imageView.image = [UIImage imageNamed:@"2.jpg"];
});
});
//异步的执行一个全局队列global_queueu
@@@
异步线程:dispatch_async 如何使用不同队列类型:
1.主队列(串行):这是在一个并行队列完成后更新UI的共同选择。要这样做,你将在一个Block内部编写另一个Block。以及,如果你在主队列调用dispatch_async到主队列,你能确保这个信任务将在当前方法完成后的某个时间执行
2.并发队列:在后台执行非UI工作的共同选择
3.自定义串行队列:当你想串行执行后台任务并追踪它时就是一个好选择。这消除了资源争用,因为你知道一次只有一个任务在执行。注意若你需要来自某个方法的数据,你必须内联另一个 Block 来找回它或考虑使用 dispatch_sync。
- 延后工作 dispatch_after
// 设定一个时间基数
double delayInSecond = 4.0;
//声明一个变量指定要延迟的时长 "NSEC_PER_SEC "
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSecond*NSEC_PER_SEC);
//等待popTime 的时间 然后异步的添加一个block到主线程
dispatch_after(popTime, dispatch_get_main_queue(), ^{
self.imageView.image = [UIImage imageNamed:@"2.jpg"];
});
dispatch_after:是一个延迟的dispatch_async,在等待了一段时间后才开始执行。
最好只在主队列使用。其它的队列不推荐使用。
- 创建一个线程安全的单例
单例常常是不安全的,一般的创建方法
+(instancetype)shareManger{
static PhotoManager *sharedPhotoManager = nil;
if (!sharedPhotoManager) {
sharedPhotoManager = [[PhotoManager alloc] init];
sharedPhotoManager.photosArray = [NSMutableArray array];
NSLog(@"初始化了");
}
return sharedPhotoManager;
}
//有可能淡第一个线程进入IF语句的时候 ,这时候正好第二个线程启动了,完成了这个单例的初始化,这时切换回了第一个线程,又初始化了一次。那就又两个单例了。
+(instancetype)shareManager{
static test *test1 = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
test1 = [[test alloc]init];
});
return test1;
}
dispatch_once() 整个APP生命周期里只执行一次,这样写碰到上面的情况就不会再出现两个单例的情况了,因为第二个线程会被阻塞。。
有可能同时进行读写功能的时候,当一个线程在读数组,一个线程在写数组的时候,有可能出现线程安全问题
比如:
//读数组
- (NSArray *)photos
{
return _photosArray;
}
//写数组
- (void)addPhoto:(Photo *)photo
{
if (photo) {
[_photosArray addObject:photo];
}
}
以上就是线程不安全的。
障碍
dispatch_barriers :是一组函数,使GCD的障碍API确保提交的Block在那个特定时间上是制定队列上唯一执行的条目,这就意味着所有的咸鱼调度障碍提交到队列的条目必能在这个Block执行前完成,同一时间只能执行一个障碍Block,障碍Block完成后就可以去执行其他的Block
最好在自定义并发队列中执行,这对于原子或者临界区代码来说都是极佳的选择,任何你在设置货实例化的需要线程安全的事物都是使用障碍的最佳候选。
可以建立一个属性 :
@property (nonatomic, strong) dispatch_queue_t concurrentPhotoQueue;
然后再单例中创建你的自定义队列
//初始化自定义队列
self.concurrentPhotoQueue = dispatch_queue_create("我是concurrentPhotoQueue自定义队列", DISPATCH_QUEUE_CONCURRENT);
// 这里使用 dispatch_queue_create 初始化 concurrentPhotoQueue 为一个并发队列。第一个参数是样式命名;确保它是描述性的,将有助于调试。第二个参数指定你的队列是串行还是并发
然后是写入数组的代码
- (void)addPhoto:(Photo *)photo
{
if (photo) {
//添加写操作到自定义队列,当稍后执行时,这将是队列中唯一执行的条目。这个Block永远不会同时和其他Block一起在队列中执行。
dispatch_barrier_async(self.concurrentPhotoQueue, ^{
[_photosArray addObject:photo];
dispatch_async(dispatch_get_main_queue(), ^{
//发送一个通知说明完成了添加图片,因为会刷新UI,所以用主线程
[self postContentAddedNotification];
});
});
}
}
写操作完成了,但是还有读擦偶偶,要确保线程安全,你需要在自定义队列上执行操作,dispatch_sync()同步的提交工作并且在返回前等待它完成(或者你需要等待操作完成后才能使用Block处理过的数据(同事写一个__blcok变量写在dispatch_sync范围之外,来使用dispatch_sync处理过的对象))
关于什么时候使用dispatch_sync:
1.自定义串行队列:在这个状况下要非常小心!如果你正运行一个队列并调用dispatch_sync放在同一个队列,那就百分百创建了一个死锁。
2.主队列(串行):同上
3.并发队列:这才是同步工作的好选择,不论是通过调度障碍,或者需要等待一个任务完成才能执行进一步处理的情况。
- (NSArray *)photos
{
__block NSArray *array ;
dispatch_sync(self.concurrentPhotoQueue, ^{
array = [NSArray arrayWithArray:_photosArray];
});
return array;
}
未完待续。。。