GCD

参考文章

《iOS高级编程》中的多线程与GCD
iOS 多线程:『GCD』详尽总结
一个超级会举例子的人的博客

测试用例的github链接

GCDTest

多线程编程

多线程技术

我们知道在iOS开发中,一共有四种多线程技术:pthread,NSThread,GCD,NSOperation:

前两者是面向线程开发的多线程技术,需要开发者自己去维护线程的生命周期,比较繁琐。

后两者是面向队列开发的多线程技术,开发者仅仅定义想执行的任务追加到适当的Dispatch Queue(队列)中并设置一些优先级,依赖等操作就可以了,其他的事情可以交给系统来做。

本段摘自:GCD

进程与线程

进程:

系统中正在运行的每一个应用程序都是一个进程,每个进程系统都会分配给它独立的内存运行。放到我们的iOS系统中,每一个应用都是一个进程。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。

线程:

一个进程的所有任务都在线程中进行,因此每个进程至少要有一个线程,也就是主线程。同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。它是程序执行流的最小单元,是进程中的一个实体,是执行程序最基本的单元,有自己栈和寄存器。
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。多进程是指操作系统能同时运行多个任务(程序)。多线程是指在同一程序中有多个顺序流在执行。

并发和并行

在用户启动程序后,CPU会从程序指定的地址开始,一个一个执行CPU命令列
这样的执行是一条无分叉的路径(可能会迂回,因为有函数调用),但这意味着一个CPU一次只能执行一个命令,不能执行某处分开的并列的两个命令,执行也不会出现分歧
在这里插入图片描述
这样一条无分叉路径本质上就是一个线程

这种无分叉路径不止一条,存在多条时即为“多线程”,在多线程中,“一个CPU核执行多条不同路径上的不同命令”。
在这里插入图片描述

这就是“虚假的多线程”,而时间片即CPU分配给各个程序的时间,每个线程被分配一个时间段,称作它的时间片,即该进程允许运行的时间,使各个程序从表面上看是同时进行的。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。而不会造成CPU资源浪费。在宏观上:我们可以同时打开多个应用程序,每个程序并行不悖,同时运行。但在微观上:由于只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。
并发

现在再引入CPU核概念:现在一个物理的CPU芯片实际上有64个(64核)CPU,如果一个CPU核虚拟为两个CPU核工作,那么一台计算机上使用多个CPU核就是理所当然的事了。尽管如此,“一个CPU命令列为一条无分叉路径”仍然不变。虽然CPU一个机器只有一个,但是我们在里面集成多个CPU核,计算核心,每个CPU核一次能够执行的CPU命令始终为1
由于集成了多个CPU核,现在我们的多线程就不是看起来像多线程那么简单了,而是真正实现了多线程。
也就是实现从并发到并行并行
这种利用多线程编程的技术就叫“多线程编程”

多线程的优缺点

缺点:

多线程编程实际上是一种易发生各种问题的编程技术。

  • 多个线程更新相同资源时会导致数据不一致(数据竞争)
  • 停止等待事件的线程会导致多个线程互相持续等待(死锁)
  • 使用太多线程会消耗大量内存
    缺点

优点

尽管极易发生各种问题,也应该使用多线程编程,因为使用多线程编程可保证程序的响应性能
放在iOS里面,我们通过主线程来描绘用户界面,触摸屏幕事件,假如把后台下载等工作都放在主线程去进行,显然就会导致主线程RunLoop堵塞,应用程序画面长时间停留
在这里插入图片描述

什么是GCD

以下摘自苹果的官方说明。,

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

也就是说,GCD用我们难以置信的非常简洁的记述方法,实现了极为复杂繁琐的多线程编程,可以说这是一项划时代的技术。

GCD实现原理

用于实现DispatchQueue而使用的软件组件
在这里插入图片描述

添加block到DispatchQueue

