iOS底层探索----- GCD底层原理 完结

1,131 阅读9分钟

前言

上篇文章,探究了 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函数创建并执行:

98A516E6-3751-4FE2-9B3A-A7201D7F81D8.png

如果是普通队列,使用do...while进行线程池的创建,在创建之前,还要对线程池的状态进行判断

FD14D630-0D7E-4FA6-B676-973DBB5CB9EE.png

  • 判断dgq_thread_pool_size,源码中标记为1

  • dgq_thread_pool_size会根据逻辑自增,加到最大值为止;

  • remainingfloor为入参,传入10

  • 计算can_request线程数,如果t_count小于floor返回0,否则返回t_count减去floor的差值;

  • 如果remaining线程数大于can_requestpthread线程池减少请求,以can_request线程数为准;

  • 如果remaining0,表示根队列的pthread线程池已满;

使用pthread_create函数,创建线程:

image.png

最大线程数

根据上面 _dispatch_root_queue_poke_slow 的分析,dgq_thread_pool_size关联到了线程池,根据 dgq_thread_pool_size 的赋值情况,就能找到线程池最大线程数的设定:

DC547D4C-BC0A-4A17-8B9B-DCFD537E1355.png

最后再找 DISPATCH_WORKQ_MAX_PTHREAD_COUNT 的定义情况:

BFE5058C-0E74-460B-8CA3-5026EAA9139C.png

  • 最大线程池数设置255,但实际程序中开辟的线程数,不一定能达到这个最大值。

在官方文档中,辅助线程为512KB,辅助线程允许的最小堆栈大小为16KB,并且堆栈大小必须是4KB 的倍数

程序启动,系统给出的虚拟内存4GB,用户态占3GB,内核态占1GB。但内核态的1GB并不能全部用来开辟线程,所以最大线程数是未知的:

7B929931-7618-49DA-9DD1-50A2603F704D.png

  • 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。当数据不断releaseretain时,多线程会造成数据还没有retain完毕,就开始进行release,相当于加入空数据,进行release

同步栅栏函数分析

源码中,找到dispatch_barrier_sync函数的实现:

58F4E918-8E5C-4D3D-A12E-1FAFC579F372.png

进入_dispatch_barrier_sync_f

F385432E-D4BC-446D-BC60-F2A3125046EE.png

进入_dispatch_barrier_sync_f_inline

3548A8C0-9FCF-4180-B510-2CAFA094A1A9.png

  • 在代码里面,存在进入_dispatch_sync_f_slow函数的代码,证明同步栅栏函数也可能出现死锁的情况。

接着进入 _dispatch_sync_recurse 函数里面:

574AEBDF-5E9C-4B11-8D56-FEEF3090B7A1.png

再进入 _dispatch_sync_invoke_and_complete_recurse 函数:

88529F30-5B00-4A26-AB36-0021C6EE1F49.png

再进入 _dispatch_sync_complete_recurse 函数:

7E26BACD-BF06-4C92-A227-16200AB9228E.png

根据 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实现:

0681FE67-E2C0-4FCD-BA06-CB94FDFB32BB.png

进入_dispatch_lane_wakeup函数:

BF758AE8-44CB-4C89-BF5B-B5308229E81D.png

  • 针对栅栏函数进行判断,进入_dispatch_lane_barrier_complete函数

进入_dispatch_lane_barrier_complete函数

FFFE19F2-F3BF-4186-ABF8-FCD4EBD5C16B.png

  • 如果是串行队列,栅栏相当于同步函数,调用_dispatch_lane_drain_barrier_waiter函数;
  • 如果是并发队列,调用_dispatch_lane_drain_non_barriers函数,进行栅栏相关处理;
  • 栅栏之前的任务全部完成,调用_dispatch_lane_class_barrier_complete函数。

全局队列中的栅栏函数

全局队列的dq_wakeup实现:

C19665AC-4270-4864-8EE7-9FD435DF44D5.png

进入_dispatch_root_queue_wakeup函数:

1FFF8BA7-7F01-4D47-9B26-7188DFEE04DD.png

  • 全局队列中,没有对栅栏函数的任何判断和处理。所以,栅栏函数在全局队列中,和普通的同步或异步函数别无二致。

