多线程

作者: 雪碧童鞋 | 来源:发表于2021-08-19 10:06 被阅读0次

同步、异步、并发、串行

同步和异步决定能否开启新的线程
同步: 在当前线程中执行任务,不具备开启新线程的能力
异步:在新的线程中执行任务,具备开启新线程的能力
并发和串行主要影响任务的执行方式
并发:多个任务并发(同时)执行
串行:一个任务执行完毕后,再执行下一个任务

并发队列 手动创建串行队列 主队列
同步(sync) 没有开启新线程<br />串行执行任务 没有开启新线程<br />串行执行任务 没有开启新线程<br />串行执行任务
异步(async) 开启新线程<br />并发执行任务 开启新线程<br />串行执行任务 没有开启新线程<br />串行执行任务

使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)

1. dispatch_after

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

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

2. dispatch_once

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

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

3. dispatch_apply

dispatch_apply将指定的Block追加到指定的队列中重复执行,并等到全部的处理执行结束。
模拟for循环,当要对NSArray类对象的所有元素执行处理时,不必一个一个的编写for循环部分

 //1.创建NSArray类对象
     NSArray *array = @[@"a", @"b", @"c", @"d", @"e", @"f", @"g", @"h", @"i", @"j"];
 
     //2.创建一个全局队列
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
     //3.通过dispatch_apply函数对NSArray中的全部元素进行处理,并等待处理完成
     dispatch_apply([array count], queue, ^(size_t index) {
         NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
     });
   //4.会在上面执行完之后执行
     NSLog(@"done");

4. dispatch_group_t

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

1. dispatch_group_async
// 创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 添加异步任务
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务1-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务2-%@", [NSThread currentThread]);
        }
    });
    // 等前面的任务执行完毕后,会自动执行这个任务
     dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务3-%@", [NSThread currentThread]);
        }
    });
2. dispatch_group_enter & dispatch_group_leave
 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(@"刷新页面");
    });
3.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(@"刷新页面");
    });
}

5. 栅栏函数dispatch_barrier_sync & dispatch_barrier_async

先执行栅栏前任务,再执行栅栏任务,最后执行栅栏后任务
应用场景:同步锁

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

注意点:

  1. 使用全局队列起不到栅栏函数的作用
  2. 使用全局队列时由于对全局队列造成堵塞,可能致使系统其他调用全局队列的地方也堵塞从而导致崩溃(并不是只有你在使用这个队列)
  3. 栅栏函数只能控制同一并发队列
- (void)test {
    dispatch_queue_t queue = dispatch_queue_create("HJTest", 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]);
}

由于并发队列异步执行任务是乱序执行完毕的,所以使用栅栏函数可以很好的控制队列内任务执行的顺序

6. dispatch_semaphore_t

  • dispatch_semaphore_create():创建信号量

  • dispatch_semaphore_wait():等待信号量,信号量减1。当信号量< 0时会阻塞当前线程,根据传入的等待时间决定接下来的操作——如果永久等待将等到信号(signal)才执行下去

  • dispatch_semaphore_signal():释放信号量,信号量加1。当信号量>= 0 会执行wait之后的代码

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

以下代码也可以使用栅栏函数实现

 // 创建信号量
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    dispatch_queue_t queue = dispatch_queue_create("HJTest", 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);
    }

7. dispatch_source

1.定义及使用

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: 取消
@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首次执行时间,间隔,精确度 leeway:期望容忍值
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

相关文章

  • iOS多线程 NSOperation

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程 pthread、NSThread

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程: GCD

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程运用

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程基础

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • 多线程介绍

    一、进程与线程 进程介绍 线程介绍 线程的串行 二、多线程 多线程介绍 多线程原理 多线程的优缺点 多线程优点: ...

  • iOS进阶之多线程管理(GCD、RunLoop、pthread、

    深入理解RunLoopiOS多线程--彻底学会多线程之『GCD』iOS多线程--彻底学会多线程之『pthread、...

  • iOS多线程相关面试题

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • 多线程之--NSOperation

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • iOS多线程之--NSThread

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

网友评论

      本文标题:多线程

      本文链接:https://www.haomeiwen.com/subject/aayibltx.html