编程人员所使用GCD的API全部包含在libdispatch库中的C语言函数。DispatchQueue通过结构体和链表被实现为FIFO对列。FIFO队列管理是通过dispatch_async等函数所追加的Block
Block并不是直接加入FIFO队列,而是先加入DispatchContinuation这一dispatch_continuation_t类型结构体中,然后再加入FIFO队列。该Dispatch Continuation用于记忆block所属的DispatchGroup和其他一些信息,相当于一般常说的执行上下文。
(就是dispatchqueue的实现形式是FIFO队列,FIFO队列中保存的是dispatch_continuation_t结构体,而dispatch_continuation_t结构体中保存的是通过添加函数添加到线程的block以及block的一些信息。)

GlobalDispatchQueue中block的执行过程

当在GlobalDIspatchQueue中执行block时,

  • libdispatch从GlobalDispatchQueue自身的FIFO队列中取出DispatchContinuation.
  • 调用pthread_workqueue_additem_np函数,将该GlobalDIspatchQueue自身、符合其优先级的workqueue信息以及执行DispatchContinuation的回调函数等传递给参数。
  • pthread_workqueue_additem_np函数使用workq_kernreturn系统调用,通知位于内核的workqueue增加应当执行的项目。
  • 根据该通知,XUN内核基于系统判断是否要生成线程,如果Overcommit优先级的GlobalDIspatchQueue,wprkqueue则始终生成线程。
  • workqueue的线程执行pthread_workqueue函数,函数调用libdispatch的回调函数,在该回调函数中执行加入到DispatchContinuation的Block。
  • Block执行结束后,进行通知DispatchGroup结束,释放DispathchContinuation,开始准备执行加入到GlobalDIspatchQueue中的下一个Block。

GCD的API

GCD大大简化了偏于复杂的多线程编程的源代码

DispatchQueue

DispatchQueue的种类
在这里插入图片描述

  • SerialDispatchQueue: 串型处理,加到线程的任务不一定能马上处理,要等待现在执行中的处理结束。
  • ConcurrentDispatchQueue:并行执行多个处理(使用多个线程同时执行多个处理),可以不用等待处理结束,但并行处理执行的处理数量取决于当前系统的状态,即iOS和OSX基于DispatchQueue中的处理数、CPU核数以及CPU负荷等当前系统的状态来决定ConcurrentDispatchQueue中并行执行的处理数。

dispatch_queue_creat(创建线程)

创建的线程的内存管理

如果你部署的最低目标低于 iOS 6.0 or Mac OS X 10.8
你应该自己管理GCD对象,使用(dispatch_retain,dispatch_release),ARC并不会去管理它们
如果你部署的最低目标是 iOS 6.0 or Mac OS X 10.8 或者更高的
ARC已经能够管理GCD对象了,这时候,GCD对象就如同普通的OC对象一样,不应该使用dispatch_retain ordispatch_release

dispatch_queue_creat(创建线程)

dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
  • 返回值类型dispatch_queue_t
  • 参数1:const char *_Nullable label:创建的线程的名称
  • 参数2: dispatch_queue_attr_t _Nullable attr):创建的线程类型
    1. 为NULL时——SerialDispatchQueue
    2. 为DISPATCH_QUEUE_CONCURRENT时——ConcurrentDispatchQueue
创建多个线程的问题

在说明dspalch queue create 函数之前,先讲下关于Serial DiptchQuoe生成个数的注意事项。如前所述,Concurrent Dispatch Queue并行执行多个追加处理,而Serial Dispatch Queue同时只能执行1个追加处理。虽然Serial Dispatch Queue和Concurent Dispatch Qucue受到系统资源的限制,但用dispatch queue create 函数可生成任意多个Dispatch Queue。

