libdispatch-1271.120.2 下载
苹果官方资源opensource
多线程相关文献:
iOS 多线程原理 - 线程与队列底层
iOS 多线程原理 - GCD函数底层
iOS 线程底层 - 锁
本章节探究:
1.单例 dispatch_once
2.栅栏函数 barrier
3.调度组 group
4.信号量 semaphore
5.dispatch_source
在了解了线程与队列的底层原理之后,本章节来看看GCD函数的底层原理,研究这些API是怎么调用的,并附上使用案例。
+ (SingleExample *)shareInstance {
static SingleExample *single = nil;
static dispatch_once_t onceToken ;
dispatch_once(&onceToken, ^{
single = [[SingleExample alloc] init];
}) ;
return single;
}
来看看dispatch_once这个函数原理。
打开libdispatch源码
dispatch_once的源码声明:

dispatch_once_t *val它里面有一个状态的记录,来保证block只被调用一次。
dispatch_once_f的源码声明:

需要看看执行func的要出于什么条件下才会被执行 (_dispatch_once_gate_tryenter)
_dispatch_once_gate_tryenter的源码声明:

而等待是怎么等的呢?(_dispatch_once_wait)
_dispatch_once_wait的源码声明:

在死循环里不断地查询单例状态,一旦任务执行完毕才跳出循环。
单例总结:
1.线程安全
2.任务只会被执行一次
3.通过一个状态值来保证任务是否被执行过
同步栅栏函数dispatch_barrier_sync案例:
- (void)test_barrier {
dispatch_queue_t t = dispatch_queue_create("AnAn", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(t, ^{
NSLog(@"1");
});
dispatch_async(t, ^{
NSLog(@"2");
});
// 栅栏函数
dispatch_barrier_sync(t, ^{
sleep(2);
NSLog(@"%@", [NSThread currentThread]); // main
NSLog(@"3");
});
NSLog(@"4");
dispatch_async(t, ^{
NSLog(@"5");
});
}
// 12顺序不一定;3一定在12后面;45在3后面;45顺序不一定
// 同步栅栏dispatch_barrier_sync 和 普通的同步dispatch_sync效果是一样的
异步栅栏函数dispatch_barrier_async案例:
- (void)test_barrier {
dispatch_queue_t t = dispatch_queue_create("AnAn", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(t, ^{
NSLog(@"1");
});
dispatch_async(t, ^{
NSLog(@"2");
});
// 异步栅栏函数
dispatch_barrier_async(t, ^{
sleep(2);
NSLog(@"3");
});
NSLog(@"4");
dispatch_async(t, ^{
NSLog(@"5");
});
}
// 124顺序不一定,3一定在12后面,5一定在3后面
// 异步栅栏函数只能栅得住非全局队列的任务
全局队列案例:
- (void)test_barrier_global {
dispatch_queue_t t = dispatch_get_global_queue(0, 0);
dispatch_async(t, ^{
NSLog(@"1");
});
dispatch_async(t, ^{
NSLog(@"2");
});
// 栅栏函数
dispatch_barrier_async(t, ^{
sleep(2);
NSLog(@"3");
});
NSLog(@"4");
dispatch_async(t, ^{
NSLog(@"5");
});
}
// 1245没有顺序 3在最后
// 异步栅栏函数栅不住全局队列里的任务
栅栏函数分为同步栅栏和异步栅栏。
dispatch_barrier_async在自定义的并发队列里,全局和串行达不到我们要的效果。
苹果文档中指出,如果使用的是全局队列或者创建的不是并发队列,则dispatch_barrier_async实际上就相当于dispatch_async。
dispatch_barrier_sync
其实同步栅栏与普通同步实现的效果是差不多的,在源码上只有一点点小差异。



_dispatch_barrier_sync_f_inline里会判断不同的队列条件来去选择分支继续往下走,这里我以并发队列为例,它会走_dispatch_sync_f_slow代码分支:

以并发队列为例,它会走_dispatch_sync_invoke_and_complete_recurse代码分支:

_dispatch_sync_function_invoke_inline和dispatch_sync底层一样的,是去调用func。
栅栏函数完成任务后会执行_dispatch_sync_complete_recurse唤醒队列里后续的任务。

_dispatch_sync_complete_recurse里通过do...while去唤醒队列里的任务dx_wakeup;
dx_wakeup是一个dq_wakeup的宏定义:
#define dx_wakeup(x, y, z) dx_vtable(x)->dq_wakeup(x, y, z)

(栅栏函数栅不住全局队列的原因就在这里,因为它指定的wakeup函数不一样。)
唤醒以并发队列为例,它会走_dispatch_lane_wakeup

为barrier形式,调用_dispatch_lane_barrier_complete:

串行队列,则会进行等待,等待其他的任务执行完成,再按顺序执行;并发队列,则会调用_dispatch_lane_drain_non_barriers方法将栅栏之前的任务执行完成;_dispatch_lane_class_barrier_complete方法,也就是把栅栏拔掉了,不拦了,从而执行栅栏之后的任务。唤醒以全局并发队列为例,它会走_dispatch_root_queue_wakeup
它里面就没有拦截有关栅栏函数相关的东西。

同步栅栏函数dispatch_barrier_sync和普通同步函数dispatch_sync效果是一样的:
阻塞当前线程,不开辟线程,立即执行,同步栅栏函数还需要等栅栏任务完成后唤醒非全局队列后续的任务
为什么苹果设计栅栏函数栅不住全局并发队列?
因为我们系统也会使用全局并发队列,避免造成系统任务被阻塞。
dispatch_barrier_async

_dispatch_continuation_init保存任务
_dispatch_continuation_init保存了任务,在需要执行的时候拿出来执行

_dispatch_continuation_async
dx_push是宏定义:
#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)
找到dq_push的声明:

根据不同的队列赋值给dq_push不一样的函数
以并发队列为例:
_dispatch_lane_concurrent_push的源码声明:
_dispatch_lane_concurrent_push就是栅栏异步与普通异步函数的分支:


走到dx_wakeup函数,这里在同步栅栏部分已经介绍过了。
栅栏函数总结:
1.栅栏函数只针对非全局队列;
2.栅栏函数不能栅住全局队列,因为系统也在用它,防止阻塞住系统任务;
3.栅栏函数需要等待当前队列前面的任务执行完,再去执行栅栏任务,最后唤醒执行栅栏任务后面的任务
- (void)test_group {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t que1 = dispatch_queue_create("An", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t que2 = dispatch_queue_create("Lin", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_enter(group);
dispatch_async(que1, ^{
sleep(4);
NSLog(@"1");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(que2, ^{
sleep(3);
NSLog(@"2");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0 ), ^{
sleep(2);
NSLog(@"3");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_main_queue(), ^{
sleep(1);
NSLog(@"4");
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"6");
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"5");
});
}
// 5在4321任务之后
当然也可以使用dispatch_group_async来代替dispatch_group_enter和dispatch_group_leave;效果是一样的。
研究的对象有三个:dispatch_group_enter、dispatch_group_leave、dispatch_group_notify。
dispatch_group_enter的源码分析
ps: 这里 DISPATCH_GROUP_VALUE_INTERVAL = 0x0000000000000004ULL
注释里说的 0->-1 跃迁的进位其实是位运算。实际上是+4
苹果官方文档对dispatch_group_enter的解释:
调用此函数将增加组中当前未完成任务的计数。如果应用程序通过dispatch_group_async函数以外的方式显式地从组中添加和删除任务,那么使用这个函数(与dispatch_group_leave一起使用)允许您的应用程序正确地管理任务引用计数。对这个函数的调用必须与对dispatch_group_leave的调用相平衡。您可以使用此函数同时将一个块与多个组关联。
dispatch_group_leave的源码分析
苹果官方文档对dispatch_group_leave的解释:
调用此函数将减少组中当前未完成任务的计数。如果应用程序通过dispatch_group_async函数以外的方式显式地从组中添加和删除任务,那么使用这个函数(与dispatch_group_enter一起使用)允许您的应用程序正确地管理任务引用计数。
对该函数的调用必须平衡对dispatch_group_enter的调用。调用它的次数超过dispatch_group_enter是无效的,这会导致负的计数。
dispatch_group_notify的源码分析

dispatch_group_enter通过改变调度组状态值+4;dispatch_group_leave通过调度组状态值-4;dispatch_group_notify在调度组内不断地获取调度组状态值,如果状态值达到平衡(等于0),则说明前面的任务做完了,需要执行notify里的任务。
调度组总结:
1.dispatch_group_enter与dispatch_group_leave必须成对使用;
2.dispatch_group_leave次数多于dispatch_group_enter会导致崩溃;
3.调度组底层是通过修改调度组的状态值的增(enter)减(leave),不断地监听这个状态值是否达到平衡(等于0),一旦平衡则去执行dispatch_group_notify里的任务。
- (void)test_semaphore {
// 设置0,任务1不需要等;设置1,任务1和2不需要等...
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1");
dispatch_semaphore_signal(sem);
});
// 等待任务1的signal
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(2);
NSLog(@"2");
dispatch_semaphore_signal(sem);
});
// 等待任务2的signal
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"3");
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"4");
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"5");
dispatch_semaphore_signal(sem);
});
}
// 12345
dispatch_semaphore_create 创建信号量,指定信号量数值dispatch_semaphore_signal 发送信号量,将信号量数值+1dispatch_semaphore_wait 等待信号量;当信号量数值为0时,阻塞当前线程一直等待;当信号量数值大于等于1时,将信号量数值-1并执行当前线程的任务dispatch_semaphore_create的源码声明:

