多线程之GCD应用

news2024/12/26 11:43:27

一些套话

GCD全称是Grand Central Dispatch,它是纯 C 语言,并且提供了非常多强大的函数
GCD的优势:

  • GCD 是苹果公司为多核的并行运算提出的解决方案
  • GCD 会自动利用更多的CPU内核(比如双核、四核)
  • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码

我们要关注的点就是:GCD的核心——将任务添加到队列,并且指定执行任务的函数

dispatch_block_t

dispatch_block_t block = ^{
    NSLog(@"GCD的基本使用");
};
dispatch_queue_t queue = dispatch_queue_create("com.Felix", NULL);
dispatch_async(queue, block);

这串代码最能体现GCD的核心:

  • dispatch_block_t使用block封装任务
  • dispatch_queue_t创建队列
  • dispatch_async将任务添加到队列

上述代码通常也写成这种形式

dispatch_queue_t queue = dispatch_queue_create("com.Felix", NULL);
dispatch_async(queue, ^{
    NSLog(@"GCD的基本使用");
});

dispatch_sync & dispatch_async

多线程执行任务分为dispatch_sync同步执行任务和dispatch_async异步执行:

  • dispatch_sync同步执行
  • 必须等待当前语句执行完毕,才会执行下一条语句
    不会开启线程
  • 在当前线程执行block的任务
    dispatch_async异步执行
  • 不用等待当前语句执行完毕,就可以执行下一条语句
  • 会开启线程执行block任务
  • 异步是多线程的代名词

dispatch_queue_t

在这里插入图片描述
多线程中队列分为串行队列(Serial Dispatch Queue)并发队列(Concurrent Dispatch Queue)

  • 串行队列:线程执行只能依次逐一先后有序的执行,等待上一个执行完再执行下一个
    • 使用dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL)创建串行队列
    • 亦可以使用dispatch_queue_create("xxx", NULL)创建串行队列(GCD底层会讲到)
  • 主队列:绑定主线程,所有任务都在主线程中执行、经过特殊处理的串行的队列
    • 使用dispatch_get_main_queue()获取主队列
  • 并发队列:线程可以同时一起执行,不需要等待上一个执行完就能执行下一个任务
    • 使用dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT);创建并发队列
  • 全局队列:系统提供的并发队列
    • 最简单的是使用dispatch_get_global_queue(0, 0)获取系统提供的并发队列
    • 第一个参数是优先级枚举值,默认优先级为DISPATCH_QUEUE_PRIORITY_DEFAULT=0
    • 优先级从高到低依次为DISPATCH_QUEUE_PRIORITY_HIGH、DISPATCH_QUEUE_PRIORITY_DEFAULT、DISPATCH_QUEUE_PRIORITY_LOW、DISPATCH_QUEUE_PRIORITY_BACKGROUND

串行/并发和同步/异步的排列组合

请添加图片描述

主队列和全局队列单独考虑,组合结果以总结表格为准

串行+同步

任务一个接一个执行,不开辟线程

- (void)test {
    NSLog(@"主线程-%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 10; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"串行&同步线程%d-%@", i, [NSThread currentThread]);
        });
    }
}
--------------------输出结果:-------------------
// 主线程-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// 串行&同步线程0-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// 串行&同步线程1-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// ...按顺序输出
--------------------输出结果:-------------------

串行+异步

任务一个接一个执行,会开辟线程

- (void)test {
    NSLog(@"主线程-%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"串行&异步线程%d-%@", i, [NSThread currentThread]);
        });
    }
}
--------------------输出结果:-------------------
// 主线程-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// 串行&异步线程0-<NSThread: 0x6000009b8880>{number = 6, name = (null)}
// 串行&异步线程1-<NSThread: 0x6000009b8880>{number = 6, name = (null)}
// ...按顺序输出
--------------------输出结果:-------------------

并发+同步

任务一个接一个执行,不开辟线程

