【iOS】GCD再学

文章目录

  • 前言
  • GCD概要
    • 什么是GCD
    • 多线程编程
  • GCD的API
    • Dispatch Queue
    • dispatch_queue_create
    • Main Dispatch Queue/Global Dispatch Queue
    • dispatch_set_target_queue
    • dispatch_after
    • Dispatch Group
    • dispatch_barrier_async
    • dispatch_sync
    • dispatch_apply
    • dispatch_suspend/dispatch_resume
    • Dispatch Semaphore
    • dispatch_once
    • Dispatch I/0
  • GCD实现
    • Dispatch Source


前言

这周继续学GCD


提示:以下是本篇文章正文内容,下面案例可供参考

GCD概要

什么是GCD

Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需定义想要执行的任务追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。线程管理是作为系统的一部分实现的,因此可统一管理,也可执行任务,这样就比之前的线程更有效率

dispatch_async(queue, ^{
            //长时间处理
            //例如AR用画像识别
            //数据库访问
            
            
            
            //长时间处理结束,主线程使用这个处理结果
            dispatch_async(dispatch_get_main_queue(), ^{
                //只在主线程可以执行的处理
                //例如用户界面更新
            })})

上面的就是在后台中执行长时间处理,处理结束时,主线程使用该处理结果的源代码。