传递小于零的值将导致返回 NULL,也就是小于0就不会正常执行。
总的来说:信号量初始值可以控制线程池中的最多并发数量
dispatch_semaphore_signal的源码声明:
os_atomic_inc2o原子操作自增加1,然后会判断,如果value > 0,就会返回0;
加一次后依然小于0就报异常 Unbalanced call to dispatch_semaphore_signal(),然后会调用_dispatch_semaphore_signal_slow做容错的处理。


dispatch_semaphore_wait的源码声明:
os_atomic_dec2o进行原子自减1操作,也就是对value值进行减操作,控制可并发数。_dispatch_semaphore_wait_slow去等待。_dispatch_semaphore_wait_slow方法。看看_dispatch_semaphore_wait_slow的等待逻辑


一个do-while循环,当不满足条件时,会一直循环下去,从而导致流程的阻塞。
上面举例里面就相当于,下图中的情况:

信号量总结:
1.dispatch_semaphore_wait信号量等待,内部是对并发数做自减1操作,如果小于0,会执行_dispatch_semaphore_wait_slow然后调用_dispatch_sema4_wait是一个do-while,直到满足条件结束循环。
2.dispatch_semaphore_signal信号量释放 ,内部是对并发数做自加1操作,直到大于0时,为可操作。
3.保持线程同步,将异步执行任务转换为同步执行任务。
4.保证线程安全,为线程加锁,相当于自旋锁。