- (void)test {
    NSLog(@"主线程-%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"并发&同步线程%d-%@", i, [NSThread currentThread]);
        });
    }
}
--------------------输出结果:-------------------
// 主线程-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// 串行&同步线程0-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// 串行&同步线程1-<NSThread: 0x600003b64fc0>{number = 1, name = main}
// ...按顺序输出
--------------------输出结果:-------------------

并发+异步

任务乱序执行,开辟线程

- (void)test {
    NSLog(@"主线程-%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"并发&异步线程%d-%@", i, [NSThread currentThread]);
        });
    }
}
--------------------输出结果:-------------------
// 主线程-<NSThread: 0x600002a9cd40>{number = 1, name = main}
// 并发&异步线程1-<NSThread: 0x600002a9ca40>{number = 5, name = (null)}
// 并发&异步线程0-<NSThread: 0x600002add3c0>{number = 4, name = (null)}
// ...乱序输出
--------------------输出结果:-------------------

下面来看一下主队列全局队列的使用情况:

主队列+同步

相互等待,造成死锁

- (void)test {
    NSLog(@"主线程-%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    for (int i = 0; i < 10; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"主队列&同步线程%d-%@", i, [NSThread currentThread]);
        });
    }
}
--------------------输出结果:-------------------
// 主线程-<NSThread: 0x600001980d40>{number = 1, name = main}
// 崩溃...
--------------------输出结果:-------------------

主队列+异步

任务一个接一个执行,不开辟线程

- (void)test {
    NSLog(@"主线程-%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"主队列&异步线程%d-%@", i, [NSThread currentThread]);
        });
    }
}
--------------------输出结果:-------------------
// 主线程-<NSThread: 0x600001980d40>{number = 1, name = main}
// 主队列&异步线程0-<NSThread: 0x600001980d40>{number = 1, name = main}
// 主队列&异步线程1-<NSThread: 0x600001980d40>{number = 1, name = main}
// ...按顺序输出
--------------------输出结果:-------------------

全局队列+同步

任务一个接一个执行,不开辟线程(同并发+同步)

- (void)test {
    NSLog(@"主线程-%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 10; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"全局队列&同步线程%d-%@", i, [NSThread currentThread]);
        });
    }
}
--------------------输出结果:-------------------
// 主线程-<NSThread: 0x600001980d40>{number = 1, name = main}
// 全局队列&同步线程0-<NSThread: 0x60000099c080>{number = 1, name = main}
// 全局队列&同步线程1-<NSThread: 0x60000099c080>{number = 1, name = main}
// ...按顺序输出
--------------------输出结果:-------------------

全局队列+异步

任务乱序执行,开辟线程(同并发+异步)

- (void)test {
    NSLog(@"主线程-%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"全局队列&异步线程%d-%@", i, [NSThread currentThread]);
        });
    }
}
--------------------输出结果:-------------------
// 主线程-<NSThread: 0x600001cd4ec0>{number = 1, name = main}
// 全局队列&异步线程2-<NSThread: 0x600001c8eb00>{number = 3, name = (null)}
// 全局队列&异步线程3-<NSThread: 0x600001c82b80>{number = 7, name = (null)}
// ...乱序输出
--------------------输出结果:-------------------

总结

执行\队列串行队列并发队列主队列全局队列
同步执行按序执行,不开辟线程按序执行,不开辟线程死锁按序执行,不开辟线程
异步执行按序执行,开辟线程乱序执行,开辟线程按序执行,不开辟线程乱序执行,开辟线程

dispatch_after

dispatch_after表示在某队列中的block延迟执行

应用场景:在主队列上延迟执行一项任务,如viewDidload之后延迟1s,提示一个alertview(是延迟加入到队列,而不是延迟执行)

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"2秒后输出");
});

dispatch_once

dispatch_once保证在App运行期间,block中的代码只执行一次

应用场景:单例method-Swizzling

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    //创建单例、method swizzled或其他任务
});

dispatch_apply

