IOS并发编程——Grand Center Dispatch

2024-09-06 00:48

本文主要是介绍IOS并发编程——Grand Center Dispatch,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

并发编程往往能够提高程序的效率,在其他平台中进行并发编程往往就是多线程的编程,在IOS中同样可以进行多线程编程,但是Apple的官方文档却告诉我们,尽量不要使用原生线程,而是使用其他替代技术。为什么呢?有如下几点理由:

1、原生线程编程往往需要涉及同步,线程资源获取释放等操作,相对复杂。

2、原生多线程编程线程切换运行由人为控制,不如直接交给操作系统来管理线程效率高(操作系统会根据系统实时状况灵活操作多线程)。

3、每一次对原生线程的操作,都要进行内核层的操作。而内核层操作花费时间大。而由操作系统代劳的并发操作只会在必要时机切换到内核层。

OK,既然将并发操作交给系统有那么多好处,那我们就来了解一下IOS系统的并发编程。

IOS并发编程技术 包括

  • Dispatch Queues
  • Dispatch Source
  • Operation Queues
首先,我们来了解下Dispatch Queues,即GCD。


什么是GCD

Grand Central Dispatch (GCD) ,大中枢派发。是Apple系统的一种执行task block的技术。这些task block通过提交给系统或自创建的Dispatch queue,由系统来并发或顺序的执行。
这里涉及到两点:
1、我们要执行的任务——task block,
2、要让我们的任务执行,需要将我们的task block提交到dispatch queue中。要并发的执行,就将task block提交到并发dispatch queue,要顺序执行,就将任务提交到顺序dispatch queue中。
之后,我们就让系统来给我们代劳代码执行,并发任务切换等活啦~ 怎么样,简单吧。
下面,我们就根据这两点:task block, dispatch queue来深入了解GCD。

block object(task block)

task block,即block object。类似于C语言中的函数指针,它是一个C风格代码块,可以用于OC,C,C++代码中。其中包含了独立的代码运行单元。当然,它的用途并不仅限于GCD,我们可以像使用函数指针一样,来使用block object。
block object本质上是一个heap上的数据结构,类似于普通的OC对象,由编译器产生并管理。
block object的使用如下,它可以接受参数,并有返回值。
int x = 123;
int y = 456;// Block declaration and assignment
void (^aBlock)(int) = ^(int z) {printf("%d %d %d\n", x, y, z);
};// Execute the block
aBlock(789);   // prints: 123 456 789

block object接受的参数

block 可以接受外界传入的参数,默认的,参数会被copy到block结构体中,即外界的传入参数与block内部的参数具有相同的value却在不同的内存地址分配。
但这要求我们对传入参数做只读操作。
如果对参数进行了改变,编译器会提示我们在外部的参数前加上__block前缀,加上__block前缀后,在block中,参数不再是按值传递,而是操作的外部参数本身,即同一块内存地址。由于我们没有提供相应的同步条件,因此要求block的执行必须是synchronously的。
可以结合下面代码来理解关于block的参数传递
代码:
NSLog(@"%d", &a);dispatch_sync(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{NSLog(@"%d", &a);});NSLog(@"%d", &a);

输出:


代码:
int a = 13;NSLog(@"%d", &a);dispatch_sync(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{NSLog(@"%d", &a);});NSLog(@"%d", &a);

输出:

关于block设计的指导方针

1、对于asynchronously执行的block,应当避免接收由外界创建并释放的大数据结构或指针类型的变量,因为当你的block开始执行时,所传入的指针指向的那块内存可能已经被释放掉。
2、对于提交到dispatch queue中的block,系统会自动引用block,并在执行完block后将其释放,无需人工干涉。
3、虽然比原生线程,创建block的花销更小,但是其也是要消耗系统资源的。可以考虑用内联函数代替block。
4、不用直接在多个block间共享变量。如果是属于同一个dispatch queue的block,可以利用dispatch queue的context pointer来共享数据。Storing Custom Context Information with a Queue.
5、虽然dispatch queue会自动释放内存,但并不保证内存释放的时机。若我们在block中创建了OC变量,最好有我们自己同时创建autorelease pool来释放内存。


Dispatch Queues

要想让我们的task block,还必须将task block提交到对应的dispatch queue中。dispatch queue类似于一个工作队列,系统采用FIFO的原则,对提交到dispatch queue中的task block进行处理。

Dispatch queue分类

1、Serial queue, 想让task block顺序执行,则将task block提交到Serial queue。
2、Concurrent queue,想让task block并发执行,则将block提交到Concurrent queue。
3、Main dispatch queue,是App全局的Serial queue,负责运行程序主线程上的任务并处理程序UI事件等,同时,也会处理你提交到main dispatch queue中的任务。注意,由于其和UI响应相关,应注意不要使自己提交的task阻塞main dispatch queue而导致程序界面停止响应。
注意,不管是Serial queue还是Concurrent queue,均遵循FIFO的原则,只不过Serial queue是在等待上一个task block完成后才放出下一个task block,而Concurrent queue则不管上一个task block是否完成,均会在适当的时间将task block放出。

