任务和队列
执行任务的方式:
1.用同步的方式执行任务:
dispatch_sync(dispatch_queue_t queue, ^(void)block);
queue:队列
block:任务
2.用异步方式执行任务:
dispatch_async(dispatch_queue_t queue, ^(void)block);
同步和异步的区别:
同步:只能在当前线程中执行任务,不具备开启新线程的能力
异步:可以在新的线程中执行任务,具备开启新线程的能力;
队列的类型:
并发队列:
可以让多个任务并发(同时)执行(开启多个线程同时执行任务)
并发功能只有在异步函数(dispatch_async)下才有效
全局队列+同步任务/并发队列+同步任务:没有开启新线程,任务是逐个完成的
全局队列+异步任务/并发队列+异步任务:开启了新线程,任务是并发完成的
并发队列:
dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create(“com.example.gcd.MyConcurrentDispatchQueue”,DISPATCH_QUEUE_CONCURRENT);
全局队列:
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
串行队列:
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create(“com.example.gcd.MySerialDispatchQueue”,NULL);
主队列:dispatch_get_main_queue();
让任务一个接着一个的执行(也就是说必须等一个任务执行完毕后才可执行下一个任务)
串行队列+同步任务:没有开启新的线程,任务逐个完成
串行多列+异步任务:开启新的线程,任务逐个完成
主队列+同步任务:死锁;
主队了+异步任务:没有开启新的线程,任务逐个完成
并发和串行主要影响任务的执行方式,并发--多个任务并发执行,串行--一个任务完成后,再执行下一个任务
同步和异步主要影响能不能开启新的线程:同步--在当前线程中执行任务,不具备开启线程的能力;异步:可以在新的线程中执行任务,具备开启新线程的能力
开发者定义想要执行的任务并追加到适当地Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。
dispatch_async(queue,^{
/* 长时间处理
例如 AR用画像识别
例如 数据库访问
*/
/*长时间处理结束,主线程使用该处理结果*/
dispatch_async(dispatch_get_main_queue(),^{
/*只在主线程可以执行的处理
例如 用户界面更新
*/
});
});
在导入GCD之前,Cocoa框架提供了NSObject类的performSelectorInBackgound:withObject:实例方法和performSelectorOnMainThread实例方法等简单地多线程编程技术
[self performSelectorInBackground:@selector(doWork) withObject:nil];
[self performSelectorOnMainThread:@selector(doneWork) withObject:nil waitUntilDone:NO];
多线程编程容易导致的问题:
- 数据竞争:多个线程更新相同的资源会导致数据的不一致
- 死锁:停止等待事件的线程会导致多个线程相互持续等待
- 使用太多线程会消耗大量内存
GCD的API
开发者定义想要执行的任务并追加到适当地Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。
dispatch_async(queue,^{
/*想要执行的任务*/
});
Dispatch Queue是执行处理的等待队列。Dispatch Queue按照追加的顺序(先进先出FIFO)执行处理。
Dispatch Queue的种类:
- Serial Dispatch Queue等待现在执行中处理结束,同时只能执行1个追加处理。
- Concurrent Dispatch Queue 不等待现在执行中处理结束,可以并行执行多个处理,但并行执行的处理数量取决于当前系统的状态。
iOS和OS X 的核心-- XNU内核决定应当使用的线程数,并指生成所需的线程执行处理。另外,当处理结束,应当执行的处理数减少时,XUN内核会结束不再需要的线程,XUN内核仅使用Concurrent Dispatch Queue便可完美地管理并执行多个处理的线程
dispatch_queue_create
第一种方法是通过GCD的API生成Dispatch Queue。
通过dispatch_queue_create函数可生成Dispatch Queue:
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create(“com.example.gcd.MySerialDispatchQueue”,NULL);
该函数的第一个参数是指定Serial Dispatch Queue的名称,推荐使用应用程序ID这种逆序全程域名,该名称在Xcode和Instruments的调试器中作为Dispatch Queue名称表示,另外该名称也出现在应用程序崩溃时所生成的CrashLog中。
第二个参数:生成Serial Dispatch Queue时,第二个参数指定为NULL;生成Concurrent Dispatch Queue时,指定为 DISPATCH_QUEUE_CONCURRENT.
返回值:dispatch_queue_t类型变量
注:尽管有ARC这一通过编译器自动管理内存的优秀技术,但生成的Dispatch Queue必须由程序员负责释放。Dispatch_queue_create函数生成的Dispatch Queue在使用结束后通过dispatch_release函数释放。
用dispatch_queue_create函数可生成任意多个Dispatch Queue,dispatch_queue_create生成的多个Serial Dispatch Queue 可并行执行
dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create(“com.example.gcd.MyConcurrentDispatchQueue”,DISPATCH_QUEUE_CONCURRENT);
dispatch_async(myConcurrentDispatchQueue,^{NSLog(@”block on myConcurrentDispatchQueue”);});
dispatch_retain(myConcurrentDispatchQueue);
dispatch_release(myConcurrentDispatchQueue);
Main Dispatch Queue /Global Dispatch Queue
第二种方法是获取系统标准提供的Dispatch Queue
Main Dispatch Queue是在主线程中执行的Dispatch Queue,因为主线程只有一个,所以Main Dispatch Queue自然是Serial Dispatch Queue;
Global Dispatch Queue是所有应用程序都能够使用的Concurrent Dispatch Queue,没有必要通过函数逐个生成Concurrent Dispatch Queue,只要获取Global Dispatch Queue使用即可。
Global Dispatch Queue有4个优先级,分别是高优先级(High Priority)、默认优先级(Default Priority)、低优先级(Low Priority)和后台优先级(Background Priority)。通过XUN内核管理的用于Global Dispatch Queue的线程,将各自使用的Global Dispatch Queue的执行优先级作为线程的执行优先级使用。在向Global Dispatch Queue追加处理时,应选择与处理内容对应的执行优先级的Global Dispatch Queue
//Main Dispatch Queue的获取方法
Dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
//Global Dispatch Queue(高优先级)的获取方法
dispatch_queue_t globalDispatchQueueHight = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
//Global Dispatch Queue(默认优先级)的获取方法
dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_D EFAULT,0);
//Global Dispatch Queue(低优先级)的获取方法
dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0);
//Global Dispatch Queue(后台优先级)的获取方法
dispatch_queue_t globalDispatchQueueHight = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
Main Dispatch Queue 和Global Dispatch Queue执行dispatch_retain函数和dispatch_release函数不会引起任何变化,也不会有任何问题
//在默认优先级的Global Dispatch Queue中执行Block
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
/*可并行执行的处理*/
//在Main Dispatch Queue中执行Block
dispatch_async(dispatch_get_main_queue(),^{
//只能在主线程中执行的处理
});
});
线程间通信
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:IMAGE_URL];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image= [UIImage imageWithData:data];
//回归到主线程
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
dispatch_set_target_queue
dispatch_queue_create函数生成的Dispatch Queue不管是Serial Dispatch Queue还是Concurrent Dispatch Queue,都使用默认优先级Global Dispatch Queue相同执行优先级的线程,而变更生成的Dispatch Queue的执行优先级要使用dispatch_set_target_queue函数,
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create(“com.example.gcd.MySerialDispatchQueue”,NULL);
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGOUND,0);
dispatch_set_target_queue(mySerialDispatchQueue,globalDispatchQueueBackground);
指定要变更优先级的Dispatch Queue 为dispatch_set_target_queue函数的第一个参数,指定要使用的执行优先级相同优先级的Global Dispatch Queue为第二个参数(目标),第一个参数如果指定系统提供的Main Dispatch Queue 和Global Dispatch Queue则不知道会出现什么状况,因此这些均不可指定
将Dispatch Queue指定为dispatch_set_target_queue函数的参数,不仅可以变更Dispatch Queue的执行优先级,还可以作成Dispatch Queue的执行阶层;如果在多个Serial Dispatch Queue中用dispatch_set_target_queue函数指定目标位某一Serial Dispatch Queue,那么原先本应并行执行的多个Serial Dispatch Queue,在目标Serial Dispatch Queue上只能同时执行一个处理。
dispatch_after
想在指定时间(3秒,可能不仅限于3秒)后执行处理,用dispatc_after来实现
dispatch_time_t time = dispatch_time( DISPATCH_TIME_NOW,3ull*NSEC_PER_SEC);
//150毫秒:150ull*NSEC_PER_MSEC
dispatch_after(time,dispatch_get_main_queue(),^{
NSLog(@”waited at least three seconds.”);
});
dispatch_after第一个参数是dispatch_time_t类型的值,该值使用dispatch_time函数或dispatch_walltime函数作成
Dispatch Group
在追加到Dispatch Queue中的多个处理全部结束后想执行结束处理。
只使用一个Serial Dispatch Queue时,只要将想要执行的处理全部追加到该Serial Dispatch Queue中并在最后追加结束处理,即可实现,但是在使用Concurrent Dispatch Queue时或同时使用多个Dispatch Queue时,源代码就会变得颇为复杂->使用Dispatch Group
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group,queue,^{NSLog(@”blk0”);});
dispatch_group_async(group,queue,^{NSLog(@”blk1”);});
dispatch_group_async(group,queue,^{NSLog(@”blk2”);});
dispatch_group_notify(group,dispatch_get_main_queue(),^{NSLog(@”done”);});
因为向Global Dispatch Queue 即Concurrent Dispatch Queue 追加处理,多个线程并行执行,所以追加处理的执行顺序不定,执行时会发生变化,但是执行结果的done一定是最后输出的。监视Dispatch Group的执行结束后,将结束的处理追加到Dispatch Queue中
可以使用dispatch_group_wait函数仅等待全部处理执行结束
dispatch_time_t time = dispatch_time( DISPATCH_TIME_NOW,1ull*NSEC_PER_SEC);
long result = dispatch_group_wait(group,time);
if(result == 0)
{
//属于Dispatch Group的全部处理执行结束
}else {
//属于Dispatch Group的某一个处理还在执行中
}
当等待时间为 DISPATCH_TIME_FOREVER由dispatch_group_wait函数返回时,由于属于Dispatch Group 的处理必定全部执行结束,因此返回值恒为0;
dispatch_barrier_async
在访问数据库或文件时,避免数据竞争
dispatch_queue_t queue = dispatch_queue_create(“com.example.gcd,ForBarrier”,DISPATCH_QUUE_CONCURRENT);
dispatch_async(queue,blk0_for_reading);
dispatch_async(queue,blk1_for_reading);
dispatch_async(queue,blk2_for_reading);
/*写处理,将写入的内容供之后的读取操作*/
dispatch_barrier_async(queue,blk_for_writing)
dispatch_async(queue,blk3_for_reading);
dispatch_async(queue,blk4_for_reading);
dispatch_async(queue,blk5_for_reading);