dispatch_apply将指定的Block追加到指定的队列中重复执行,并等到全部的处理执行结束——相当于线程安全的for循环
应用场景:用来拉取网络数据后提前算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性

  • 添加到串行队列中——按序执行
  • 添加到主队列中——死锁
  • 添加到并发队列中——乱序执行
  • 添加到全局队列中——乱序执行
- (void)test {
    /**
     param1:重复次数
     param2:追加的队列
     param3:执行任务
     */
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_SERIAL);
    NSLog(@"dispatch_apply前");
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"dispatch_apply的线程%zu-%@", index, [NSThread currentThread]);
    });
    NSLog(@"dispatch_apply后");
}
--------------------输出结果:-------------------
// dispatch_apply前
// dispatch_apply的线程0-<NSThread: 0x6000019f8d40>{number = 1, name = main}
// ...是否按序输出与串行队列还是并发队列有关
// dispatch_apply后
--------------------输出结果:-------------------

dispatch_group_t

dispatch_group_t:调度组将任务分组执行,能监听任务组完成,并设置等待时间

应用场景:多个接口请求之后刷新页面

dispatch_group_async

dispatch_group_notifydispatch_group_async执行结束之后会受到通知

- (void)test {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"请求一完成");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"请求二完成");
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新页面");
    });
}
--------------------输出结果:-------------------
// 请求二完成
// 请求一完成
// 刷新页面
--------------------输出结果:-------------------

dispatch_group_enter & dispatch_group_leave

dispatch_group_enterdispatch_group_leave成对出现,使进出组的逻辑更加清晰

- (void)test {
    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(@"请求一完成");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"请求二完成");
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新页面");
    });
}
--------------------输出结果:-------------------
// 请求二完成
// 请求一完成
// 刷新页面
--------------------输出结果:-------------------

调度组要注意搭配使用,必须先进组再出组,缺一不可

dispatch_group_wait使用

long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)

  • group:需要等待的调度组
  • timeout:等待的超时时间(即等多久)
    • 设置为DISPATCH_TIME_NOW意味着不等待直接判定调度组是否执行完毕
    • 设置为DISPATCH_TIME_FOREVER则会阻塞当前调度组,直到调度组执行完毕
  • 返回值:为long类型
    • 返回值为0——在指定时间内调度组完成了任务
    • 返回值不为0——在指定时间内调度组没有按时完成任务

将上述调度组代码进行改写

- (void)test {
    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(@"请求一完成");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"请求二完成");
        dispatch_group_leave(group);
    });
    
    long timeout = dispatch_group_wait(group, DISPATCH_TIME_NOW);
//    long timeout = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//    long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
    NSLog(@"timeout=%ld", timeout);
    if (timeout == 0) {
        NSLog(@"按时完成任务");
    } else {
        NSLog(@"超时");
    }
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新页面");
    });
}
--------------------输出结果:-------------------
// timeout=49
// 请求一完成
// 请求二完成
// 超时
// 刷新页面
--------------------输出结果:-------------------

dispatch_barrier_sync & dispatch_barrier_async

应用场景:同步锁
在这里插入图片描述
前文已经提过并发执行异步队列会开辟线程,而任务也会因为任务复杂度和cpu的调度导致各个乱序执行完毕,比如上图中的任务3明明是先于任务4执行,但是晚于任务4执行完毕
此时GCD就提供了两个API——dispatch_barrier_sync和dispatch_barrier_async,使用这两个API就能将多个任务进行分组——等栅栏前追加到队列中的任务执行完毕后,再将栅栏后的任务追加到队列中。简而言之,就是先执行栅栏前任务,再执行栅栏任务,最后执行栅栏后任务

串行队列使用栅栏函数

- (void)test {
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_SERIAL);
    
    NSLog(@"开始——%@", [NSThread currentThread]);
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"延迟2s的任务1——%@", [NSThread currentThread]);
    });
    NSLog(@"第一次结束——%@", [NSThread currentThread]);
    