获取Dispatch queue

获取Dispatch queue有两种方式:
1、直接获取系统提供的dispatch queue
2、创建自己的dispatch queue

获取系统dispatch queue

IOS系统为我们提供了现成的Dispatch queue,多数情况下,我们在需要时直接拿来用即可。
获取System 定义的的全局Concurrent queue
dispatch_get_global_queue
dispatch_queue_t dispatch_get_global_queue ( long identifier, unsigned long flags );

该函数会返回一个系统全局的concurrent queue,因为该queue是系统管理的,因此我们调用
dispatch_suspend dispatch_resume , 或  dispatch_set_context等函数操作该queue时,是无效的。

获取与程序main thread 相关的Serial queue

dispatch_get_main_queue

创建自己的Dispatch queue

可以通过函数

dispatch_queue_create

 dispatch_queue_t dispatch_queue_create ( const char *label, dispatch_queue_attr_t attr ); 

创建自己的Dispatch queue。label为Dispatch queue名称,而attr可以设置为 DISPATCH_QUEUE_SERIAL (or NULL)创建serial queue, DISPATCH_QUEUE_CONCURRENT来创建concurrent queue。


Dispatch queue的内存管理

除了上述系统管理的global queue与main queue不需要我们操心外,我们需要对自己创建的queue进行引用计数管理。即使在我们在创建具有垃圾回收功能的程序,而dispatch queue不支持垃圾回收机制。
dispatch queue的计数管理函数如下:
dispatch_retain
dispatch_release

在dispatch queue中存储自定义信息

我们可以通过调用函数
dispatch_set_context
dispatch_get_context

在dispatch queue中设置,提取自定义数据。系统并不会理会这些数据,因此,我们需要手动来分配释放或解引用OC对象。而这一个操作,可以放在dispatch queue的finalizer function中执行(类似于类的析构函数)。

Dispatch queue的finalizer function

对于dispatch queue,我们可以设置finalizer function,让Dispatch queue的引用计数为0时,自动调用该函数。 注意,仅当我们为Dispatch queue设置了自定义数据时,finalizer 函数才会被调用。

