前言
上篇文章,探究了 GCD
底层原理中的同步、异步函数,死锁,GCD
单例。接下来再接着往下探究分析。
资源准备
线程池
创建线程
异步函数的执行流程:
_dispatch_root_queue_push
--> _dispatch_root_queue_push_override
--> _dispatch_root_queue_poke
--> _dispatch_root_queue_poke_slow
.
在_dispatch_root_queue_poke_slow
中,如果是全局队列,使用_pthread_workqueue_addthreads
函数创建并执行:
如果是普通队列,使用do...while
进行线程池的创建,在创建之前,还要对线程池的状态进行判断
-
判断
dgq_thread_pool_size
,源码中标记为1
; -
dgq_thread_pool_size
会根据逻辑自增,加到最大值为止; -
remaining
和floor
为入参,传入1
和0
; -
计算
can_request
线程数,如果t_count
小于floor
返回0
,否则返回t_count
减去floor
的差值; -
如果
remaining
线程数大于can_request
,pthread
线程池减少请求,以can_request
线程数为准; -
如果
remaining
为0
,表示根队列的pthread
线程池已满;
使用pthread_create
函数,创建线程:
最大线程数
根据上面 _dispatch_root_queue_poke_slow
的分析,dgq_thread_pool_size
关联到了线程池,根据 dgq_thread_pool_size
的赋值情况,就能找到线程池最大线程数的设定:
最后再找 DISPATCH_WORKQ_MAX_PTHREAD_COUNT
的定义情况:
- 最大线程池数设置
255
,但实际程序中开辟的线程数,不一定能达到这个最大值。
在官方文档中,辅助线程为512KB
,辅助线程允许的最小堆栈大小为16KB
,并且堆栈大小必须是4KB
的倍数
程序启动,系统给出的虚拟内存4GB
,用户态占3GB
,内核态占1GB
。但内核态的1GB
并不能全部用来开辟线程,所以最大线程数是未知的:
1GB
满载,最小堆栈大小为16KB
计算,最大线程数可开辟64 * 1024
。按照辅助线程512KB
计算,最大线程数可开辟2048
。
栅栏函数
在iOS
中,可以通过栅栏函数来控制任务的执⾏顺序, 这种栅栏函数就有两种,一种是同步,一种是异步:
-
dispatch_barrier_async
:异步栅栏函数,前面的任务执行完毕才会来到这里; -
dispatch_barrier_sync
:同步栅栏函数,和异步栅栏函数的作用相同,但是同步栅栏函数会堵塞线程,影响后面的任务执行;
使用栅栏函数的注意事项:
-
栅栏函数只能控制同一并发队列;
-
同步栅栏函数添加队列,当前线程会被锁死,直到栅栏之前的任务和栅栏本身的任务执行完毕,当前线程才会继续执行;
-
只能是自定义并发队列,而不是全局并发队列。全局并发队列不支持栅栏函数,因为可能会干扰系统级的任务执行;
-
如果是串行队列,使用栅栏函数的作用等同于一个同步函数,没有任何意义;
栅栏函数还可用于线程安全,类似于锁的作用
NSMutableArray *mArray = [[NSMutableArray alloc] init];
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10000; i++) {
dispatch_async(concurrentQueue, ^{
dispatch_barrier_async(concurrentQueue, ^{
[mArray addObject:[NSString stringWithFormat:@"%d",i]];
});
// @synchronized (self) {
// [self.mArray addObject:[NSString stringWithFormat:@"%d",i]];
// }
});
}
- 此案例,如果不加栅栏函数,也不加互斥锁,使用并发队列多线程对同一数组进行
addObject
,很有可能会发生崩溃; - 因为数据的写入,本质是对旧值的
release
,对新值的retain
。当数据不断release
和retain
时,多线程会造成数据还没有retain
完毕,就开始进行release
,相当于加入空数据,进行release
。
同步栅栏函数分析
源码中,找到dispatch_barrier_sync
函数的实现:
进入_dispatch_barrier_sync_f
:
进入_dispatch_barrier_sync_f_inline
:
- 在代码里面,存在进入
_dispatch_sync_f_slow
函数的代码,证明同步栅栏函数也可能出现死锁的情况。
接着进入 _dispatch_sync_recurse
函数里面:
再进入 _dispatch_sync_invoke_and_complete_recurse
函数:
再进入 _dispatch_sync_complete_recurse
函数:
根据 do...while
循环,当存在栅栏函数时,就唤醒线程,如果不存在,就接着往下执行 _dispatch_lane_non_barrier_complete
:
- 判断
targetq
,存在栅栏调用dx_wakeup
等待 - 否则,调用
_dispatch_lane_non_barrier_complete
函数
dx_wakeup
的宏定义:
#define dx_wakeup(x, y, z) dx_vtable(x)->dq_wakeup(x, y, z)
并发队列的dq_wakeup
实现:
进入_dispatch_lane_wakeup
函数:
- 针对栅栏函数进行判断,进入
_dispatch_lane_barrier_complete
函数
进入_dispatch_lane_barrier_complete
函数
- 如果是串行队列,栅栏相当于同步函数,调用
_dispatch_lane_drain_barrier_waiter
函数; - 如果是并发队列,调用
_dispatch_lane_drain_non_barriers
函数,进行栅栏相关处理; - 栅栏之前的任务全部完成,调用
_dispatch_lane_class_barrier_complete
函数。
全局队列中的栅栏函数
全局队列的dq_wakeup
实现:
进入_dispatch_root_queue_wakeup
函数:
- 全局队列中,没有对栅栏函数的任何判断和处理。所以,栅栏函数在全局队列中,和普通的同步或异步函数别无二致。
信号量
信号量可以让异步任务同步执行,可以当锁使用,并且能够控制GCD
最大并发数:
dispatch_queue_t queue = dispatch_queue_create("cooci", NULL);
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
sleep(1);
NSLog(@"当前 - %d, 线程 - %@", i, [NSThread currentThread]);
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
-
dispatch_semaphore_create
:初始化信号量,设置GCD
最大并发数,必须>= 0
; -
dispatch_semaphore_wait
:等待,对信号量减1
,相当于加锁; -
dispatch_semaphore_signal
:释放,对信号量加1
,相当于解锁;
创建
进入dispatch_semaphore_create
函数:
- 初始化信号量,设置
GCD
最大并发数; - 最大并发数必须
>= 0
;
等待
进入dispatch_semaphore_wait
函数
-
os_atomic_dec2o
宏,进行减1
操作; -
若信号量
>= 0
,直接返回0
,执行wait
之后的代码; -
若信号量
< 0
,将阻塞当前线程,进入_dispatch_semaphore_wait_slow
函数;
进入_dispatch_semaphore_wait_slow
函数
- 根据
timeout
的值进行不同的逻辑处理。
如果为DISPATCH_TIME_FOREVER
类型,进入_dispatch_sema4_wait
函数
- 核心代码为
do...while
,通过循环使得下面的代码无法执行。
释放
进入dispatch_semaphore_signal
函数
-
os_atomic_inc2o
宏,进行加1
操作; -
若信号量
> 0
,直接返回0
,继续执行后续代码; -
若信号量等于
LONG_MIN
,抛出异常。这种情况表示wait
操作过多,二者之间无法匹配。之后会调用_dispatch_semaphore_signal_slow
函数,进入延迟等待;
调度组
调度组最直接的作⽤:控制任务执⾏顺序
dispatch_group_create
:创建组dispatch_group_async
:进组任务dispatch_group_notify
:进组任务执行完毕通知dispatch_group_wait
:进组任务执行等待时间dispatch_group_enter
:进组dispatch_group_leave
:出组
使用进组任务,代码如下:
- (void)testGCD{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"接口1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"接口2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新");
});
}
使用进组、出组
- (void)testGCD1 {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"接口1");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"接口2");
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新");
});
}
dispatch_group_enter
和dispatch_group_leave
必须成对使用,否则会一直等待或出现异常。
创建组
进入dispatch_group_create
函数:
进入_dispatch_group_create_with_count
函数:
- 创建
dispatch_group_t
结构体,参数n
默认传入0
。
进组
进入dispatch_group_enter
函数
-
使用
os_atomic_sub_orig2o
宏,对dg_bits
进行减1
操作; -
old_bits
只可能是-1
和0
两种可能; -
old_bits
和DISPATCH_GROUP_VALUE_MASK
进行&
运算,将结果赋值给old_value
;- 如果
old_bits
为0
,old_value
为0
,调用_dispatch_retain
函数 - 如果
old_bits
为-1
,old_value
为DISPATCH_GROUP_VALUE_MASK
,表示进组和出组函数使用不平衡,报出异常
- 如果
出组
进入dispatch_group_leave
函数:
- 使用
os_atomic_add_orig2o
宏,进行加1
操作; &
运算后的旧值等于DISPATCH_GROUP_VALUE_1
,等待do...while
停止循环,调用_dispatch_group_wake
函数;DISPATCH_GROUP_VALUE_1
等同于DISPATCH_GROUP_VALUE_MASK
;- 如果旧值为
0
,表示进组和出组函数使用不平衡,报出异常;
进入_dispatch_group_wake
函数
- 函数的作用,唤醒
dispatch_group_notify
函数; - 核心代码在
do...while
循环中,调用_dispatch_continuation_async
函数。
进入_dispatch_continuation_async
函数
- 最终调用
dx_push
通知
进入dispatch_group_notify
函数
- 判断状态为
0
,调用_dispatch_group_wake
函数 - 所以通知并不需要一直等待,因为
dispatch_group_notify
和dispatch_group_leave
中,都有_dispatch_group_wake
函数的调用
任务
进入dispatch_group_async
函数
进入_dispatch_continuation_group_async
函数:
- 调用
dispatch_group_enter
函数,进行进组操作
源码中搜索_dispatch_client_callout
函数,在_dispatch_continuation_with_group_invoke
函数中,同样调用dispatch_group_leave
函数,进行出组操作
dispatch_source
dispatch_source
是基础数据类型,用于协调特定底层系统事件的处理
基本介绍
dispatch_source
替代了异步回调函数,来处理系统相关的事件。当配置一个dispatch
时,你需要指定监测的事件、队列、以及任务回调。当事件发生时,dispatch source
会提交block
或函数到指定的queue
去执行。
使用dispatch_source
代替dispatch_async
的原因在于联结的优势。
联结:在任一线程上调用它的一个函数dispatch_source_merge_data
后,会执行Dispatch Source
事先定义好的句柄(可以把句柄简单理解为一个block
),这个过程叫Custom event
,用户事件。是dispatch source
支持处理的一种事件。
句柄:是一种指向指针的指针,它指向的就是一个类或者结构,它和系统有密切的关系,包含:
- 实例句柄
HINSTANCE
- 位图句柄
HBITMAP
- 设备表句柄
HDC
- 图标句柄
HICON
- 通用句柄
HANDLE
简单来说:这种事件是由你调用dispatch_source_merge_data
函数来向自己发出的信号
使用dispatch_source
的优点:
- 其
CPU
负荷非常小,尽量不占用资源 - 联结的优势
创建dispatch source
使用dispatch_source_create
函数
dispatch_source_t source = dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue)
- :
dispatch
源可处理的事件 handle
:可以理解为句柄、索引或ID
,假如要监听进程,需要传入进程ID
mask
:可以理解为描述,提供更详细的描述,让它知道具体要监听什么queue
:自定义源需要的一个队列,用来处理所有的响应句柄
dispatch source
中type
的类型:
DISPATCH_SOURCE_TYPE_DATA_ADD
:自定义的事件,变量增加DISPATCH_SOURCE_TYPE_DATA_OR
:自定义的事件,变量OR
DISPATCH_SOURCE_TYPE_MACH_SENDMACH
:端口发送DISPATCH_SOURCE_TYPE_MACH_RECVMACH
:端口接收DISPATCH_SOURCE_TYPE_MEMORYPRESSURE
:内存压力 (注:iOS8
后可用)DISPATCH_SOURCE_TYPE_PROC
:进程监听,如进程的退出、创建一个或更多的子线程、进程收到UNIX信号DISPATCH_SOURCE_TYPE_READ
:IO
操作,如对文件的操作、socket
操作的读响应DISPATCH_SOURCE_TYPE_SIGNAL
:接收到UNIX
信号时响应DISPATCH_SOURCE_TYPE_TIMER
:定时器DISPATCH_SOURCE_TYPE_VNODE
:文件状态监听,文件被删除、移动、重命名DISPATCH_SOURCE_TYPE_WRITE
:IO
操作,如对文件的操作、socket
操作的写响应
常用API
//挂起队列
dispatch_suspend(queue)
//分派源创建时默认处于暂停状态,在分派源分派处理程序之前必须先恢复
dispatch_resume(source)
//向分派源发送事件,需要注意的是,不可以传递0值(事件不会被触发),同样也不可以传递负数。
dispatch_source_merge_data
//设置响应分派源事件的block,在分派源指定的队列上运行
dispatch_source_set_event_handler
//得到分派源的数据
dispatch_source_get_data
//得到dispatch源创建,即调用dispatch_source_create的第二个参数
uintptr_t dispatch_source_get_handle(dispatch_source_t source);
//得到dispatch源创建,即调用dispatch_source_create的第三个参数
unsigned long dispatch_source_get_mask(dispatch_source_t source);
//取消dispatch源的事件处理--即不再调用block。如果调用dispatch_suspend只是暂停dispatch源。
void dispatch_source_cancel(dispatch_source_t source);
//检测是否dispatch源被取消,如果返回非0值则表明dispatch源已经被取消
long dispatch_source_testcancel(dispatch_source_t source);
//dispatch源取消时调用的block,一般用于关闭文件或socket等,释放相关资源
void dispatch_source_set_cancel_handler(dispatch_source_t source, dispatch_block_t cancel_handler);
//可用于设置dispatch源启动时调用block,调用完成后即释放这个block。也可在dispatch源运行当中随时调用这个函数。
void dispatch_source_set_registration_handler(dispatch_source_t source, dispatch_block_t registration_handler);
timer
的封装
打开SparkTimer.h
文件,写入以下代码:
#import <Foundation/Foundation.h>
@class SparkTimer;
typedef void (^TimerBlock)(SparkTimer * _Nonnull timer);
@interface SparkTimer : NSObject
@property (nonatomic, readonly, getter=isValid) BOOL valid;
@property (nonatomic, nullable, readonly, retain) id userInfo;
+ (SparkTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo immediately:(BOOL)isImmediately;
+ (SparkTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo immediately:(BOOL)isImmediately timerBlock:(TimerBlock)block;
- (void)invalidate;
@end
打开SparkTimer.m
文件,写入以下代码:
#import "SparkTimer.h"
@interface SparkTimer ()
@property (nonatomic, assign) NSTimeInterval interval;
@property (nonatomic, nullable, readwrite, retain) id userInfo;
@property (nonatomic, assign) BOOL repeats;
@property (nonatomic, assign) BOOL immediately;
@property (nonatomic, strong) dispatch_source_t timer;
@property (nonatomic, readwrite, getter=isValid) BOOL valid;
@end
+ (SparkTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelectoruserInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo immediately:(BOOL)isImmediately {
return [[SparkTimer alloc] initWithInterval:ti target:aTarget selector:aSelector userInfo:userInfo repeats:yesOrNoimmediately:isImmediately timerBlock:nil isBlock:NO];
}
+ (SparkTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo immediately:(BOOL)isImmediately timerBlock:(TimerBlock)block {
return [[SparkTimer alloc] initWithInterval:ti target:nil selector:nil userInfo:userInfo repeats:yesOrNoimmediately:isImmediately timerBlock:block isBlock:YES];
}
- (instancetype)initWithInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo immediately:(BOOL)isImmediately timerBlock:(TimerBlock)block isBlock:(BOOL)isBlock {
self = [super init];
if (self) {
_interval = ti;
_userInfo = userInfo;
_repeats = yesOrNo;
_immediately = isImmediately;
_valid = NO;
@weakify(self)
[self createTimer:^{
@strongify(self)
if(!isBlock){
[self callOutWithTarget:aTarget selector:aSelector];
return;
}
[self callOutWithTimerBlock:block];
}];
}
return self;
}
- (void)callOutWithTarget:(id)aTarget selector:(SEL)aSelector {
if(!aTarget || !aSelector) {
[self invalidate];
return;
}
[aTarget performSelector:aSelector withObject:self];
[self checkRepeats];
}
- (void)callOutWithTimerBlock:(TimerBlock)block{
if(!block) {
[self invalidate];
return;
}
block(self);
[self checkRepeats];
}
- (void)checkRepeats {
if(self.repeats) {
return;
}
[self invalidate];
}
- (void)createTimer:(void(^)(void))block {
_valid = YES;
//1.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//2.创建timer
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_time_t start;
if(self.immediately) {
start = DISPATCH_TIME_NOW;
} else {
start = dispatch_time(DISPATCH_TIME_NOW, self.interval * NSEC_PER_SEC);
}
//3.设置timer首次执行时间,间隔,精确度
dispatch_source_set_timer(_timer, start, self.interval * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
//4.设置timer事件回调
dispatch_source_set_event_handler(_timer, ^{
NSLog(@"计时");
block();
});
//5.默认是挂起状态,需要手动激活
dispatch_resume(_timer);
}
- (void)invalidate {
if(!self.isValid){
return;
}
_valid = NO; dispatch_source_cancel(_timer);
}
- (id)userInfo {
return _userInfo;
}
@end