dispatch_source_create 创建源dispatch_source_set_event_handler 设置源事件回调dispatch_source_merge_data 源事件设置数据dispatch_source_get_data 获取源事件数据dispatch_resume 继续dispatch_suspend 挂起dispatch_source_cancel 取消源事件定时器监听
倒计时案例:
- (void)iTimer {
__block int timeout = 60;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
if(timeout <= 0) {
dispatch_source_cancel(_timer);
} else {
timeout--;
NSLog(@"倒计时:%d", timeout);
}
});
dispatch_resume(_timer);
}
自定义事件,变量增加
变量增加案例:
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIButton *iBt;
@property (weak, nonatomic) IBOutlet UIProgressView *iProgress;
@property (nonatomic, strong) dispatch_source_t source;
@property (nonatomic, strong) dispatch_queue_t queue;
@property (nonatomic, assign) NSUInteger totalComplete;
@property (nonatomic ,assign) int iNum;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.totalComplete = 0;
self.queue = dispatch_queue_create("lg", DISPATCH_QUEUE_SERIAL);
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
dispatch_source_set_event_handler(self.source, ^{
NSUInteger value = dispatch_source_get_data(self.source); // 每次去获取iNum的值
self.totalComplete += value;
NSLog(@"进度: %.2f",self.totalComplete/100.0);
self.iProgress.progress = self.totalComplete/100.0;
});
// [self iTimer];
}
- (IBAction)btClick:(id)sender {
if ([self.iBt.titleLabel.text isEqualToString:@"开始"]) {
dispatch_resume(self.source);
NSLog(@"开始了");
self.iNum = 1;
[sender setTitle:@"暂停" forState:UIControlStateNormal];
for (int i= 0; i<1000; i++) {
dispatch_async(self.queue, ^{
sleep(1);
dispatch_source_merge_data(self.source, self.iNum); // 传递iNum触发hander
});
}
} else {
dispatch_suspend(self.source);
NSLog(@"暂停了");
self.iNum = 0;
[sender setTitle:@"开始" forState:UIControlStateNormal];
}
}
@end
附上dispatch_source的Demo
我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
我正在尝试用ruby中的gsub函数替换字符串中的某些单词,但有时效果很好,在某些情况下会出现此错误?这种格式有什么问题吗NoMethodError(undefinedmethod`gsub!'fornil:NilClass):模型.rbclassTest"replacethisID1",WAY=>"replacethisID2andID3",DELTA=>"replacethisID4"}end另一个模型.rbclassCheck 最佳答案 啊,我找到了!gsub!是一个非常奇怪的方法。首先,它替换了字符串,所以它实际上修改了
我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin
这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下
我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的
如何在Ruby中按名称传递函数?(我使用Ruby才几个小时,所以我还在想办法。)nums=[1,2,3,4]#Thisworks,butismoreverbosethanI'dlikenums.eachdo|i|putsiend#InJS,Icouldjustdosomethinglike:#nums.forEach(console.log)#InF#,itwouldbesomethinglike:#List.iternums(printf"%A")#InRuby,IwishIcoulddosomethinglike:nums.eachputs在Ruby中能不能做到类似的简洁?我可以只
1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里
说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时
我正在尝试使用ruby编写一个双线程客户端,一个线程从套接字读取数据并将其打印出来,另一个线程读取本地数据并将其发送到远程服务器。我发现的问题是Ruby似乎无法捕获线程内的错误,这是一个示例:#!/usr/bin/rubyThread.new{loop{$stdout.puts"hi"abc.putsefsleep1}}loop{sleep1}显然,如果我在线程外键入abc.putsef,代码将永远不会运行,因为Ruby将报告“undefinedvariableabc”。但是,如果它在一个线程内,则没有错误报告。我的问题是,如何让Ruby捕获这样的错误?或者至少,报告线程中的错误?