当生成多个Serial Dispatch Queue时,各个Serial Dispatch Queue将并行执行。虽然在1个Srial Dispatch Queue中同时只能执行一个追 加处理,但如果将处理分别追加到4个SerialDispaich Qucue中,各个Serial Dispach Quue执行1个,即为同时执行4个处理。
在这里插入图片描述
Serial Dispatch Queue的生成个数应当仅限所必需的数量。例如更新数据库时1个表生会1个SerialDispatch Queue, 更新文件时1个文件或是可以分割的1个文件块生成1个SerialDspatch Qucue.虽然“Serial Dispatch Queue比Concuret Dispatch Queue能生成更多的线程”,但绝不能激动之下大量生成Serial Dispatch Queue.

当想并行执行不发生数据竞争等问题的处理时,使用(Concurent Dispatch Queue.而且对于ConcurentDispanohQueue来说,不管生成多少,由于XNU内核只使用有效管理的线程,因此不会发生Serial Dispatch Qucos的那些问题应该置这个问题

下面我们回来继续讲dispatch queue create 函数。该函数的第-一个参数指定Serial DispatchQueue的名称。像此源代码这样,Dispatch Queue的名称推荐使用应用程序ID这种逆序全程域名(FQDN, fully qualifed domain name)。该名称在Xcode和Instruments的调试器中作为DispatchQueue名称表示。另外,该名称也出现在应用程序崩渍时所生成的CrashLog中.我们命名时应遵循这样的原则:对我们编程人员来说简单易懂,对用户来说也要易懂。如果嫌命名麻烦设为NULL也可以,但你在调试中一定会后悔没有为Dispatch Queue署名。

获取线程

系统提供的DispatchQueue
在程序运行的时候,GCD 会初始化 六个 rootqueue 和 一个 mainqueue,这六个 rootqueue 有低中高三种优先级,这些 rootqueue 主要是用来调度任务,我们自己创建的队列其实并不能调配任务,因为我们创建的队列的 do_probe 都是空的,我自己创建的队列都是链接在 rootqueue 下的,利用 rootqueue 来调配任务.

  • mainqueue 是要绑定在 UI 线程的,用来更新界面的,mainqueue 是一种串行队列。 mainqueue 在用户层彰显的就是 dispatch_get_main_queue()
  • rootqueue 彰显的是 dispatch_get_global_queue 。

MainDispatchQueue

dispatch_get_main_queue 会返回主队列,也就是UI队列。它一般用于在其它队列中异步完成了一些工作后,需要在UI队列中更新界面。

主线程中执行的DispatchQueue,因为主线程只有一个,所以MainDispatchQueue为SerialDispatchQueue。在这里插入图片描述

GlobalDispatchQueue

dispatch_get_global_queue 会获取一个全局队列,我们姑且理解为系统为我们开启的一些全局线程。我们用priority指定队列的优先级,而flag作为保留字段备用(一般为0)。

Global Dispatch Queue:是所有应用程序都能够使用的Concurrent Dispatch Queue。有四个执行优先级,高优先级(High Priority)、默认优先级(Default Priority)、低优先级(Low Priority)和后台优先级(Background Priority)。XNU内核管理的用于Global Dispatch Queue的线程,将各自使用的Global Dispatch Queue的执行优先级作为线程的执行优先级使用。向Global Dispatch Queue中追加处理时,要选择与处理内容对应的执行优先级的Global Dispatch Queue。

线程与runloop

  • runloop的作用:
    主线程默认开启runloop,而子线程默认不开启,所以有关runloop的操作,在子线程中不会执行,若想在子线程中执行,则需在子线程中手动创建和开启线程。

  • 举个例子

    dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{
         Person *person = [[Person alloc] init];
         [person performSelector:@selector(say) withObject:nil afterDelay:0];
    });

调用的方法在指定的时间后执行,没有runloop时,无法处理NSTimer事件,所以无法执行。
手动开启runloop

    dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        Person *person = [[Person alloc] init];
        [person performSelector:@selector(say) withObject:nil afterDelay:0];
        [[NSRunLoop currentRunLoop] run];
    });

这样就会执行say方法了。