//    dispatch_barrier_async(queue, ^{
//        NSLog(@"----------栅栏任务----------%@", [NSThread currentThread]);
//    });
//    NSLog(@"栅栏结束——%@", [NSThread currentThread]);
    
    dispatch_async(queue, ^{
        sleep(1);
        NSLog(@"延迟1s的任务2——%@", [NSThread currentThread]);
    });
    NSLog(@"第二次结束——%@", [NSThread currentThread]);
}

不使用栅栏函数

开始——<NSThread: 0x600001068900>{number = 1, name = main}
第一次结束——<NSThread: 0x600001068900>{number = 1, name = main}
第二次结束——<NSThread: 0x600001068900>{number = 1, name = main}
延迟2s的任务1——<NSThread: 0x600001025ec0>{number = 3, name = (null)}
延迟1s的任务2——<NSThread: 0x600001025ec0>{number = 3, name = (null)}

使用栅栏函数

开始——<NSThread: 0x6000001bcf00>{number = 1, name = main}
第一次结束——<NSThread: 0x6000001bcf00>{number = 1, name = main}
栅栏结束——<NSThread: 0x6000001bcf00>{number = 1, name = main}
第二次结束——<NSThread: 0x6000001bcf00>{number = 1, name = main}
延迟2s的任务1——<NSThread: 0x6000001fcf00>{number = 5, name = (null)}
----------栅栏任务----------<NSThread: 0x6000001bcf00>{number = 1, name = main}
延迟1s的任务2——<NSThread: 0x6000001fcf00>{number = 5, name = (null)}

栅栏函数的作用是将队列中的任务进行分组,所以我们只要关注任务1任务2在这里插入图片描述
结论:由于串行队列异步执行任务是一个接一个执行完毕的,所以使用栅栏函数没意义

并发队列使用栅栏函数

- (void)test {
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"开始——%@", [NSThread currentThread]);
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"延迟2s的任务1——%@", [NSThread currentThread]);
    });
    NSLog(@"第一次结束——%@", [NSThread currentThread]);
    
//    dispatch_barrier_async(queue, ^{
//        NSLog(@"----------栅栏任务----------%@", [NSThread currentThread]);
//    });
//    NSLog(@"栅栏结束——%@", [NSThread currentThread]);
    
    dispatch_async(queue, ^{
        sleep(1);
        NSLog(@"延迟1s的任务2——%@", [NSThread currentThread]);
    });
    NSLog(@"第二次结束——%@", [NSThread currentThread]);
}

不使用栅栏函数

开始——<NSThread: 0x600002384f00>{number = 1, name = main}
第一次结束——<NSThread: 0x600002384f00>{number = 1, name = main}
第二次结束——<NSThread: 0x600002384f00>{number = 1, name = main}
延迟1s的任务2——<NSThread: 0x6000023ec300>{number = 5, name = (null)}
延迟2s的任务1——<NSThread: 0x60000238c180>{number = 7, name = (null)}

使用栅栏函数

开始——<NSThread: 0x600000820bc0>{number = 1, name = main}
第一次结束——<NSThread: 0x600000820bc0>{number = 1, name = main}
栅栏结束——<NSThread: 0x600000820bc0>{number = 1, name = main}
第二次结束——<NSThread: 0x600000820bc0>{number = 1, name = main}
延迟2s的任务1——<NSThread: 0x600000863c80>{number = 4, name = (null)}
----------栅栏任务----------<NSThread: 0x600000863c80>{number = 4, name = (null)}
延迟1s的任务2——<NSThread: 0x600000863c80>{number = 4, name = (null)}

在这里插入图片描述
结论:由于并发队列异步执行任务是乱序执行完毕的,所以使用栅栏函数可以很好的控制队列内任务执行的顺序

dispatch_barrier_sync/dispatch_barrier_async区别

  • dispatch_barrier_async:前面的任务执行完毕才会来到这里
  • dispatch_barrier_sync:作用相同,但是这个会堵塞线程,影响后面的任务执行