dispatch_async(queue, ^{

这行代码标识让处理在后台执行
这样,仅一行代码就能让处理在主线程中执行,GCD使用了之前讲的Blocks进一步简化了应用程序代码
导入GCD前,Cocoa框架提供了NSObject类的performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg实例方法和perforSelectorOnMainThread实例方法等简单的多线程编程技术。

- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg

请添加图片描述

- (void)doWork {
    NSAutoreleasePool* pool = [NSAutoreleasePool alloc] init];


	//长时间处理
	//例如AR用画像识别
    //数据库访问
            


	//长时间处理结束,主线程使用这个处理结果
	[self perfoemSelectorOnMainThread:@selector(doneWork) withObject:nil waitUntilDone:NO];

	[pool drain];
}
- (void)doneWork {
	//只在主线程中可以执行的处理
	//如用户界面更新
}

performSelector系方法确实要比使用NSThread类进行多线程编程简单,但与之前使用GCD的源代码相比,结果一目了然。相比performSelector方法,GCD更简洁,如果使用GCD,不仅不必使用NSThread类或performSelector方法这些过时的API,可以通过GCD提供的系统级线程管理提高执行效率。

多线程编程

请添加图片描述
在Objective-C的if语句和for语句等控制语句或函数的调用的情况下,执行命令列的地址会远离当前的地址(位置迁移),但是,一个cpu一次只能执行一个命令,不能执行某处分开的并列的两个命令,因此通过CPU主席i报告的CPU命令列就好比一条无分叉的大道。其执行不会出现分歧。请添加图片描述
一个CPU执行的CPU命令列为一条无分叉路径。即为线程。
现在一个物理的CPU芯片实际上有64个CPU,如果1个CPU核虚拟为2个CPU核工作,那么一台计算机上使用多个CPU核就是理所当然的事情了。“1个CPU核执行的CPU命令列为1条无分叉路径”仍然不变。
这种无分叉路径不止有1体哦啊,存在多条时即为多线程。在多线程中,1个cpu核执行多条不同路径上的不同命令请添加图片描述
OS X和iOS的核心XUN内核在发生操作系统事件时会切换执行路径。执行中路径的状态,例如CPU的寄存器等信息保存到各自路径专用的内存块中,从切换目标路径专用的内存块中,复原CPU寄存器等信息,继续执行切换路径的CPU命令列,这被称为“上下文切换”
由于使用多线程的程序可以在某个线程和其他线程之间反复多次进行上下文切换,因此看上去就好像1个CPU核能够并列地执行多个线程一样。而且在具有多个CPU核的情况下,就不是看上去像了,而是真的提供了多个CPU核并行执行多个线程的技术
这种利用多线程编程的技术就被称为“多线程编程”
但是,多线程编程实际上是一种容易发生各种问题的编程技术,比如多个线程更新相同的资源会导致数据的不一致(数据竞争),停止等待事件的线程会导致多个线程相互持续等待(死锁)。使用太多线程会消耗大量内存等
使用多线程编程可以保证应用程序的响应性能
应用程序启动时,使用主线程来描绘用户界面,处理触摸屏幕的事件等。如果在该主线程中进行长时间的处理,如AR用画像的识别或数据库的访问,就会妨碍主线程的执行(阻塞)。在OS X和iOS的应用程序中,会妨碍主线程中被称为RunLoop的主循环的执行,从而导致不能更新用户界面,应用程序的画面长时间停滞等问题
这就是长时间的处理不在主线程中执行而在其他线程中执行的原因
请添加图片描述
使用多线程编程,在执行长时间的处理仍可保证用户界面的响应功能
GCD简化了偏于复杂的多线程编程的源代码。

GCD的API

Dispatch Queue

苹果官方对GCD的说明:开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中

dispatch_async(queue, ^{
            //想执行的任务
        });

该源代码使用Block语法“定义想执行的任务”,通过dispatch_async函数最佳赋值在变量queue的“Dispatch Queue”中,仅这样就可使指定的Block在另一线程中执行
“Ddispatch Queue”是执行处理的等待队列。应用程序编程人员通过dispatch_async函数等API,在Block语法中记述想执行的处理并将其追加到Dispatch Queue中。Dispatch Queue按照追加的顺序(先进先出FIFO,First- In- First- Out)执行处理

请添加图片描述
另外在执行处理时存在两种Dispatch Queue,一种是等待现在执行中处理的Serial Dispatch Queue,另一种是不等待现在执行中处理的Concurrent Dispatch Queue。

Dispatch Queue的种类说明
Serial Dispatch Queue等待现在执行中处理结果
Concurrent Dispatch Queue不等待现在执行中处理结果

请添加图片描述
比较这两种Dispatch Queue。
请添加图片描述
当变量queue为Serial Dispatch Queue时,因为要等待现在执行中的处理结束,所以首先执行blk0,blk0执行结束后,接着执行blk1,blk1结束后再开始执行blk2,如此重复。同时执行的处理数只能有1个,即执行该源代码后,一定按照以下顺序进行处理

blk0
blk1
blk2
blk3
blk4
blk5
blk6
blk7

当变量queue为Concurrent Dispatch Queue时,因为不用等待现在执行中的处理结束,所以首先执行blk0,不管blk0的执行是否结束,都开始执行blk1,不管blk1是否执行结束,都开始执行blk2
这样虽然不用等待处理结束,可以并行执行多个处理,但并行执行的处理数量取决于当前系统的状态,即iOS和OS X基于Dispatch Queue中的处理数,CPU核数以及CPU负荷等当前系统的状态来决定Concurrent Dispatch Queue中并行执行的处理数。所谓“并行执行”就是使用多个线程同时执行多个处理请添加图片描述
XUN内核决定应当使用的线程数,并且只生成岁许线程执行处理。另外,当处理结束,应当执行的处理数减少时,XUN内核会结束不再需要的线程。XUN内核仅使用Concurrent Dispatch Queue便可管理并行执行多个处理的线程
请添加图片描述
假设准备4个Concurrent Dispatch Queue用线程,首先blk0在线程0中开始执行,接着blk1在线程1中,blk2在线程2中,blk3在线程3中开始执行。线程0中blk0执行结束后开始执行blk4,由于线程1中blk1的执行没有结束,因此线程2中blk2执行结束后开始执行blk5,就这样循环往复
像这样在Concurrent Dispatch Queue中执行处理时,执行顺序会根据处理内容和系统状态发生改变,它不同于执行顺序固定的Serial Dispatch Queue,在不能改变执行状态的处理顺序或不想并行执行多个处理时使用serial Dispatch Queue
如何得到Dispatch Queue?

dispatch_queue_create

第一种方法是通过GCD的API生成Dispatch Queue
通过dispatch_queue_create函数可以生成Dispatch Queue
生成Serial Dispatch Queue

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MyserialDispatchQueue", NULL);

在dispatch_queue_create函数之前,先说一下Serial Dispatch Queue生成个数的注意事项。
如前所述,Concurrent Dispatch Queue并行执行多个追加处理,而Serial Dispatch Queue同时只能执行1个追加处理,虽然Secrial Dispatch Queue和Concurrent Dispatch Queue受到系统资源的限制,但用dispatch_queue_create函数可生成任意多个Dispatch Queue
当生成多个Serial Dispatch Queue时,各个Serial Dispatch Queue将并行执行。虽然在1个Serial Dispath Queue中同时只能执行一个追加处理,但如果将处理分别追加到4个Serial Dispatch Queue中,各个Serial Dispatch Queue执行1个,即为同时执行4个处理。请添加图片描述
一旦生成Serial Dispatch Queue并追加处理,系统对于1个Serial Dispatch Queue就只生成并使用一个线程,如果生成2000个Serial Dispatch Queue,那么就生成2000个线程
像之前列举的多线程编程一样,如果过多使用多线程,就会大量消耗内存,引起大量的上下文切换,大幅度降低系统的相应性能。请添加图片描述
为了避免多线程编程的问题之一–多个线程更新相同资源导致数据竞争时使用Serial Dispatch Queue。请添加图片描述
但是Serial Dispatch Queue的生成个数应当仅限所必需的数量。虽然“Serial Dispatch Queue 比Concurrent Dispatch Queue能生成更多的线程”,但绝对不能激动之下大量生成Serial Dispatch Queue
当想并行执行不发生数据竞争等问题的处理时,使用Concurrent Dispatch Queue,而且对于Concerrent Dispatch Queue来说,不管生成多少,由于XUN内核只使用有效管理的线程,因此不会发生Serial Dispatch Queue的那些问题
dispatch_queue_create函数,该函数的第一个参数指定Serial Dispatch Queue的名称,Dispatch Queue的名称推荐使用的应用程序ID这种逆序全程域名(FQDN,fully qualified domain name)。
生成Serial Dispatch Queue时,像该源代码那样,将第二个参数置为NULL,生成Concurrent Dispatch Queue时,像下面一样,指定为DISPATCH_QUEUE_CONCURRENT


dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.example.gcd.MyConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);