dispatch_set_target_queue变更队列优先级

dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);

第一个参数是要执行变更的队列(不能指定主队列和全局队列),第二个参数是目标队列。

第一个参数如果指定系统提供的MainDispatchQueue和GlobalDispatchQueue,则不知道会发生什么状况,一次均不可指定。
dispatch_set_target_queue 函数有两个作用:第一,变更队列的执行优先级;第二,目标队列可以成为原队列的执行阶层。

//首先创建5个串行队列
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue1", NULL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue2", NULL);
dispatch_queue_t serialQueue3 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue3", NULL);
dispatch_queue_t serialQueue4 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue4", NULL);
dispatch_queue_t serialQueue5 = dispatch_queue_create("com.gcd.setTargetQueue2.serialQueue5", NULL);

//每个队列上输出一个数字
dispatch_async(serialQueue1, ^{
    NSLog(@"1");
});
dispatch_async(serialQueue2, ^{
    NSLog(@"2");
});
dispatch_async(serialQueue3, ^{
    NSLog(@"3");
});
dispatch_async(serialQueue4, ^{
    NSLog(@"4");
});
dispatch_async(serialQueue5, ^{
    NSLog(@"5");
});

//创建目标串行队列
dispatch_queue_t targetSerialQueue = dispatch_queue_create("com.gcd.setTargetQueue2.targetSerialQueue", NULL);

//设置执行阶层
dispatch_set_target_queue(serialQueue1, targetSerialQueue);
dispatch_set_target_queue(serialQueue2, targetSerialQueue);
dispatch_set_target_queue(serialQueue3, targetSerialQueue);
dispatch_set_target_queue(serialQueue4, targetSerialQueue);
dispatch_set_target_queue(serialQueue5, targetSerialQueue);

//执行操作
dispatch_async(serialQueue1, ^{
    NSLog(@"1");
});
dispatch_async(serialQueue2, ^{
    NSLog(@"2");
});
dispatch_async(serialQueue3, ^{
    NSLog(@"3");
});
dispatch_async(serialQueue4, ^{
    NSLog(@"4");
});
dispatch_async(serialQueue5, ^{
    NSLog(@"5");
});

创建五个串型队列,从一到五,每个队列的任务是打印队列数字,打印结果乱序,给他们指定执行阶层,则他们的优先级被安排在串型目标队列targetSerialQueue上,所以可以有序执行。
在必须将不可并行执行的处理追加到多个SerialDIspatchQueue中时,如果使用dispatch_target_queue函数将目标指定为某一个SerialDispatchQueue,即可防止处理并行执行。

dispatch_after

想在指定时间后执行处理时,可用dispatch_after.

_dispatch_after(dispatch_time_t when, dispatch_queue_t dq,
		void *ctxt, void *handler, bool block)
  • 第一个参数为为dispatch_time_t类型
  • 第二个参数为要追加处理的DispatchQueue
  • 第三个参数为要执行处理的Block

dispatch_time_t:

dispatch_time_t
dispatch_time(dispatch_time_t when, int64_t delta);

用于获取到从第一个参数dispatch_time_t类型值中指定的时间开始,到第二个参数指定的毫微秒单位时间后的时间。

第一个参数常用的DISPATCH_TIME_NOW表示现在的时间。

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

“ull”是C语言的数值字面量,是显示表明类型时使用的字符串。
NSEC_PER_SEC单位:毫微秒
NSEC_PER_MSEC单位:毫秒

tips:

  • dispatch_after函数不是在指定时间后执行处理,而只是在指定时间追加处理到Dispatch Queue(第二个参数)。
  • dispatch_after函数精度问题,有误差,大致延迟执行处理,可以用该函数,严格要求时间时会出现问题。

DispatchGroup