信号量

信号量可以让异步任务同步执行,可以当锁使用,并且能够控制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函数:

BA8AC9AD-73CB-4F8A-B859-D5E40261E1A3.png

  • 初始化信号量,设置GCD最大并发数;
  • 最大并发数必须>= 0

等待

进入dispatch_semaphore_wait函数

CC27F37A-5ABC-4632-97F8-B08D61EF0E91.png

  • os_atomic_dec2o宏,进行减1操作;

  • 若信号量>= 0,直接返回0,执行wait之后的代码;

  • 若信号量< 0,将阻塞当前线程,进入_dispatch_semaphore_wait_slow函数;

进入_dispatch_semaphore_wait_slow函数

B099BCAB-475C-4B67-AEB3-0A21507DB899.png

  • 根据timeout的值进行不同的逻辑处理。

如果为DISPATCH_TIME_FOREVER类型,进入_dispatch_sema4_wait函数

A5C85BBD-E8A9-42C6-AE15-C4F29C2E7447.png

  • 核心代码为do...while,通过循环使得下面的代码无法执行。

释放

进入dispatch_semaphore_signal函数

0038E24C-0B61-482F-A144-A57FE28003BD.png

  • 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_enterdispatch_group_leave必须成对使用,否则会一直等待或出现异常。

创建组

进入dispatch_group_create函数:

F0B5E601-7F7A-40B9-8034-078B8F499AA9.png

进入_dispatch_group_create_with_count函数:

902C6DB9-345F-488B-B3D5-41E9555C7B3D.png

  • 创建dispatch_group_t结构体,参数n默认传入0

进组

进入dispatch_group_enter函数

D554EC26-72F8-4013-88D3-DAC176728AC2.png

  • 使用os_atomic_sub_orig2o宏,对dg_bits进行减1操作;

  • old_bits只可能是-10两种可能;

  • old_bitsDISPATCH_GROUP_VALUE_MASK进行&运算,将结果赋值给old_value

    • 如果old_bits0old_value0,调用_dispatch_retain函数
    • 如果old_bits-1old_valueDISPATCH_GROUP_VALUE_MASK,表示进组和出组函数使用不平衡,报出异常

出组

进入dispatch_group_leave函数:

3ECBBE94-8541-41E1-A0AA-B49286681376.png

  • 使用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函数

CDE2E6D3-F244-474C-B838-F1373006732B.png

  • 函数的作用,唤醒dispatch_group_notify函数;
  • 核心代码在do...while循环中,调用_dispatch_continuation_async函数。

进入_dispatch_continuation_async函数

6FE8C249-761E-4BF6-B5D3-FDF1A93BE662.png

  • 最终调用dx_push

通知

进入dispatch_group_notify函数

236EE18B-C128-4BCC-9A7B-6213B77108CD.png

  • 判断状态为0,调用_dispatch_group_wake函数
  • 所以通知并不需要一直等待,因为dispatch_group_notifydispatch_group_leave中,都有_dispatch_group_wake函数的调用

任务

进入dispatch_group_async函数

C11A229F-3F9F-4C3A-AC36-2E7640F6802C.png

进入_dispatch_continuation_group_async函数:

19F12B2C-5E60-467B-ADA4-17DCCAC0CB73.png

  • 调用dispatch_group_enter函数,进行进组操作

源码中搜索_dispatch_client_callout函数,在_dispatch_continuation_with_group_invoke函数中,同样调用dispatch_group_leave函数,进行出组操作

49140E38-2926-4C0D-827A-F0650D19B4AB.png

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 sourcetype的类型:

  • 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_READIO操作,如对文件的操作、socket操作的读响应
  • DISPATCH_SOURCE_TYPE_SIGNAL:接收到UNIX信号时响应
  • DISPATCH_SOURCE_TYPE_TIMER:定时器
  • DISPATCH_SOURCE_TYPE_VNODE:文件状态监听,文件被删除、移动、重命名
  • DISPATCH_SOURCE_TYPE_WRITEIO操作,如对文件的操作、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