dispatch_queue_create函数的返回值为Dispatch Queue的“dispatch_queue_t类型”

dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.example.gcd.MyConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(myConcurrentDispatchQueue, ^{
            NSLog(@"block on myConcurrentDispatchQueue");
        });

尽管有ARC这一通过编译器自带的内存管理的优秀技术,但生成的Dispatch Queue必须由程序员释放,因为Dispatch Queue并没有像Block那样具有作为Objective-C对象来处理的技术
通过dispatch_queue_create函数生成的Dispatch Queue在使用结束后通过dispatch——release函数释放

dispatch_release(myConcurrentDispatchQueue);

该名称中含有release,由此可推断出相应的存在dispatch——retain函数

dispatch_retain(myConcurrentDispatchQueue);

即Dispatch Queue也像Objective-C的引用计数内存管理一样,需要通过dispatch_retain函数和dispatch_releas函数的引用计数来管理内存,在前面的源代码中,需要释放通过diapatch_queue_create函数生成并赋值给变量myConcurrentDispatchQueue中的Concurrent Dispatch Queue

dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.example.gcd.MyConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(myConcurrentDispatchQueue, ^{
            NSLog(@"block on myConcurrentDispatchQueue");
        });
        dispatch_async(queue, ^{
            NSLog(@"block on myConcurrentDispatchQueue");
        });
        dispatch_release(myConcurrentDispatchQueue);

虽然Concurrent Dispatch Queue时使用多线程执行追加的处理,但像该例这样,在dispatch_async函数中追加Block到Concurrent Dispatch Queue,并立即通过dispatch_release函数进行释放
该block通过dispatch_retain函数持有Dispatch Queue
在dispatch_async函数中追加Block到Dispatch Queue后,即使立即释放Dispatch Queue,该Dispatch Queue由于被Block所持有也不会被废弃,因而Block能够执行
名称中含有“create”的API在不需要其生成的对象时,有必要通过diapatch_release函数进行释放

Main Dispatch Queue/Global Dispatch Queue

第二种方法是获取系统标准提供的Dispatch Queue
实际上不同特意生成Dispatch Queue系统也会给我们提供几个。那就是Main Dispatch Queue和Global Dispatch Queue
Main Dispatch Queue正如其名称中含有的Main一样,是在主线程中执行的Dispatch Queue,因为主线程只有1个,所以Main Dispatch Queue自然就是Serial Dispatch Queue。
追加到Main Dispatch Queue的处理在主线程的RunLoop中执行,由于在主线程中执行,因此要将用户界面的界面更新等一些必须在主线程中执行的处理追加到Main Dispatch Queue中使用。正好与NSObject类的performSelectorOnMainThread实例方法这一执行方法相同。请添加图片描述
另一个Global Dispatch Queue是所有的应用程序都能够使用的Concurrent Dispatch Queue。没有必要通过dispatch_queue_create函数逐个生成Concurrent Dispatch Queue。只要获得Global Dispatch Queue使用即可。
Global Dispatch Queue有四个优先级,高优先级(High Priority),默认优先级(Default Priority),低优先级(Low Priority)和后台优先级(Background Priority)在向Global Dispatch Queue中追加处理时,应选择与处理内容对应的执行优先级的Global Dispatch Queue。
请添加图片描述


        //Mian Dispatch Queue的获取方法
        dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
        
        //Global Dispatch Queue(高优先级)的获取方法
        dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
        
        //Global Dispatch Queue(默认优先级)的获取方法
        dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        //Global Dispatch Queue(低优先级)的获取方法
        dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
        
        //Global Dispatch Queue(后台优先级)的获取方法
        dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

对于Main Dispatch Queue和Global Dispatch Queue执行dispatch_retain函数和dispatch_release函数不会引起任何变化,也不会有任何问题。这是获取并使用Global Dispatch Queue比生成,使用,释放Concurrent Dispatch Queue更轻松的原因。
源代码上在进行类似通过dispatch_queue_create函数生成Dispatch Queue的处理更轻松时,可以直接在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_set_target_queue