在追加到DispatchQueue中的多个处理全部结束后想执行结束处理,在SerialDispatchQueue中,只要将想执行的处理全部追加到该SerialDispatchQueue中,并在最后追加结束处理即可,而在ConcurrentDispatchQueue或同时使用多个DispatchQueue时,原代码就会变得复杂,此时可以使用DispatchGroup。

  • 在不使用时
dispatch_queue_t queue0 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue0, ^{
        NSLog(@"blk0");
    });
    dispatch_async(queue0, ^{
        NSLog(@"blk1");
    });
    dispatch_async(queue0, ^{
        NSLog(@"blk2");
    });
    NSLog(@"done");

在这里插入图片描述
使用DIspatchGroupe输出就变为有序输出

    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_group_wait

long
dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
  • 第一个参数为目标dispatchGroup
  • 第二个参数为指定的等待时间,为dispatch_time_t类型的值,若为DISPATCH_TIME_FOREVER则表示永久等待
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);

long result = dispatch_group_wait(group, time);

if (result == 0) {
    // 全部处理执行结束
} else {
    // 某一个处理还在执行中
}

其返回值有两种

  • 不为0时:意味着虽然经过了指定的时间,但属于DispatchGroup的某一个处理还在执行中。
  • 为0时:全部处理执行结束

总结:

两种判断方式,dispatch_group_notify和dispatch_group_wait,推荐使用dispatch_group_notify函数追加结束处理到MainDispatchQueue中,这是因为它可以简化源代码。

dispatch_barrier_async

对于并行队列,由于其中有多线程,难以预计任务完成顺序先后,如果需要在确保某些任务完成后执行某项任务,就需要用到栅栏

作用:dispatch_barrier_async函数会等待追加到ConcurrentDispatchQueue上的并行执行的处理全部结束之后,再将指定的处理追加到该ConcurrentDispatchQueu中,且dispatch_barrier_async函数追加的处理执行完毕后,ConcurrentDispatchQueu才恢复一般的动作。

在这里插入图片描述

dispatch_queue_t queue = dispatch_queue_create("com.example.gdc.ForBarrier", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"reading1");
    });

    dispatch_async(queue, ^{
        NSLog(@"reading2");
    });

    dispatch_async(queue, ^{
        NSLog(@"reading3");
    });

//    dispatch_async(queue, ^{
//        NSLog(@"writing");
//    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"writing");
    });

    dispatch_async(queue, ^{
        NSLog(@"reading4");
    });

    dispatch_async(queue, ^{
        NSLog(@"reading5");
    });

在reading3之后修改内容,我们希望reading4和5读到的内容为修改后的,而该线程为ConcurrentDispatchQueue,很可能不是我们想要的结果,此时可以用dispatch_barrier_async。

使用ConcurrentDispatchQueu和dispatch_barrier_async还可以实现高效率的数据访问和文件访问。

dispatch_sync

async意味着非同步,sync意味着同步,在指定的处理结束之前,该函数不会返回。这种等待意味着当前线程停止,也就是说,执行dispatch_sync函数所在的Dispatch Queue的线程停止,等待dispatch_sync函数的Block处理在第一个参数Dispatch Queue中的线程中执行结束。

dispatch_sync函数容易导致死锁

举个🌰:

    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"123");
    });

理解GCD死锁看了这篇文章,我发现viewDidLoad就是一个任务,在viewDidLoad里加任务,会把任务加到队尾,此时viewDidLoad是一个任务,所以队尾在viewDidLoad之后,而不是当前任务处。
我现在有一个任务清单,当前任务是学习GCD,在我学习这个任务时,我觉得要学block才能学GCD,于是我把block加入到我的任务清单。
我希望严格按照任务清单来学习,所以我要学完GCD后再学block,而要学完GCD就必须学习block,所以block等我学完GCD,GCD的完成又需要等待block学完,就造成了互相等待。
在这个例子中,我的任务清单就是上述代码中的主线程queue,学完GCD就是完成把输出123加到我的任务清单(主线程)中并执行,sync就表示我的任务清单必须按顺序执行。在这里插入图片描述
sync源码分析utm_source=blogxgwz4
还有另一种常见的死锁

    dispatch_queue_t sisuoQueue = dispatch_queue_create("sisuoQueue", NULL);
    dispatch_async(sisuoQueue, ^{
        dispatch_sync(sisuoQueue, ^{
            NSLog(@"1");
        });
    });

