网站底部html代码,广州推广,汽车门户网站开发,美业营销策划公司参考文章#xff1a;GCD函数和队列原理探索
之前写项目的时候#xff0c;进行耗时的网络请求使用GCD处理过异步请求#xff0c;但对一些概念都很模糊#xff0c;这次就来系统学习一下GCD相关 相关概念
什么是GCD#xff1f;
Grand Center Dispatch简称GCD#xff0c;是…参考文章GCD函数和队列原理探索
之前写项目的时候进行耗时的网络请求使用GCD处理过异步请求但对一些概念都很模糊这次就来系统学习一下GCD相关 相关概念
什么是GCD
Grand Center Dispatch简称GCD是苹果公司开发的技术以优化应用程序支持多核心处理器
GCD的优势
GCD 是苹果公司为多核的并⾏运算提出的解决⽅案GCD 会⾃动利⽤更多的CPU内核⽐如双核、四核GCD 会⾃动管理线程的⽣命周期创建线程、调度任务、销毁线程程序员只需要告诉GCD想要执⾏什么任务不需要编写任何线程管理代码
GCD要做的事情就是GCD将任务添加到队列并指定执行任务的函数
「任务」
任务 执行操作就是在线程中执行的那段代码。在GCD中是放在Block中的。执行任务有两种方式
同步执行sync 必须等待当前语句执⾏完毕才会执⾏下⼀条语句不会开启线程只能在当前线程中执行任务不具备开启新线程的能力 异步执行async 异步添加任务到指定的队列中它不会做任何等待可以继续执行任务可以在新的线程中执行任务具备开启新线程的能力异步是多线程的代名词 注意异步执行async 虽然具有开启新线程的能力但是并不一定开启新线程这跟任务指定的队列类型有关 两者主要的区别是是否等待队列的任务执行结束以及是否具备开启新线程的能力
「队列」
队列Dispatch Queue 这里的队列指执行任务的等待队列即用来存放任务的队列。每读取一个任务则从队列中释放一个任务参考下图 队列分为两种串行队列和并发队列。不同的队列中任务排列的方式是不一样的任务通过队列的调度由线程池安排的线程来执行 两者都符合FIFO先进先出的原则
串行队列Serial Dispatch Queue每次只有一个任务被执行让任务一个接着一个地执行。只开启一个线程一个任务执行完毕后再执行下一个任务并发队列Concurrent Dispatch Queue可以让多个任务并发同时执行。可以开启多个线程并且同时执行任务 注意并发队列的并发功能只有在异步dispatch_async方法下才有效 两者的主要区别是执行顺序不同以及开启线程数不同。参考下图 创建队列
// 串行队列
dispatch_queue_t serialQueue dispatch_queue_create(bySelf, DISPATCH_QUEUE_SERIAL);// 并行队列
dispatch_queue_t concurrentQueue dispatch_queue_create(bySelf, DISPATCH_QUEUE_CONCURRENT);MRC下生成的队列需由程序员负责持有或释放通过以下两个函数的引用计数来管理内存
dispatch_retain(serialQueue);
dispatch_release(serialQueue);队列与函数组合
队列用来调用任务函数用来执行任务那么队列和函数不同的配合会有怎样的运行效果呢
同步函数串行队列 不会开启线程在当前线程中执行任务任务串行执行任务一个接着一个执行会产生阻塞 同步函数并发队列 不会开启线程在当前线程中执行任务任务一个接着一个执行 异步函数串行队列 会开启一个线程任务一个接着一个执行 异步函数并发队列 开启线程在当前线程执行任务任务异步执行没有顺序CPU调度有关
GCD中的队列
主队列 The main queue系统自带的一个队列放到这个队列中的代码会被系统分配到主线程中执行。Main queue可以调用dispatch_get_main_queue()来获得。因为main queue是与主线程相关的所以这是一个串行队列 交至其中的任务顺序执行(一个任务执行完毕后再执行下一个任务) dispatch_queue_t mainQueue dispatch_get_main_queue();全局队列 Global queues整个应用程序存在4个全局队列(系统已经创建好只需获得即可)高、中(默认)、后台三个优先级队列可以调用dispatch_get_global_queue函数传入有下级来访问队列。全局队列是并行队列可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)并发功能只有在异步(dispatch_async)函数下才有效 dispatch_queue_t globalQueueHigh dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_t globalQueueDefault dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t globalQueueLow dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t globalQueueBackground dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);自定义队列
用户自己创建队列dispatch_queue_create创建的队列可以是串行的也可以是并行的因为系统已经给我们供了并行、串行队列所以一般情况下我们不再需要在创建自己的队列。用户创建的队列可以有任意多个
注意: 分线程不能刷新UI刷新UI只能在主线程。如果每个线程都刷新UI将会很容易造成UI冲突会出现不同步的情况所以只有主线程中能刷新UI系统是为了降低编程的复杂程度最大程度的避免冲突
相关案例分析
耗时性
任务是耗时的不同函数只要执行任务都会耗时
异步函数会开启线程执行的耗时相对较少在实际开发中异步可以用来解决并发、多线程等问题
六种情况示例
主队列添加同步任务
在当前的main队列中添加一个任务并同步执行该任务
void mainSyncTest(void) {/*主队列同步不会开启线程会崩溃*/NSLog(start);// dispatch_queue_t concurrentQueue dispatch_queue_create(bySelf, DISPATCH_QUEUE_CONCURRENT);dispatch_queue_t serialQueue dispatch_queue_create(bySelf, DISPATCH_QUEUE_SERIAL);dispatch_sync(serialQueue, ^{NSLog(a);});NSLog(b);
}在当前流程中默认队列就是主队列也是一个串行队列任务执行顺序应为 而到了第二步的块任务会向当前的主队列中添加一个任务NSLog(a);因为主队列是一个串行队列现在要执行b必须要等a任务块执行完成而a又必须等b执行完成产生了相互等待问题造成了死锁见下图 运行结果就是崩溃 解决办法就是将主队列改成自定义的串行队列或并发队列
NSLog(start);// dispatch_queue_t concurrentQueue dispatch_queue_create(bySelf, DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQueue dispatch_queue_create(bySelf, DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{NSLog(a);
});NSLog(b);运行结果 主队列添加异步任务
void mainAyncTest(void) {NSLog(start);dispatch_queue_t serialQueue dispatch_queue_create(bySelf, DISPATCH_QUEUE_SERIAL);dispatch_async(serialQueue, ^{NSLog(a);});NSLog(b);
}主队列添加异步任务不会阻塞不会崩溃
运行结果 并发队列添加异步任务
void concurrentAsyncTest(void) {dispatch_queue_t concurrentQueue dispatch_queue_create(bySelf, DISPATCH_QUEUE_CONCURRENT);NSLog(1 --- %, [NSThread currentThread]);dispatch_async(concurrentQueue, ^{NSLog(2 --- %, [NSThread currentThread]);dispatch_async(concurrentQueue, ^{NSLog(3 --- %, [NSThread currentThread]);});NSLog(4 --- %, [NSThread currentThread]);});NSLog(5 --- %, [NSThread currentThread]);
}并发队列通道比较宽不会导致任务的阻塞 每个任务复杂度基本一致异步不会堵塞主线程dispatch_async会开启一个新的线程去执行其中的任务块
运行结果 并发队列添加同步任务
void concurrentSyncTest(void) {dispatch_queue_t concurrentQueue dispatch_queue_create(bySelf, DISPATCH_QUEUE_CONCURRENT);NSLog(1 --- %, [NSThread currentThread]);dispatch_sync(concurrentQueue, ^{NSLog(2 --- %, [NSThread currentThread]);dispatch_sync(concurrentQueue, ^{NSLog(3 --- %, [NSThread currentThread]);});NSLog(4 --- %, [NSThread currentThread]);});NSLog(5 --- %, [NSThread currentThread]);
}因为并发队列所以不会导致队列任务的阻塞同时因为是同步执行所以不会开启新的线程按照顺序去执行流 串行队列添加异步任务
void serialAsyncTest(void) {// 串行队列NSLog(start --- %, [NSThread currentThread]);dispatch_queue_t serialQueue dispatch_queue_create(bySelf, DISPATCH_QUEUE_SERIAL);for (int i 0; i 5; i) {dispatch_async(serialQueue, ^{NSLog(%d --- %, i, [NSThread currentThread]);});}NSLog(hello queue);NSLog(end --- %, [NSThread currentThread]);
}运行结果 串行队列添加异步任务开启了一条新线程但是任务还是串行所以任务是一个一个执行 另一方面可以看出所有任务是在打印的start和end之后才开始执行的。说明任务不是马上执行而是将所有任务添加到队列之后才开始同步执行
串行队列添加同步任务
void serialSyncTest(void) {NSLog(start --- %, [NSThread currentThread]);dispatch_queue_t serialQueue dispatch_queue_create(bySelf, DISPATCH_QUEUE_SERIAL);for (int i 0; i 5; i) {dispatch_sync(serialQueue, ^{NSLog(%d --- %, i, [NSThread currentThread]);});}NSLog(hello queue);NSLog(end --- %, [NSThread currentThread]);
}运行结果 串行队列同步执行任务所有任务都是在主线程中执行的并没有开启新的线程。而且由于串行队列所以按顺序一个一个执行 同时我们还可以看到所有任务都在打印的start和end之间这说明任务是添加到队列中马上执行的
串行队列添加同步和异步任务混合
void serialSyncAndAsyncTest(void) {NSLog(start --- %, [NSThread currentThread]);dispatch_queue_t serialQueue dispatch_queue_create(bySelf, DISPATCH_QUEUE_SERIAL);dispatch_async(serialQueue, ^{NSLog(a --- %, [NSThread currentThread]);dispatch_sync(serialQueue, ^{NSLog(b --- %, [NSThread currentThread]);});NSLog(d --- %, [NSThread currentThread]);});NSLog(hello queue);NSLog(end --- %, [NSThread currentThread]);
}在异步添加的线程中情况类似主队列添加同步函数b任务块和d相互等待了导致死锁
运行结果 并发队列多任务
void concurrentSyncAndAsyncTest(void) {dispatch_queue_t concurrentQueue dispatch_queue_create(bySelf, DISPATCH_QUEUE_CONCURRENT);dispatch_async(concurrentQueue, ^{NSLog(1);});dispatch_async(concurrentQueue, ^{NSLog(2);});dispatch_sync(concurrentQueue, ^{NSLog(3);});NSLog(0);dispatch_async(concurrentQueue, ^{NSLog(7);});dispatch_async(concurrentQueue, ^{NSLog(8);});dispatch_async(concurrentQueue, ^{NSLog(9);});
}
// 3 0 1 2 7 9 8
// 3 0 1 7 9 8 2
// 3 0 1 2 7 8 9
// 3 1 0 2 7 8 9
// 3 2 1 0 7 8 9
// 3 1 2 0 7 9 8结果分析
主队列共10个任务1、2、3的顺序不确定3、0是同步任务所以3一定在0前面7、8、9一定在0后面
GCD线程间通信
在iOS开发过程中我们一般在主线程里边进行UI刷新例如点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时需要回到主线程那么就用到了线程之间的通讯
void communicateAmongThread(void) {dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{for (int i 0; i 6; i) {NSLog(1 --- %, [NSThread currentThread]);}// 回到主线程dispatch_async(dispatch_get_main_queue(), ^{NSLog(2 --- %, [NSThread currentThread]);});});
}运行结果 可以看到在其他线程中先执行操作执行完了之后回到主线程执行主线程的相应操作
GCD的栅栏方法
我们有时需要异步执行两组操作而且第一组操作执行完之后才能开始执行第二组操作。这样我们就需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给分割起来当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async方法在两个操作组间形成栅栏
这是没添加栅栏函数之前的结果顺序不确定 void barrierFunc(void) {dispatch_queue_t queue dispatch_queue_create(666, DISPATCH_QUEUE_CONCURRENT);dispatch_async(queue, ^{NSLog(1 --- %, [NSThread currentThread]);});dispatch_async(queue, ^{NSLog(2 --- %, [NSThread currentThread]);});dispatch_barrier_async(queue, ^{NSLog(----barrier-----%, [NSThread currentThread]);});dispatch_async(queue, ^{NSLog(3 --- %, [NSThread currentThread]);});dispatch_async(queue, ^{NSLog(4 --- %, [NSThread currentThread]);});
}栅栏函数可保证异步操作的执行顺序这里保证了1或2先执行3或4后执行 GCD的延时方法
当我们需要延迟执行一段代码时就需要用到GCD的dispatch_after方法
void delayExec(void) {NSLog(run -- 0);dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{// 3秒后异步执行这里的代码...NSLog(run -- 2);});
}运行结果 GCD的一次性代码只执行一次
我们在创建单例、或者有整个程序运行过程中只执行一次的代码时我们就用到了GCD的dispatch_once方法。使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
void onceExec(void) {static dispatch_once_t onceToken;dispatch_once(onceToken, ^{// 只执行一次的代码这里面是默认线程安全的NSLog(Hhhhhhhh);});
}onceExec();
onceExec();调用两次只会执行一次 GCD的队列组
有时候我们会有这样的需求分别异步执行2个耗时操作然后当2个耗时操作都执行完毕后再回到主线程执行操作。这时候我们可以用到GCD的队列组。
我们可以先把任务放到队列中然后将队列放入队列组中调用队列组的dispatch_group_notify回到主线程执行操作
void queueGroup(void) {// GCD的队列组dispatch_group_t group dispatch_group_create();dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// 执行1个耗时的异步操作int i 0;while (i 100) {NSLog(1);i;}});dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// 执行1个耗时的异步操作int i 0;while (i 100) {NSLog(2);i;}});dispatch_group_notify(group, dispatch_get_main_queue(), ^{// 等前面的异步操作都执行完毕后回到主线程...NSLog(3);});
}运行结果 dispatch_set_target_queue
这个函数有两个作用
改变队列的优先级防止多个串行队列的并发执行
改变队列的优先级
dispatch_queue_create函数生成的串行队列和并发队列都使用 与默认优先级的Global Dispatch Queue 相同执行优先级 的线程
void serialBackgroundQueue(void) {// 需求生成一个后台的串行队列dispatch_queue_t serialQueue dispatch_queue_create(bySelf, DISPATCH_QUEUE_SERIAL);dispatch_queue_t globalQueueBackground dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);// 第1个参数需要改变优先级的队列// 第2个参数目标队列dispatch_set_target_queue(serialQueue, globalQueueBackground);
}防止多个串行队列的并发执行
如果是将任务追加到3个串行队列中那么这些任务就会并发执行。因为每个串行队列都会创建一个线程这些线程会并发执行 如果将多个串行的queue使用dispatch_set_target_queue指定到了同一目标那么这多个串行queue在目标queue上就是同步执行的不再是并行执行 将串行队列加入指定优先级队列会按照加入优先级队列的顺序依次执行串行队列
未加入优先级队列 void abandonSerialQueuesToConcurrent(void) {dispatch_queue_t targetQueue dispatch_queue_create(test.target.queue, DISPATCH_QUEUE_SERIAL);dispatch_queue_t queue1 dispatch_queue_create(test.1, DISPATCH_QUEUE_SERIAL);dispatch_queue_t queue2 dispatch_queue_create(test.2, DISPATCH_QUEUE_SERIAL);dispatch_queue_t queue3 dispatch_queue_create(test.3, DISPATCH_QUEUE_SERIAL);// dispatch_set_target_queue(queue1, targetQueue);
// dispatch_set_target_queue(queue2, targetQueue);
// dispatch_set_target_queue(queue3, targetQueue);dispatch_async(queue1, ^{NSLog(1 in);[NSThread sleepForTimeInterval:3.f];NSLog(1 out);});dispatch_async(queue2, ^{NSLog(2 in);[NSThread sleepForTimeInterval:2.f];NSLog(2 out);});dispatch_async(queue3, ^{NSLog(3 in);[NSThread sleepForTimeInterval:1.f];NSLog(3 out);});
}加入后 dispatch_suspend/dispatch_resume
dispatch_suspend并不会立即暂停正在运行的block而是在当前block执行完成后暂停后续的block执行
挂起指定队列dispatch_suspend(serialQueue); 恢复指定队列dispatchp_resume(serialQueue);
void suspendOrResumeQueue(void) {dispatch_queue_t queue dispatch_queue_create(com.test.gcd, DISPATCH_QUEUE_SERIAL);//提交第一个block延时5秒打印。dispatch_async(queue, ^{sleep(5);NSLog(After 5 seconds...);});//提交第二个block也是延时5秒打印dispatch_async(queue, ^{sleep(5);NSLog(After 5 seconds again...);});//延时一秒NSLog(sleep 1 second...);sleep(1);//挂起队列NSLog(suspend...);dispatch_suspend(queue);//延时10秒NSLog(sleep 17 second...);sleep(17);//恢复队列NSLog(resume...);dispatch_resume(queue);
}线程安全
为了保证线程安全我们之前了解过互斥锁在书上给了我们dispatch_semaphore方法我们总结为三点 synchronized加锁属于互斥锁当有线程执行锁住的代码的时候其他线程会进入休眠需要唤醒后才能继续执行性能较低 - (void)synchronizedSecurity {dispatch_async(dispatch_get_global_queue(0, 0), ^{// 加锁保证block中执行完成才会执行其他的synchronized (self) {NSLog(1开始);sleep(2);NSLog(1结束);}});dispatch_async(dispatch_get_global_queue(0, 0), ^{synchronized (self) {NSLog(2开始);sleep(2);NSLog(2结束);}});
}信号量semaphore加锁属于自旋锁当有线程执行锁住的代码的时候其他线程会进入死循环的等待当解锁后会立即执行性能较高 提供了三种函数 dispatch_semaphore_create创建一个Semaphore并初始化信号的总量同时有几个线程可以执行一般是1 dispatch_semaphore_wait可以使总信号量减1当信号总量小于0时就会一直等待阻塞所在线程否则就可以正常执行这个放在要执行的代码的前面。 dispatch_semaphore_signal发送一个信号让信号总量加1代码执行完成之后使用使其他线程可以继续执行 void semaphoreSecurity(void) {dispatch_semaphore_t semalook dispatch_semaphore_create(1);dispatch_async(dispatch_get_global_queue(0, 0), ^{dispatch_semaphore_wait(semalook, DISPATCH_TIME_FOREVER);NSLog(1开始);sleep(2);NSLog(1结束);dispatch_semaphore_signal(semalook);});dispatch_async(dispatch_get_global_queue(0, 0), ^{dispatch_semaphore_wait(semalook, DISPATCH_TIME_FOREVER);NSLog(2开始);sleep(2);NSLog(2结束);dispatch_semaphore_signal(semalook);});
}NSLock void lockSecurity(void) {NSLock *lock [[NSLock alloc]init];dispatch_async(dispatch_get_global_queue(0, 0), ^{[lock lock];NSLog(1开始);sleep(2);NSLog(1结束);[lock unlock];});dispatch_async(dispatch_get_global_queue(0, 0), ^{[lock lock];NSLog(2开始);sleep(2);NSLog(2结束);[lock unlock];});
}运行结果