dispatch_queue_create函数生成的Dispatch Queue不管是Serial Dispatch Queue还是Concurrent Dispatch Queue,都使用与默认优先级Global Dispatch Queue相同执行优先级的线程。而变更生成的Dispatch Queue的执行优先级要使用dispatch_set_target_queue函数。在后台执行动作处理的Serial Dispatch Queue的生成方法如下:
请添加图片描述
将Dispatch Queue指定为dispatch_set_target_queue函数的参数,不仅可以变更Dispatch Queue的执行优先级,还可以作为Dispatch Queue的执行阶层。如果在多个Serial Dispatch Queue中用dispatch_set_target_queue函数指定木匾为某一个Serial Dispatch Queu,那么原先本应执行的多个Serial Dispatch Queue,在目标Serial Dispatch Queue上只能同时执行一个处理请添加图片描述
在必须将不可并行执行的处理追加到多个Serial Dispatch Queue中时,如果使用dispatch_set_target_queue函数将目标指定为某一个Serial Dispatch Queue,即可防止处理并行执行

dispatch_after

想在指定时间后执行处理的情况,可使用dispatch_after函数来实现
在3秒后将指定的Block追加到Main Dispatch Queue中的代码

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC);
        dispatch_after(time, dispatch_get_main_queue(), ^{
            NSLog(@"wait at least three seconds");
        });

dispatch_after函数并不是在指定时间后执行处理,而只是在指定时间追加处理到Dispatch Queue,此源代码与在3秒后用dispatch_async函数追加Block到Main Dispatch Queue的相同。
Main Dispatch Queue在主线程的RunLoop中执行,所以在比如每隔1/60秒执行的RunLoop中,Block最快在3秒后执行,最慢在3秒+1/60秒后执行,并且在Main Dispatch Queue有大量处理或追加主线程的处理本身有延迟,这个时间长。
第二个参数指定时间用的dispatch_time_1类型的值。该值使用dispatch_time函数或dispatch_waltime函数作成。
第一个参数是指定时间用的dispatch_time_t类型的值。该值使用dispatch_time函数或dispatch_walltime函数做成
dispatch_time函数能够获得从第一个参数dispatch_time_t类型值中指定的时间开始,到第二个参数指定的毫微秒单位时间后的时间。第一个参数经常使用的值是之前源代码中出现的DISPATCH_TIME_NOW。这表示现在的时间。以下源代码可得到表示从现在开始1秒后的dispatch_time_t类型的值


        dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC);

dispatch_walttime函数由POSIX中使用的struct timespec类型的时间得到dispatch_time_t类型的值。dispatch_time函数通常用于计算相对时间,而dispatch_walttime函数用于计算绝对时间

Dispatch Group

在追加到Dispatch Queue中的多个处理全部结束后想执行结束处理,这种情况会经常出现,只使用一个Serial Dispatch Queue时,只要将想执行的处理全部追加到该Serial Dispatch Queue中并在最后追加结束处理,即可实现,但是在使用Concurrent Dispatch Queue时或同时使用多个Dispatch Queue时,源代码会变得非常复杂
在此种情况下使用Dispatch Group。追加三个Block到Global Dispatch Queue,这些Block如果全部执行完毕,就会执行Main Dispatch Queue中结束处理用的Block

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");
        });
        dispatch_release(group);

因为向Global Dispatch Queue即Concurrent Dispatch Queue追加处理,多个线程并行执行,所以追加处理的执行顺序不定。执行时会发生变化,但是此执行结果的done一定是最后输出的
无论向什么样的Dispatch Queue中追加处理,使用Dispatch Group都可以监视这些执行处理的结束,一旦检测到所有处理执行结束,就可将结束的处理追加到Dispatch Queue中,这就是使用Dispatch Group的原因
首先dispatch_group_create函数生成dispatch_group_t类型的Dispatch Group,如dispatch_group_create函数所含的create所示,该Dispatch Group与Dispatch Queue相同,使用过后要通过release函数释放。
与追加Block到Dispatch Queue时相同,Block通过diapstch_retain持有Dispatch Group,使得该Block属于Dispatch Group,如果该Block执行结束,该Block就通过dispatch_release函数释放持有的Dispatch Group,一旦Dispatch Group使用结束,就不用考虑该Dispatch Group的Blokc,通过dispatch_release函数释放即可。

dispatch_barrier_async

在访问数据库或者写入文件时,使用Serial Dispatch Queue可避免数据竞争的问题,写入处理确实不可与其他的读取处理并行执行,但是如果读取处理只是可读取处理并行执行,那么多个并行执行就不会发生错误。
也就是说,为了高效率的进行访问,读取处理追加到Concurrent Dispatch Queue中,写入处理在任一个读取处理没有执行的情况下,追加到Serial Dispatch Queue中即可。
dispatch_barrier_async和Concurrent Dispatch Queue一起使用。

dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.ForBarrier", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queue, blk0_for_reading);
        dispatch_async(queue, blk1_for_reading);
        dispatch_async(queue, blk2_for_reading);
        dispatch_async(queue, blk3_for_reading);
        dispatch_async(queue, blk4_for_reading);
        dispatch_async(queue, blk5_for_reading);
        dispatch_async(queue, blk6_for_reading);
        dispatch_async(queue, blk7_for_reading);
        dispatch_release(queue);


如果只是简单的在dispatch_async函数中国加入写入处理,那么根据Concurrent Dispatch Queue的性质,就有可能在追加到写入处理前面的处理中读取到和期望不一样的树枝。还可能因为非法访问数据导致程序异常。如果追加多个写入处理,可能导致数据竞争。
因此我们采用dispatch_barrier_async函数,这个函数会等待追加到Concurrent Dispatch Queue上的并行执行的处理全部结束之后,再将指定的处理追加到该Concurrent Dispatch Queue中,然后由dispatch_barrier_async函数追加的处理执行完毕后,Concurrent Dispatch Queue才恢复为一般的动作。追加到该Concurrent Dispatch Queue的处理又开始并行执行。请添加图片描述
使用Concurrent Dispatch Queue和dispatch_barrier_async函数可实现高效率的数据库访问和文件访问。

dispatch_sync

dispatch_async函数的async意味着“非同步”就是将指定的Block“非同步”地追加到指定的Dispatch Queue中,dispatch_async函数不做任何等待。请添加图片描述
“sync”意味着“同步”,就是将指定的Block“同步”追加到指定的Dispatch Queue中,在追加Block结束之前,dispatch_sync函数会一直等待。请添加图片描述
dispatch_group_wait函数,“等待”意味着当前线程停滞。
假设执行Main Dispatch Queue时,使用另外的线程Global Dispatch Queue进行处理,处理结束后立即使用得到的结果,这种情况下就要使用dispatch_sync函数。
一旦调用dispatch_sync函数,那么在指定的处理执行结束之前,该函数不会反悔。dispatch_sync函数可以简化代码,也可以说是简易版的dispatch_group_wait函数。
因为dispatch_sync函数使用简单,所以也容易引起问题,死锁

dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_sync(queue, ^{
            NSLog(@"Hello");
        });

这两行代码在Main Dispatch Queue即主线程中执行指定的Block,并等待其执行结束。其实主线程中正在执行这些源代码,所以无法执行追加到Main Dispatch Queue的Blokc。

dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_async(queue, ^{dispatch_sync(queue, ^{
            NSLog(@"hello");
        });
        });

Main Dispatch Queue中执行的Block等待Main Dispatch Queue中要执行的Block执行结束
Serial Dispatch Queue也会引起一样的问题

dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
        dispatch_async(queue, ^{
            NSLog(@"hello");
        });

也有dispatch_barrier_sync函数。dispatch_barrier_async函数的作用是等待追加的全部处理执行结束后,在追加处理到Dispatch Queue中,它还和dispatch_sync函数相同,会等待追加处理的执行结束。

dispatch_apply

dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API。该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行完毕

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_apply(10, queue, ^(size_t index){
            NSLog(@"%zu",index);
        });
        NSLog(@"done");

请添加图片描述
因为在Global Dispatch Queue中执行处理,所以各个处理的执行时间不定,但是最后的done一定在最后。因为dispatch_apply函数会等全部执行处理结束。
第一个参数为重复次数,第二个参数为追打对象的Dispatch Queue,第三个参数为追加的处理,与到目前为止所出现的例子并不一样,第三个参数Block为带有参数的Block,这是为了按第一个参数重复追加Blokc并区分各个Block而使用,例如要对NSArray类对象的所有元素执行处理时不需要一个个写for循环
由于dispatch_apply函数也和dispatch_sync函数相同,会等待处理结束,因此推荐在dispatch_async函数中非同步地执行dispatch_apply函数。

dispatch_suspend/dispatch_resume

当追加大量处理到Dispatch Queue时,在追加处理的过程中,有时希望不执行已追加的处理,例如演算结果被Block截获时,一些处理会对这个演算结果造成影响。
这种情况下,挂起Dispatch Queue即可。当可以执行时在恢复。
dispatch_suspend函数挂起指定的Dispatch Queue

dispatch_suspend(queue);

dispatch_resume函数恢复指定的Dispatch Queue

dispatch_resume(queue);

这些函数对已经执行的处理没有任何影响,挂起后,追加到Dispatch Queue中但尚未执行的处理在此之后停止执行,而恢复则使得这些处理能够继续执行。

Dispatch Semaphore

当并行执行的处理更新数据时,会产生数据不一致的情况,有时应用程序还会异常结束,虽然使用Serial Dispatch Queue和dispatch_barrier_async函数可以避免这类问题,但有必要进行更细粒度的排他控制。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        NSMutableArray* array = [[NSMutableArray alloc] init];
        for (int i = 0; i < 10000; ++i) {
            dispatch_async(queue, ^{
                [array addObject:[NSNumber numberWithInt:i]];
            });
        }