道理是一样的,此时我的任务清单是sisuoQueue

dispatch_apply

dispatch_apply 函数是dispatch_sync函数和DispatchGroup的关联API,该函数按指定的次数将指定的block追加到指定的DispatchQueue中,并等待全部处理执行结束。

void
dispatch_apply(size_t iterations,
	dispatch_queue_t DISPATCH_APPLY_QUEUE_ARG_NULLABILITY queue,
	DISPATCH_NOESCAPE void (^block)(size_t));
  • 参数1:重复次数
  • 参数2:追加对象的DispatchQueue
  • 参数3:追加的处理

由于dispatch_apply和dispatch_sync函数相同,会等待处理执行结束,因此推荐在dispatch_async函数中非同步的执行dispatch_apply函数。

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

在这里插入图片描述

dispatch_suspend/dispatch_resume

dispatch_suspend(queue);

挂起指定队列

dispatchp_resume(queue);

恢复指定队列

Dispatch Semaphore

当并行执行的处理更新数据时,会产生数据不一致,甚至出现异常结束的情况,使用Serial Dispatch Queue和dispatch_barrier_async函数可避免,但使用Dispatch Semaphore可以进行更细颗粒的排他控制。
Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。
Dispatch Semaphore中,计数为0时等待,计数为1或者大于1时,减去1而不等待。

dispatch_semaphore_t
dispatch_semaphore_create(long value);

其参数表示初始值。

dispatch_semaphore_wait

long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

该函数等待Dispatch Semaphore的计数值达到大于1或者等于1时,或者在待机中计数值大于等于1时,对该计数进行减法并从dispatch_semaphore_wait返回。
第二个参数与dispatch_group_wait函数相同,由dispatch_time_t类型值指定等待时间。当返回值为0时,可安全的执行需要进行排他控制的处理,处理结束时,dispatch_semaphore_single函数将Dispatch Semaphore的计数值加1.

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

NSMutableArray *array = [[NSMutableArray alloc] init];

for (int i = 0; i < 1000; i++) {
    dispatch_async(queue, ^{
        
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        
        [array addObject:[NSNumber numberWithInt:i]];
        
        // 处理结束时通过该函数将semaphore的计数值加1
        dispatch_semaphore_signal(semaphore);
    });
}

用 Dispatch Semaphore实现买票机制