将案例二中的dispatch_barrier_async改成dispatch_barrier_sync

开始——<NSThread: 0x600001040d40>{number = 1, name = main}
第一次结束——<NSThread: 0x600001040d40>{number = 1, name = main}
延迟2s的任务1——<NSThread: 0x60000100ce40>{number = 6, name = (null)}
----------栅栏任务----------<NSThread: 0x600001040d40>{number = 1, name = main}
栅栏结束——<NSThread: 0x600001040d40>{number = 1, name = main}
第二次结束——<NSThread: 0x600001040d40>{number = 1, name = main}
延迟1s的任务2——<NSThread: 0x60000100ce40>{number = 6, name = (null)}

结论:dispatch_barrier_async可以控制队列中任务的执行顺序,而dispatch_barrier_sync不仅阻塞了队列的执行,也阻塞了线程的执行(尽量少用)

栅栏函数注意点

尽量使用自定义的并发队列:

  • 使用全局队列起不到栅栏函数的作用
  • 使用全局队列时由于对全局队列造成堵塞,可能致使系统其他调用全局队列的地方也堵塞从而导致崩溃(并不是只有你在使用这个队列)

栅栏函数只能控制同一并发队列:打个比方,平时在使用AFNetworking做网络请求时为什么不能用栅栏函数起到同步锁堵塞的效果,因为AFNetworking内部有自己的队列

dispatch_semaphore_t

应用场景:同步当锁, 控制GCD最大并发数

  • dispatch_semaphore_create():创建信号量
  • dispatch_semaphore_wait():等待信号量,信号量减1。当信号量< 0时会阻塞当前线程,根据传入的等待时间决定接下来的操作——如果永久等待将等到信号(signal)才执行下去
  • dispatch_semaphore_signal():释放信号量,信号量加1。当信号量>= 0 会执行wait之后的代码

下面这段代码要求使用信号量来按序输出(当然栅栏函数可以满足要求

- (void)test {
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"当前%d----线程%@", i, [NSThread currentThread]);
        });
        // 使用栅栏函数
        // dispatch_barrier_async(queue, ^{});
    }
}

利用信号量的API来进行代码改写

- (void)test {
    // 创建信号量
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"当前%d----线程%@", i, [NSThread currentThread]);
            // 打印任务结束后信号量解锁
            dispatch_semaphore_signal(sem);
        });
        // 由于异步执行,打印任务会较慢,所以这里信号量加锁
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    }
}

输出结果

当前0----线程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
当前1----线程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
当前2----线程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
当前3----线程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
当前4----线程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
当前5----线程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
当前6----线程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
当前7----线程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
当前8----线程<NSThread: 0x600000c2c000>{number = 5, name = (null)}
当前9----线程<NSThread: 0x600000c2c000>{number = 5, name = (null)}

如果当创建信号量时传入值为1又会怎么样呢?

  • i=0时有可能先打印,也可能会先发出wait信号量-1,但是wait之后信号量为0不会阻塞线程,所以进入i=1
  • i=1时有可能先打印,也可能会先发出wait信号量-1,但是wait之后信号量为-1阻塞线程,等待signal再执行下去
当前1----线程<NSThread: 0x600001448d40>{number = 3, name = (null)}
当前0----线程<NSThread: 0x60000140c240>{number = 6, name = (null)}
当前2----线程<NSThread: 0x600001448d40>{number = 3, name = (null)}
当前3----线程<NSThread: 0x60000140c240>{number = 6, name = (null)}
当前4----线程<NSThread: 0x60000140c240>{number = 6, name = (null)}
当前5----线程<NSThread: 0x600001448d40>{number = 3, name = (null)}
当前6----线程<NSThread: 0x600001448d40>{number = 3, name = (null)}
当前7----线程<NSThread: 0x60000140c240>{number = 6, name = (null)}
当前8----线程<NSThread: 0x600001448d40>{number = 3, name = (null)}
当前9----线程<NSThread: 0x60000140c240>{number = 6, name = (null)}