因为使用Global Dispatch Queue更新NSMutableArray类对象,所以执行后由内存错误导致应用程序异常结束的概率很高,此时应该使用Dispatch Semaphone
Dispatch Semaphone是持有计数的信号,该计数是多线程编程中的技术类型信号。所谓信号,类似于过马路时常用的手旗,不可通过时放下手旗,而在Dispatch Semaphone中,使用计数功能来实现这个功能,计数为0时等待,计数为1或大于1时,减去1而不等待。

        dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

参数表示计数的初始值。该函数必须通过dispatch_release函数释放,也可以通过dispatch_retain函数持有。

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

dispatch_semaphore_wait函数等待Dispatch Semaphore的计数值达到大于或等于1,当计数值大于等于1,或者在待机中计数值大于等于1时,对该计数进行减法并从dispatch_semaphore_wait函数返回,第二个参数和dispatch_group_wait函数相同,由dispatch_time_t类型值指定等待时间。上边例子中意味着永久等待。另外,dispatch_semaphore_wait函数的返回值也和dispatch_group_wait函数相同,当dispatch_semaphore_wait函数返回0时,可以安全地执行需要进行排他控制的处理,该处理结束时通过dispatch_semaphore_signal函数将Dispatch Semaphore的计数值加1。

dispatch_once

dispatch_once是保证应用程序执行中只执行一次的API,单例模式中可以使用dispatch_once函数,通过dispatch_once,该代码即使在多线程中执行也可以保证百分之百安全。
之前的代码在大多数情况下也是安全的,但是在多核CPU中,正在更新表示是否初始化的标志变量时读取,就有可能多次执行初始化处理,而使用dispatch_once函数初始化就不必担心这样的问题

Dispatch I/0

在读取较大文件时,如果将文件分成合适的大小并使用Global Dispatch Queue并列读取的话,应该会比一般的读取速度快不少,实现这一功能的就是Dispatch I/0。
通过Dispatch I/0读写文件时,使用Global Dispatch Queue将1个文件按某个大小read/write

dispatch_async(queue, ^{
//读取多少多少字节
});

分割读取的数据通过使用Dispatch Data可以更为简单的进行结合和分割
请添加图片描述
dispatch_io_create函数生成Dispatch I/0,并制定发生错误时用来执行处理的Block。以及执行Block的Dispatch Queue,dispatch_io_set_low_water函数设定一次读取的大小,dispatch_io_read函数使用Global Dispatch Queue开始并列读取。当每个分割的文件块读取结束时,将含有文件块数据的Dispatch Data传递给dispatch_io_read函数指定的读取结束时回调用的Block,毁掉的Block分析传递过来的Dispatch Data并进行结合处理。
如果向提高文件读取速度,可以尝试使用Dispatch I/0

GCD实现

GCD的Dispatch Queue非常方便,它是如何实现的呢?

  • 用于管理追加的Block的C语言实现的FIFO队列。
  • Atomic函数中实现的用于排他控制的轻量级信号
  • 用于线程管理的C语言层实现的一
    些容器

通常,应用程序中编写的线程管理用的代码要在系统级实现
使用GCD要比使用pthreads和NSThread这些一般的多线程编程API更好,并且,如果使用GCD就不必编写为操作线程反复出现的类似的源代码,而可以在线程中集中实现处理内容,我们尽量多使用GCD或者使用Cocoa框架GCD的NSOperationQueue类等API

请添加图片描述
我们使用的GCD的API全部包含在lidispatch库中的C语言函数。Dispatch Queue通过结构体和链表,被实现为FIFO队列,FIFO队列管理是通过dispatch_async等函数所追加的Block,
Block是先加入Dispatch Continuation这一dispatch_continuation_t类型结构体中,然后在加入FIFO队列。该Dispatch Continuation用于记忆的Block所属的Dispatch Group和其他一些信息,相当于一般所说的执行上下文。
Dispatch Queue可通过dispatch_set_target_queue函数设定,可以设置执行该Dispatch Queue处理的Dispatch Queue为目标,该目标可像串珠子一样,设定多个连接在一起的Dispatch Queue,但是在连接串的最后必须设定为Main Dispatch Queue,或各种优先级的Global Dispatch Queue,或是准备用于Serial Dispatch Queue的各种优先级的Global Dispatch Queue。
Global Dispatch Queue有8种。

  • Global Dispatch Queue(High Priority)
  • Global Dispatch Queue(Default Priority)
  • Global Dispatch Queue(Low Priority)
  • Global Dispatch Queue(Background Priority)
  • Global Dispatch Queue(High Overcommit Priority)
  • Global Dispatch Queue(Low Overcommit Priority)
  • Global Dispatch Queue(Background Overcommit Priority)

