Objective-C GCD深入理解
GCD(Grand Central Dispatch),主要用于多线程编程。它屏蔽了繁琐的线程实现及管理细节,将其交由系统处理。开发者只需要定义任务block(在底层被封装成dispatch_continuation_t结构体),并提交到正确的dispatch queue中。GCD包含dispatch queue和dispatch source。
一、dispatch queue是FIFO队列,分为两种:
1、serial(串行),队列中的block同一个时刻只会派发给一个线程,所以要等当前block执行完,才会开始执行下一个block。
2、concurrent(并行),队列中的block同一个时刻可能会派发给多个线程,所以多个block可以同时执行。
3、苹果官方说明:
1 | Serial queues (also known as private dispatch queues) execute one task at a time in the order in which they are added to the queue.< br >The currently executing task runs on a distinct thread (which can vary from task to task) that is managed by the dispatch queue. |
注意:串行队列中不同block可能在不同线程执行!
二、使用dispatch queue的方法,也分为两种:
1、dispatch_sync(同步),调用dispatch_sync方法的线程会被阻塞,直到block执行结束。在当前线程调用dispatch_sync方法可能会导致死锁!!!
2、dispatch_async(异步),调用dispatch_async方法的线程不会被阻塞。
1 2 3 4 5 | NSLog (@ "1" ); // 死锁,只会输出1 dispatch_sync(currentQueue, ^{ NSlog (@ "2" ); // block也提交到当前线程对应的队列 }); NSlog (@ "3" ); |
死锁的理解:
1、dispatch_sync方法调用和block调用,是当前线程需要处理的两个任务。
2、dispatch_sync方法调用首先提交到队列中。
3、然后block调用提交到队尾,需要等待dispatch_sync方法调用完成。
4、而dispatch_sync方法调用,又需要等到block执行结束才能返回。这就形成了等待环,即死锁。
解决死锁:把block交由另一个线程执行
1 2 3 4 5 6 | // 依次输出1、2、3 NSLog (@ "1" ); dispatch_sync(notCurrentQueue, ^{ NSlog (@ "2" ); // block提交到另外一个线程对应的队列 }); NSlog (@ "3" ); |
三、创建队列:
1 2 3 4 5 | dispatch_queue_t dispatch_queue_create( const char *_Nullable label, dispatch_queue_attr_t _Nullable attr); // label:队列的唯一标识,方便调试 // attr:队列类型,NULL或者DISPATCH_QUEUE_SERIAL表示串行,DISPATCH_QUEUE_CONCURRENT表示并行 dispatch_queue_t serialQueue = dispatch_queue_create( "com.tencent.wechat.file" , DISPATCH_QUEUE_SERIAL); dispatch_queue_t concurrentQueue = dispatch_queue_create( "com.tencent.wechat.network" ,DISPATCH_QUEUE_CONCURRENT); |
在MRC下,dispatch_queue_create创建的队列,需要用dispatch_release释放掉。
四、有时候,并不需要开发者创建队列,系统已经提供了两种很好用的队列。
主队列,对应主线程,是个串行队列:
1 | dispatch_queue_t serialMainQueue = dispatch_get_main_queue(); |
全局队列,是并行队列:
1 2 3 4 | dispatch_queue_t dispatch_get_global_queue( long identifier, unsigned long flags); // identifier:队列优先级 // flags:通常填0 dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); |
五、dispatch_after用于延迟执行任务:
1 2 3 4 5 | void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block); // 3秒后提交block到主队列,这个时间并不精确,得看当时线程的繁忙程度 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC )), dispatch_get_main_queue(), ^{ }); |
六、dispatch_suspend和dispatch_resume:
1、dispatch_suspend,挂起队列,只会暂停还没执行的任务,已经在执行的任务不会被影响。
2、dispatch_resume,恢复队列,继续调度还没执行的任务。
3、注意,resume一个没有被suspend的队列会导致crash!
七、dispatch_once
详见:https://www.cnblogs.com/yangwenhuan/p/9603472.html
八、dispatch group
在多个并行block都执行完,这就可以用到dispatch group
九、dispatch_set_target_queue
修改优先级
十、dispatch_barrier_async
barrier,顾名思义,添加一个障碍,就是就是个同步点。
1 2 3 4 5 6 7 8 9 10 | dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // blk1, blk2, blk3并行执行 dispatch_async(globalConcurrentQueue, blk1); dispatch_async(globalConcurrentQueue, blk2); dispatch_async(globalConcurrentQueue, blk3); // blk1, blk2, blk3全部执行完,才开始执行blk4 dispatch_barrier_async(globalConcurrentQueue, blk4); // blk4执行完,才开始并行执行blk5, blk6 dispatch_async(globalConcurrentQueue, blk5); dispatch_async(globalConcurrentQueue, blk6); |
十一、dispatch_apply
提交特定数量的block到队列中,并等待全部执行结束。dispatch_apply存在与dispatch_sync一样的死锁问题,因此推荐把dispatch_apply放到dispatch_async中执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(10, q, ^(size_t iter) { NSLog (@ "iteration[%zu]" , iter); }); NSLog (@ "done" ); 输出: iteration[3] iteration[2] iteration[0] iteration[4] iteration[1] iteration[5] iteration[9] iteration[8] iteration[7] iteration[6] done // 最后输出一定是这个 |
十二、dispatch semaphore
dispatch semaphore是信号量的封装,可用于实现线程安全,相比串行队列,粒度更小
1 2 3 4 5 6 7 8 9 10 | // 创建信号量,初始值可以不为1 dispatch_semaphore_t s = dispatch_semaphore_create(1); // 阻塞等待信号量大于等于1 dispatch_semaphore_wait(s, DISPATCH_TIME_FOREVER); // 信号量减1,开始执行临界区代码 /* 临界区代码 */ // 临界区代码执行结束,信号量+1,最先等待信号量的线程得到执行 dispatch_semaphore_signal(s); |
十三、dispatch io
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // 获取文件描述符 dispatch_fd_t fd = open(file_path, O_RDWR); // 创建用于处理dispatch io block的队列 dispatch_queue_t queue = dispatch_queue_create(queue_name, NULL ); // 创建dispatch io,绑定文件描述符 dispatch_io_t io = dispatch_io_create(DISPATCH_IO_STREAM, fd, queue, ^( int error) { // 异常 close(fd); }); // 设置一次最多读取的字节数 dispatch_io_set_high_water(io, size); // 这里书上说会使用global queue来并发读,但是实践没看出来,懂的同学请多多指教 dispatch_io_read(io, 0, max_size, queue, ^( bool done, dispatch_data_t _Nullable data, int error) { // dispatch_data_t转NSString dispatch_data_apply(data, ^ bool (dispatch_data_t region, size_t offset, const void *buffer, size_t size) { NSString *sData = [[ NSString alloc] initWithBytes:buffer length:size encoding: NSUTF8StringEncoding ]; return true ; }); }); |
十四、dispatch queue VS NSThread, pthread
利用NSThread和pthread也是可以进行多线程编程的,但是
1)需要自己实现线程管理,如分配多少线程、什么时候分配、什么时候销毁等等
2)性能很难比dispatch queue好,因为dispatch queue是基于XNU内核的workqueue实现的(中间还有一层封装是Libc中的pthread_workqueue)
十五、dispatch source
dispatch source是内核kqueue的封装。kqueue是一种多路复用技术,用于监听内核的各种事件通知,并做出相应处理。因为利用回调代替轮询,所以kqueue的CPU占用率很小。
1 2 3 4 5 6 7 8 9 10 | // 创建source dispatch_source_t dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue); // 取消source,已经在执行的handler会继续执行完,还没执行的不再执行 void dispatch_source_cancel(dispatch_source_t source); // 事件source发生时,执行handler void dispatch_source_set_event_handler(dispatch_source_t source, dispatch_block_t handler); // 取消source时,执行handler void dispatch_source_set_cancel_handler(dispatch_source_t source, dispatch_block_t handler); // 启动source void dispatch_resume(dispatch_object_t object); |
十六、dispatch queue和block的持有关系
block持有dispatch queue
参考链接:
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· 趁着过年的时候手搓了一个低代码框架
· 推荐一个DeepSeek 大模型的免费 API 项目!兼容OpenAI接口!