结论:

  • 创建信号量时传入值为1时,可以通过两次才堵塞
  • 传入值为2时,可以通过三次才堵塞

dispatch_source

应用场景:GCDTimer

定义及使用

dispatch_source是一种基本的数据类型,可以用来监听一些底层的系统事件

  • Timer Dispatch Source:定时器事件源,用来生成周期性的通知或回调
  • Signal Dispatch Source:监听信号事件源,当有UNIX信号发生时会通知
  • Descriptor Dispatch Source:监听文件或socket事件源,当文件或socket数据发生变化时会通知
  • Process Dispatch Source:监听进程事件源,与进程相关的事件通知
  • Mach port Dispatch Source:监听Mach端口事件源
  • Custom Dispatch Source:监听自定义事件源

主要使用的API:

  • dispatch_source_create: 创建事件源
  • dispatch_source_set_event_handler: 设置数据源回调
  • dispatch_source_merge_data: 设置事件源数据
  • dispatch_source_get_data: 获取事件源数据
  • dispatch_resume: 继续
  • dispatch_suspend: 挂起
  • dispatch_cancle: 取消

在iOS开发中一般使用NSTimer来处理定时逻辑,但NSTimer是依赖Runloop的,而Runloop可以运行在不同的模式下。如果NSTimer添加在一种模式下,当Runloop运行在其他模式下的时候,定时器就挂机了;又如果Runloop在阻塞状态,NSTimer触发时间就会推迟到下一个Runloop周期。因此NSTimer在计时上会有误差,并不是特别精确,而GCD定时器不依赖Runloop,计时精度要高很多

@property (nonatomic, strong) dispatch_source_t timer;
//1.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.创建timer
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//3.设置timer首次执行时间,间隔,精确度
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
//4.设置timer事件回调
dispatch_source_set_event_handler(_timer, ^{
    NSLog(@"GCDTimer");
});
//5.默认是挂起状态,需要手动激活
dispatch_resume(_timer);

使用dispatch_source自定义定时器注意点:

  • GCDTimer需要强持有,否则出了作用域立即释放,也就没有了事件回调
  • GCDTimer默认是挂起状态,需要手动激活
  • GCDTimer没有repeat,需要封装来增加标志位控制
  • GCDTimer如果存在循环引用,使用weak+strong或者提前调用dispatch_source_cancel取消timer
    dispatch_resumedispatch_suspend调用次数需要平衡
  • source在挂起状态下,如果直接设置source = nil或者重新创建source都会造成crash。正确的方式是在激活状态下调用dispatch_source_cancel(source)释放当前的source

GCD-API总结

API说明
dispatch_sync()同步执行
dispatch_async()异步执行
dispatch_queue_create()创建队列
dispatch_get_main_queue()获取主队列
dispatch_get_global_queue()获取全局队列
dispatch_after()延时执行
dispatch_once()一次性执行
dispatch_apply()提交队列
dispatch_group_create()创建调度组
dispatch_group_async()执行进组任务
dispatch_group_enter()/ dispatch_group_leave()将调度组中的任务未执行完毕的任务数目加减1(两个函数要配合使用)
dispatch_group_wait()设置等待时间(成功为0)
dispatch_barrier_sync()同步栅栏函数
dispatch_barrier_async()异步栅栏函数
dispatch_group_notify()监听队列组执行完毕
dispatch_semaphore_creat()创建信号量
dispatch_semaphore_wait()等待信号量
dispatch_semaphore_signal()释放信号量
dispatch_source_create创建源
dispatch_source_set_event_handler设置源事件回调
dispatch_source_merge_data源事件设置数据
dispatch_source_get_data获取源事件数据
dispatch_resume继续
dispatch_suspend挂起
dispatch_cancle取消

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue输入内容/链接生成二维码