优先级中附有Overcommit的Global Dispatch Queue使用在Serial Dispatch Queue中。不管系统状态如何,都会强制生成馅好吃呢个的Dispatch Queue
这8种Global Dispatch Queue各使用1个pthread_workqueue。GCD初始化时,使用pthread_workqueue_create_np函数生成pthread_workqueue
在Global Dispatch Queue中执行Block时,lidispatch从Global Dispatch Queue自身的FIFO队列中取出Dispatch Continuation,调用pthread_workqueue_additem_np函数,将该Global Dispatch Queue自身,符合其优先级的workqueue信息以及为执行Dispatch Continuation的回调函数等传递给参数在这里插入图片描述
pthread_workqueue_additem_np函数使用work_kernreturn系统调用,通知workqueue增加应当执行的项目
workqueue的线程执行pthread_workqueue函数,该函数调用lidispatch的回调函数,在回调函数中执行加入到Dispatch Continuation的Block。
Block执行完毕后,进行通知Dispatch Group结束,释放Dispatch Continuation等处理,开始准备执行加入到Global Dispatch Queue中的下一个Block

Dispatch Source

Dispatch Source是BSD系内核惯有功能kqueue的包装
kqueue是在XUN内核中发生各种事件时,在应用程序编程方执行处理的计数。其CPU负荷很小,尽量不占用资源。kqueue可以说是应用程序处理XUN内核中发生的各种事件的方法中最优秀的一种
请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/3655.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网络安全 2023 年为什么如此吃香?事实原来是这样....

前言由于我国网络安全起步晚&#xff0c;所以现在网络安全工程师十分紧缺。俗话说:没有网络安全就没有国家安全为什么选择网络安全&#xff1f;十四五发展规划建议明确提出建设网络强国&#xff0c;全面加强网络安全保障体系和能力建设&#xff0c;加强网络文明建设&#xff0c…

多线程(三):Thread 类的基本属性

上一个篇章浅浅了解了一下 线程的概念&#xff0c;进程与线程的区别&#xff0c;如何实现多线程编程。 而且上一章提到一个重要的面试点&#xff1a; start 方法和 run 方法的区别。 start 方法是从系统那里创建一个新的线程&#xff0c;这个线程会自动调用内部的run 方法&…

瑟瑟发抖吧~OpenAI刚刚推出王炸——引入ChatGPT插件,开启AI新生态

5分钟学会使用ChatGPT 插件&#xff08;ChatGPT plugins&#xff09;——ChatGPT生态建设的开端ChatGPT插件是什么OpenAI最新官方blog资料表示&#xff0c;已经在ChatGPT中实现了对插件的初步支持。插件是专门为以安全为核心原则的语言模型设计的工具&#xff0c;可帮助ChatGPT…

JSON 教程导读

JSON 教程导读在开始深入了解JSON知识之前&#xff0c;让我们先了解什么是JSON&#xff01;JSON: JavaScript Object Notation(JavaScript 对象表示法) JSON 是存储和交换文本信息的语法&#xff0c;类似 XML。JSON 比 XML 更小、更快&#xff0c;更易解析。JSON实例&#xff1…

CODESYS增量式PID功能块(ST完整源代码)

增量式PID的详细算法公式和博途源代码,请参看下面的文章链接: 博途1200/1500PLC增量式PID算法(详细SCL代码)_博图scl语言pid增量编码器_RXXW_Dor的博客-CSDN博客SMART200PLC增量式PID可以参看下面这篇博文,文章里有完整的增量式PID算法公式,这里不在赘述西门子SMARTPLC增量…

你值得拥有——流星雨下的告白(Python实现)

目录1 前言2 霍金说移民外太空3 浪漫的流星雨展示 4 Python代码 1 前言我们先给个小故事&#xff0c;提一下大家兴趣&#xff1b;然后我给出论据&#xff0c;得出结论。最后再浪漫的流星雨表白代码奉上&#xff0c;还有我自创的一首诗。开始啦&#xff1a;2 霍金说移民外太空霍…

你的应用太慢了,给我司带来了巨额损失,该怎么办

记得很久之前看过谷歌官方有这么样的声明&#xff1a;如果一个页面的加载时间从 1 秒增加到3 秒&#xff0c;那么用户跳出的概率将增加 32%。 但是早在 2012 年&#xff0c;亚马逊就计算出了&#xff0c;页面加载速度一旦下降一秒钟&#xff0c;每年就会损失 16 亿美元的销售额…

杨辉三角形 (蓝桥杯) JAVA

目录题目描述&#xff1a;暴力破解&#xff08;四成&#xff09;&#xff1a;二分法破解&#xff08;满分&#xff09;&#xff1a;题目描述&#xff1a; 下面的图形是著名的杨辉三角形&#xff1a; 如果我们按从上到下、从左到右的顺序把所有数排成一列&#xff0c;可以得到如…