设置Dispatch queue自定义数据及finalizer function实例:
void myFinalizerFunction(void *context){MyDataContext* theData = (MyDataContext*)context;// Clean up the contents of the structuremyCleanUpDataContextFunction(theData);// Now release the structure itself.free(theData);}dispatch_queue_t createMyQueue(){MyDataContext*  data = (MyDataContext*) malloc(sizeof(MyDataContext));myInitializeDataContextFunction(data);// Create the queue and set the context data.dispatch_queue_t serialQueue = dispatch_queue_create("com.example.CriticalTaskQueue", NULL);if (serialQueue){dispatch_set_context(serialQueue, data);dispatch_set_finalizer_f(serialQueue, &myFinalizerFunction);}return serialQueue;}



将task block提交到Dispatch queue中

将task block提交到dispatch queue有两种方式,同步提交与异步提交,分别对应的函数为
同步提交
dispatch_sync 
dispatch_sync_f

异步提交
dispatch_async 
dispatch_async_f

当然,我们应优先使用异步提交到Dispatch queue,因为同步提交会使我们的线程阻塞直到提交的block完成为止。同时,对于同步提交,可能会造成死锁现象,如在提交的Dispatch serial queue中的task block中再次调用同步提交block到同一个Dispatch serial queue,则必定会造成死锁。对于concurrent queue,Apple官方文档也不建议在同一个queue中调用同步提交函数。

关于GCD的其他

利用并发queue优化循环操作

Apple文档还提到,当我们在使用循环时,如果每次循环的结果间是相互独立并且循环的执行顺序没有要求,那么可以使用
dispatch_apply
dispatch_apply_f
函数来优化循环操作。这两个函数会根据循环次数,提交若干block或函数到指定的concurrent queue中,这样,每个循环逻辑就能够并发的执行了。官方示例如下:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dispatch_apply(count, queue, ^(size_t i) {printf("%u\n",i);});

当然,这种优化并不是一定的,毕竟操作queue也是需要系统开销的, 应根据具体情况而定。
Queue Task的内存管理
Queue Task自带autorelease pool,因此所有的OC对象均会自动释放,但系统并不保证何时会自动释放这些对象。因此,如果我们的程序对内存使用还是比较敏感,应自己再写需要的autorelease pool。

暂停与恢复Queue

我们可以控制Dispatch queue的暂停与恢复,它们分别对应如下函数:
dispatch_suspend
dispatch_resume
上面两个函数会对应的增加或减少Dispatch queue的暂停引用计数。因此,要让Dispatch queue恢复运行,还需要调用与dispatch_suspend相对应次数的dispatch_resume函数。

通过Dispatch Semaphores来代替传统的Semaphores

类似于传统信号量,我们也可以通过Dispatch Semaphores来对于关键资源的访问进行控制。但是GCD使用的Semaphores更为高效,因为GCD仅当真正需要阻塞线程来等待信号量时才会真正与系统内核层交互,减少了系统消耗。 系统运用dispatch semaphores步骤如下:
  1. When you create the semaphore (using the dispatch_semaphore_create function), you can specify a positive integer indicating the number of resources available.

  2. In each task, call dispatch_semaphore_wait to wait on the semaphore.

  3. When the wait call returns, acquire the resource and do your work.

  4. When you are done with the resource, release it and signal the semaphore by calling thedispatch_semaphore_signal function.

以下是Apple的官方实例
// Create the semaphore, specifying the initial pool sizedispatch_semaphore_t fd_sema = dispatch_semaphore_create(getdtablesize() / 2);// Wait for a free file descriptordispatch_semaphore_wait(fd_sema, DISPATCH_TIME_FOREVER);fd = open("/etc/services", O_RDONLY);// Release the file descriptor when doneclose(fd);dispatch_semaphore_signal(fd_sema);

使用起来还是 比较简单的。

利用Dispatch group来等待Dispatch queue中的任务结束

有时候,我们的下一步操作需要上一步任务的结果,但在并发执行的情况下,我们就需要在程序的某个点停下来,等待或询问那个并发执行的任务是否已经完成。对于提交到concurrent queue中的任务,IOS提供了Dispatch group来进行这种等待或询问。
官方实例如下:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dispatch_group_t group = dispatch_group_create();// Add a task to the groupdispatch_group_async(group, queue, ^{// Some asynchronous work});// Do some other work while the tasks execute.// When you cannot make any more forward progress,// wait on the group to block the current thread.dispatch_group_wait(group, DISPATCH_TIME_FOREVER);// Release the group when it is no longer needed.dispatch_release(group);

利用Dispatch group进行等待,一般步骤如下
1、同往常一样,获取concurrent queue。
2、创建Dispatch group对象。
3、通过dispatch_group_async函数,将任务提交到Dispatch queue中,同时,将task,dispatch queue与dispatch group相联系起来。
4、在适当的时刻,调用dispatch_group_wait函数等待提交到dispatch queue中的任务完成。

Dispatch queue与线程安全

虽然Dispatch queue由系统控制,但是对于dispatch queue的线程安全,Apple还是希望你知道如下几点:
1、Dispatch queue本身是线程安全的。即是说,你可以在多个线程中同时操作dispatch queue,而不必进行线程同步操作。系统会为你代劳。
2、如前面提到过的,不要在同一个serial queue中调用dispatch_sync函数提交任务,这显而易见会造成死锁
3、尽量不要在提交的任务中使用锁,这样会降低dispatch queue的效率。可能的话考虑使用 serial queue来代替锁。
4、最后一条不是很理解是为什么,先抄录在这里吧
Although you can obtain information about the underlying thread running a task, it is better to avoid doing so. For more information about the compatibility of dispatch queues with threads, seeCompatibility with POSIX Threads.

参考文献:
https://developer.apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW23

这篇关于IOS并发编程——Grand Center Dispatch的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1140529

相关文章

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

高并发环境中保持幂等性

在高并发环境中保持幂等性是一项重要的挑战。幂等性指的是无论操作执行多少次,其效果都是相同的。确保操作的幂等性可以避免重复执行带来的副作用。以下是一些保持幂等性的常用方法: 唯一标识符: 请求唯一标识:在每次请求中引入唯一标识符(如 UUID 或者生成的唯一 ID),在处理请求时,系统可以检查这个标识符是否已经处理过,如果是,则忽略重复请求。幂等键(Idempotency Key):客户端在每次

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

Go Playground 在线编程环境

For all examples in this and the next chapter, we will use Go Playground. Go Playground represents a web service that can run programs written in Go. It can be opened in a web browser using the follow

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

函数式编程思想

我们经常会用到各种各样的编程思想,例如面向过程、面向对象。不过笔者在该博客简单介绍一下函数式编程思想. 如果对函数式编程思想进行概括,就是f(x) = na(x) , y=uf(x)…至于其他的编程思想,可能是y=a(x)+b(x)+c(x)…,也有可能是y=f(x)=f(x)/a + f(x)/b+f(x)/c… 面向过程的指令式编程 面向过程,简单理解就是y=a(x)+b(x)+c(x)

【iOS】MVC模式

MVC模式 MVC模式MVC模式demo MVC模式 MVC模式全称为model(模型)view(视图)controller(控制器),他分为三个不同的层分别负责不同的职责。 View:该层用于存放视图,该层中我们可以对页面及控件进行布局。Model:模型一般都拥有很好的可复用性,在该层中,我们可以统一管理一些数据。Controlller:该层充当一个CPU的功能,即该应用程序

Java并发编程之——BlockingQueue(队列)

一、什么是BlockingQueue BlockingQueue即阻塞队列,从阻塞这个词可以看出,在某些情况下对阻塞队列的访问可能会造成阻塞。被阻塞的情况主要有如下两种: 1. 当队列满了的时候进行入队列操作2. 当队列空了的时候进行出队列操作123 因此,当一个线程试图对一个已经满了的队列进行入队列操作时,它将会被阻塞,除非有另一个线程做了出队列操作;同样,当一个线程试图对一个空