- (void)initTicket {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
    
    self.ticketSurplusCount = 50;
    
    // queue1 代表北京火车票售卖窗口
    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
    // queue2 代表上海火车票售卖窗口
    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
//        [weakSelf saleTicketNotSafe];
        [weakSelf saleTicketSafe];
    });
    
    dispatch_async(queue2, ^{
//        [weakSelf saleTicketNotSafe];
        [weakSelf saleTicketSafe];
    });
}
- (void)saleTicketSafe {
    while (1) {
        // 相当于加锁
        //因为semaphoreLock, 计数为0时等待,计数为1或大于1时,减去1而不等待
        dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
        
        if (self.ticketSurplusCount > 0) {  // 如果还有票,继续售卖
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { // 如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完");
            
            // 相当于解锁
            dispatch_semaphore_signal(semaphoreLock);
            break;
        }
        
        // 相当于解锁
        dispatch_semaphore_signal(semaphoreLock);
    }
}

dispatch_once

dispatch_once函数是保证在程序执行中只执行一次指定处理的API

static int initialized = NO;
if (initialized == NO) {
    // 初始化
    initialized = YES;
}

//说明:
//上面代码,在大多数情况下是安全的,但是在多核CPU中,在正在更新表示是否初始化的标志变量initialized时读取,就有可能多次执行初始化处理
static dispatch_once_t pred;
dispatch_once(&pred, ^{
    //初始化
});

在多线程环境下执行,也可以保证安全

Dispatch I/O

Dispatch I/O和Dispatch Data可以实现输入/输出做到多个线程并列读取。
Dispatch I/O读写文件时,使用Global Dispatch Queue将一个文件按某个大小read/write。
分割读取的数据使用Dispatch Data可以更为简单的进行结合和分割。

// Apple System Log API用的源代码
static int
_asl_auxiliary(aslmsg msg, const char *title, const char *uti, const char *url, int *out_fd)
{
    asl_msg_t *merged_msg;
    asl_msg_aux_t aux;
    asl_msg_aux_0_t aux0;
    fileport_t fileport;
    kern_return_t kstatus;
    uint32_t outlen, newurllen, len, where;
    int status, fd, fdpair[2];
    caddr_t out, newurl;
    dispatch_queue_t pipe_q;
    dispatch_io_t pipe_channel;
    dispatch_semaphore_t sem;
    /* ..... 此处省略若干代码.....*/
    
    // 创建串行队列
    pipe_q = dispatch_queue_create("PipeQ", NULL);
    // 生成Dispatch I/O,指定发生错误时执行处理的Block,以及执行该Block的Dispatch Queue。 
    pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
        close(fd);
    });
    
    *out_fd = fdpair[1];
    
    // 该函数设定一次读取的大小(分割大小)
    dispatch_io_set_low_water(pipe_channel, SIZE_MAX);
    // 使用Global Dispatch Queue并列读取,当每个分割的文件块读取结束,将Dispatch Data传递给回调的Block.
    dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
        if (err == 0) // err等于0 说明读取无误
        {
            // 读取完“单个文件块”的大小
            size_t len = dispatch_data_get_size(pipedata);
            if (len > 0)
            {
                // 定义一个字节数组bytes
                const char *bytes = NULL;
                char *encoded;
                
                dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len);
                encoded = asl_core_encode_buffer(bytes, len);
                asl_set((aslmsg)merged_msg, ASL_KEY_AUX_DATA, encoded);
                free(encoded);
                _asl_send_message(NULL, merged_msg, -1, NULL);
                asl_msg_release(merged_msg);
                dispatch_release(md);
            }
        }
        
        if (done)
        {
            dispatch_semaphore_signal(sem);
            dispatch_release(pipe_channel);
            dispatch_release(pipe_q);
        }
    });
// 异步串行读取文件
NSString *desktop = @"/Users/xxxx/Desktop";
NSString *path = [desktop stringByAppendingPathComponent:@"整理.md"];
dispatch_queue_t queue = dispatch_queue_create("queue", NULL);

dispatch_fd_t fd = open(path.UTF8String, O_RDONLY, 0);
dispatch_io_t io = dispatch_io_create(DISPATCH_IO_STREAM, fd, queue, ^(int error) {
    close(fd);
});

size_t water = 1024 * 1024;
dispatch_io_set_low_water(io, water);
dispatch_io_set_high_water(io, water);

long long fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil].fileSize;
NSMutableData *totalData = [[NSMutableData alloc] init];

dispatch_io_read(io, 0, fileSize, queue, ^(bool done, dispatch_data_t  _Nullable data, int error) {
    
    if (error == 0) {
        size_t len = dispatch_data_get_size(data);
        if (len > 0) {
            [totalData appendData:(NSData *)data];
        }
    }
    
    if (done) {
        NSString *str = [[NSString alloc] initWithData:totalData encoding:NSUTF8StringEncoding];
        NSLog(@"%@", str);
    }
});
// 异步并行读取文件
NSString *desktop = @"/Users/xxx/Desktop";
NSString *path = [desktop stringByAppendingPathComponent:@"整理.md"];

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_fd_t fd = open(path.UTF8String, O_RDONLY);
dispatch_io_t io = dispatch_io_create(DISPATCH_IO_RANDOM, fd, queue, ^(int error) {
    close(fd);
});