如何编写测试用例?

带着问题学习是最高效的学习方法。 因此&#xff0c;在介绍如何编写测试用例之前&#xff0c;先看一个软件系统登录功能的测试&#xff08;如下截图所示&#xff09;&#xff1a; 要做这个登录页面的测试用例&#xff0c;你会从哪些方面思考进行测试呢&#xff1f; 看似简单的…

【C语言蓝桥杯每日一题】—— 货物摆放

【C语言蓝桥杯每日一题】—— 货物摆放&#x1f60e;前言&#x1f64c;排序&#x1f64c;总结撒花&#x1f49e;&#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢的座右铭&#xff1a;全神贯注的上吧&#xff01;&#xff01;&#xff01; &#x1f60a;作者简介…

图话第一代女性开发者

写在前面的话想问大家一个有趣的问题&#xff0c;大家知道我们程序员圈的第一位女性开发者是谁吗&#xff1f;作为开发者&#xff0c;以前并没有认真去想过这个问题&#xff0c;这两天认真的看了一下百度百科查找了一下相关的专业知识。才知道历史上第一位女性程序员是&#xf…

docker+jenkins+maven+git构建聚合项目,实现自动化部署,走了800个坑

流程 主要的逻辑就是Docker上安装jenkins&#xff0c;然后拉取git上的代码&#xff0c;把git上的代码用Maven打包成jar包&#xff0c;然后在docker运行 这个流程上的难点 一个是聚合项目有可能Maven install的时候失败。 解决办法&#xff1a;在基础模块的pom文件上添加 <…

重谈“协议” + 序列化和反序列化

目录 1、重谈 "协议" 协议的概念 结构化数据的传输 序列化和反序列化 2、网络版计算器 2.1、服务端serverTcp.cc文件 服务端serverTcp.cc总代码 2.2、客户端clientTcp.cc文件 客户端clientTcp.cc总代码 2.3、协议定制Protocol.hpp文件 服务端需要的协议 客户端需要…

惠普官网驱动程序与软件下载,如何安装打印机驱动

惠普&#xff08;HP&#xff09;是一家全球知名的计算机硬件制造商&#xff0c;其产品涵盖台式电脑、笔记本电脑、打印机、扫描仪等。为了保证产品的正常运行和最佳性能&#xff0c;惠普为其设备提供了驱动程序和软件的下载服务。本文将介绍如何在惠普官网下载所需的驱动程序和…

【Linux】 基础IO——文件(中)

文章目录1. 文件描述符为什么从3开始使用&#xff1f;2. 文件描述符本质理解3. 如何理解Linux下的一切皆文件&#xff1f;4. FILE是什么&#xff0c;谁提供&#xff1f;和内核的struct有关系么&#xff1f;证明struct FILE结构体中存在文件描述符fd5. 重定向的本质输出重定向输…

Linux基础

环境搭建&#xff1a;linux安装、远程连接常用命令&#xff1a;文件、目录、拷贝、移动、打包、压缩、文本编辑安装软件&#xff1a;文件上传、jdk、tomcat、mysql项目部署&#xff1a;Java应用、Python应用、日志查看、系统管理、用户权限Linux是一套免费使用、自由传播的操作…

ngx之日志切割

正确记日志方式是每天都进行切割重新写&#xff0c;保留固定的时间后可使用 find 删除。 用系统自带有的 logrotate /etc/logrotate.d 下面再建立一个文件&#xff0c;这里是nginx &#xff08; 中途有 ctrlZ 暂停过任务&#xff0c;后面fg恢复的 &#xff09; /usr/local/ng…

不同类型的电机的工作原理和控制方法汇总

电机控制是指对电机的启动、调速&#xff08;加速、减速&#xff09;、运转方向和停止进行的控制&#xff0c;不同类型的电机有着不同的工作原理和控制方法。 一、无刷电机 无刷电机是由电机主体和电机驱动板组成的一种没有电刷和换向器的机电一体化产品。在无刷电机中&#xf…

【leetcode】链表(2)

目录 1. 环形链表 解题思路 2. 环形链表 II 解题思路 3. 删除排序链表中的重复元素 解题思路 4. 删除排序链表中的重复元素 II 解题思路 5. 移除链表元素 解题思路 6. 链表的中间结点 解题思路 1. 环形链表 OJ&#xff1a;环形链表 给你一个链表的头节点 head &am…

第二章 作业(6789B)【编译原理】

第二章 作业【编译原理】前言推荐第二章 作业678911最后前言 以下内容源自《编译原理》 仅供学习交流使用 推荐 无 第二章 作业 6 6.令文法G6为 N→D|ND D→0|1|2|3|4|5|6|7|8|9 (1)G6的语言L(G6)是什么? (2)给出句子0127、34和568的最左推导和最右推导。 &#xff08;…