方式一&#xff1a;qrcode&#xff08;无 icon 图标&#xff09; npm i qrcodejs2 --save完整代码 <template><div class"flex-box"><div>qrcode&#xff08;无 icon 图标&#xff09;</div><div class"qr-code" ref"qrCo…

MySQL Windows版本下载及安装时默认路径的修改

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、MySQL 下载二、默认路径修改1、安装前准备【非常重要】2、启动安装程序总结1、MySQL下载2、MySQL默认路径修改前言 MySQL 被Oracle收购后,各种操作规范及约束也相应的跟着来了,这不,只…

Linux服务器增加虚拟交换内存

文章目录 swap分区的创建1、查看磁盘使用情况2、添加Swap分区3、对交换文件格式化并转换为swap分区4、挂载并激活分区5、查看新swap分区是否正常添加并激活使用6、修改 fstab 配置&#xff0c;设置开机自动挂载该分区7、查看是否已经使用了交换内存 更改Swap配置查看当前的swap…

浅谈自动化测试

谈谈那些实习测试工程师应该掌握的基础知识&#xff08;一&#xff09;_什么时候才能变强的博客-CSDN博客https://blog.csdn.net/qq_17496235/article/details/131839453谈谈那些实习测试工程师应该掌握的基础知识&#xff08;二&#xff09;_什么时候才能变强的博客-CSDN博客h…

linux 指令最后一期

bc ---- linux下的计算器 bc 是一个计算器 我们输入&#xff1a;quit 来退出这个计算器 我们可以这样来用&#xff1a; uname -r uname –r指令&#xff1a; 语法&#xff1a;uname [选项] 功能&#xff1a; uname用来获取电脑和操作系统的相关信息。 补充说明&#xff1a…

springboot框架下,请使用@ConfigurationProperties替代@Value加载配置

一、背景 程序启动时&#xff0c;详细报错见下&#xff1a; 10:40:31.965 [main] ERROR org.springframework.boot.SpringApplication - Application run failed org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘redisDi…

【设计模式——学习笔记】23种设计模式——外观模式Facade(原理讲解+应用场景介绍+案例介绍+Java代码实现)

文章目录 案例引入介绍基本介绍类图出场角色 案例实现案例一类图代码实现 案例二类图代码实现 外观模式在Mybatis源码中的应用总结文章说明 案例引入 在家庭影院中&#xff0c;要享受一场电影&#xff0c;需要如下步骤&#xff1a; 直接用遥控器&#xff1a;统筹各设备开关开…

De Bruijin序列与魔术(一)——De Bruijin序列简介

早点关注我&#xff0c;精彩不错过&#xff01; 欢迎回到数学魔术系列&#xff01;久违了&#xff01; 在牌序领域&#xff0c;一个特别数学化也是很冷门的一个序&#xff0c;DeBruijin序列&#xff0c;算是经典中的经典了。但它在魔术圈里流传并不甚广的原因是&#xff0c;可扩…

再创佳绩!数据猿荣获《2023金融科技影响力品牌》奖

‍数据智能产业创新服务媒体 ——聚焦数智 改变商业 7月26日至27日&#xff0c;CFS2023第十二届财经峰会暨2023可持续商业大会在北京举行&#xff0c;峰会期间举行了开幕式、高层论坛、CFS致敬盛典、2023品牌创新展等主题活动&#xff0c;以及多场关于数字化转型、金融创新、新…

一分钟搞定谷歌浏览器无法翻译的问题

文章目录 前言一、找到hosts.xml文件二、以记事本方式打开hosts文件三、在hosts文件最下方加入以下内容&#xff0c;点击保存&#xff1a;四、刷新DNS五、关闭google浏览器重新打开即可。 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 据CSDN消息&…

【Java 高频面试闯关秘籍】大梦谁先觉,平生我自知

一、mysql使用innodb引擎&#xff0c;请简述mysql索引的最左前缀如何优化orderby语句&#xff1f; 关键点&#xff1a; &#xff08;1&#xff09;如果排序字段不在索引列上&#xff0c;filesort有两种算法&#xff1a;mysql就要启动双路排序和单路排序 &#xff08;2&#x…