off_t currentSize = 0;
long long fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil].fileSize;

size_t offset = 1024 * 1024;
dispatch_group_t group = dispatch_group_create();
NSMutableData *totalData = [[NSMutableData alloc] initWithLength:fileSize];

for (; currentSize <= fileSize; currentSize += offset) {
    dispatch_group_enter(group);
    dispatch_io_read(io, currentSize, offset, queue, ^(bool done, dispatch_data_t  _Nullable data, int error) {
        
        if (error == 0) {
            size_t len = dispatch_data_get_size(data);
            if (len > 0) {
                const void *bytes = NULL;
                (void)dispatch_data_create_map(data, (const void **)&bytes, &len);
                [totalData replaceBytesInRange:NSMakeRange(currentSize, len) withBytes:bytes length:len];
            }
        }
        
        if (done) {
            dispatch_group_leave(group);
            
            NSString *str = [[NSString alloc] initWithData:totalData encoding:NSUTF8StringEncoding];
            NSLog(@"%@", str);
        }
    });
}


dispatch_async和dispatch_sync。

一个是异步不等待任务完成就返回,另一个是同步任务,需要等待任务完成。这两种提交任务的方式有所不同:

dispatch_async :底层运用了线程池,会在和当前线程不同的线程上处理任务。

dispatch_sync :一般不会新开启线程,而是在当前线程执行任务(比较特殊的是main queue,它会利用main runloop 将任务提交到主线程来执行),同时,它会阻塞当前线程,等待提交的任务执行完毕。当target queue是并发线程时,会直接执行任务。而target queue是串行队列时,会检测当前线程是否已经拥有了该串行队列(这种情况常发生在dispatch_sync嵌套调用的情况下),如果答案是肯定的,则会触发crash,这与老版本GCD中会触发死锁不同,因为在新版GCD中,已经加入了这种死锁检测机制,从而触发crash,避免了调试困难的死锁的发生。

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

问题

1

这个为啥会崩,我觉得他只是有的快有的慢而已,但最终应该都会出来吧
在这里插入图片描述
因为是并发线程,所以可能有两个线程同时执行给array添加元素的操作

并不是每个分配一个线程,就像for循环十个,不会创建十个线程,在循环中,前面用完的可以给后面用。

2

viewdidload里的任务在什么时候加上去的

在网络请求里面的把任务加载到主线程上,加载到主线程的[tableView reloadData]
viewdidiload在网络请求结束后是加载button

所以按理说应该是[tableView reloadData]先加载到主线程上,button的初始化等后加载到线程上

但是调试先走button的加载。

在执行viewDidLoad之前就已经把任务加上去了,所以viewdidLoad中添加任务是添加额外的任务,回加到最后面。

3

关于网络请求的执行顺序

之前听说网络请求会最后执行,这不是很准确。

网络请求dataTask会开启新线程。

通过[dataTask resume]执行网络请求任务,请求到数据后进入completionHandler(完成处理器),所以不一定是最后执行,只是往往数据请求较慢,会在所有任务执行完后才请求到数据。

队列和线程

  • 队列和线程没有直接的关系

  • 队列是一个把任务放进去的容器,队列的性质决定任务是串型执行还是并发执行。

  • 至于在哪个线程执行,由系统决定。

  • async和sync决定是否阻塞当前线程完成任务,而async异步执行默认开启新的线程。

  • DispatchQueue通过结构体和链表被实现为FIFO对列。FIFO队列管理的是通过dispatch_async等函数所追加的Block,它管理队列的执行顺序是串型还是并发,是否开启新线程由系统决定,开启多少个也有系统决定,除了系统之外,async函数默认开启新线程。

关于死锁

在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值