Diffusion Model 浅学笔记

Diffusion Model Created by: 银晗 张 Created time: May 29, 2023 8:12 AM VAE → GAN →Diffusion 要讲扩散模型&#xff0c;不得不提VAE。VAE和GAN一样&#xff0c;都是从隐变量Z生成目标数据X。 它们假设隐变量服从某种常见的概率分布&#xff08;比如正态分布&#xff…

Android Unit Test

一、测试基础知识 1.1 测试级别 测试金字塔&#xff08;如图 2 所示&#xff09;说明了应用应如何包含三类测试&#xff08;即小型、中型和大型测试&#xff09;&#xff1a; 小型测试是指单元测试&#xff0c;用于验证应用的行为&#xff0c;一次验证一个类。 中型测试是指…

ROS中使用Kinect v2

ROS中使用Kinect v2 Kinect v2 简介 Kinect v2是微软公司推出的第二代Kinect深度摄像头&#xff0c;也称为Kinect for Xbox One或Kinect for Windows v2。它是Kinect系列产品的升级版本&#xff0c;于2013年首次发布。Kinect v2采用了一系列先进的传感器和技术&#xff0c;使其…

【MATLAB第60期】【更新中】基于MATLAB的ARMAX具有外生回归因子的移动平均自回归模型

【MATLAB第60期】【更新中】基于MATLAB的ARMAX具有外生回归因子的移动平均自回归模型 版本更新&#xff1a; 2023/7/29版本&#xff1a; 1.增加自定义参数&#xff0c;方便直接套数据运行。 pre_num3;%预采样数据个数 learn_pr0.85; %训练数据比例&#xff08;不包括预采样数…

通讯录--集合动态的文件版

简易的通讯录往往需要朴实的“烹饪”就能完成一道“美味的佳肴”。 我们需要一个通讯录&#xff0c;能够存储联系人的信息&#xff0c;能够对联系人的信息进行增删查改&#xff0c;查询&#xff0c;按姓名排序。相比对之前的三子棋、扫雷&#xff0c;有了一定的了解&#xff0c…

一起学数据结构(2)——线性表及线性表顺序实现

目录 1. 什么是数据结构&#xff1a; 1.1 数据结构的研究内容&#xff1a; 1.2 数据结构的基本概念&#xff1a; 1.2.1 逻辑结构&#xff1a; 1.2.2 存储结构&#xff1a; 2. 线性表&#xff1a; 2.1 线性表的基本定义&#xff1a; 2.2 线性表的运用&#xff1a; 3 .线性…

【学会动态规划】打家劫舍 II(12)

目录 动态规划怎么学&#xff1f; 1. 题目解析 2. 算法原理 1. 状态表示 2. 状态转移方程 3. 初始化 4. 填表顺序 5. 返回值 3. 代码编写 写在最后&#xff1a; 动态规划怎么学&#xff1f; 学习一个算法没有捷径&#xff0c;更何况是学习动态规划&#xff0c; 跟我…

JavaScript学习 -- SM3算法基本原理

SM3算法是一种由国家密码管理局发布的哈希算法&#xff0c;被广泛用于数字签名和消息认证等应用中。在JavaScript中&#xff0c;我们可以使用第三方库来计算数据的SM3哈希值。本篇文章将介绍SM3算法的基本原理和相关技术&#xff0c;并提供一些实例来演示如何在JavaScript中使用…

深度剖析六大国产CPU

前言 CPU 是底层硬件基础设施中的核心&#xff0c;当前主流芯片架构为 ARM 和 X86&#xff0c;均为国外主导&#xff0c;芯片国产化率较低。“十五”期间&#xff0c;国家启动发展国产 CPU 的泰山计划&#xff0c;863 计划也提出自主研发 CPU。2006 年核高基专项启动&#xff…