• iOS - 多线程-GCD


    iOS - 多线程-GCD

    1. 常见多线程方案

    NSThreadGCDNSOperation底层都依赖于pthread

    2. GCD

    2.1 GCD的常见函数

    GCD中有2个用来执行任务的函数
    • 同步的方式执行任务
      dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

      1. queue:队列
      2. block:任务
    • 异步的方式执行任务
      dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

    • GCD源码:https://github.com/apple/swift-corelibs-libdispatch

    2.2 GCD的队列

    2.2.1 GCD的队列可以分为2大类型
    • 并发队列(Concurrent Dispatch Queue)

      1. 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
      2. 并发功能只有在异步dispatch_async)函数下才有效
    • 串行队列(Serial Dispatch Queue)

      1. 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

    2.3 容易混淆的术语

    2.4.1 有4个术语比较容易混淆:同步、异步并发、串行
    • 同步异步主要影响:能不能开启新的线程

      • 同步:在当前线程中执行任务,不具备开启新线程的能力
      • 异步:在新的线程中执行任务,具备开启新线程的能力
    • 并发串行主要影响:任务的执行方式

      • 并发多个任务并发(同时)执行
      • 串行一个任务执行完毕后,再执行下一个任务

    2.4 各种队列的执行效果

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

    3. 死锁

    3.1 死锁示例

    // 问题:以下代码是在主线程执行的,会不会产生死锁?
    NSLog(@"执行任务1");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
       NSLog(@"执行任务2");
    });
    NSLog(@"执行任务3");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.2 死锁分析

    如上代码

    • 正常情况应该是顺序执行任务1任务2任务3
    • 任务2使用dispatch_sync同步执行方式,放入主线程队列,因此任务2需要排队等待前面的任务执行完成后才执行
    • 但是当前方法体viewDidLoad可以认为就是一个任务在执行,但是执行到任务2dispatch_sync处,会等待dispatch_sync执行完成再继续往下执行
    • 此时,相当于任务2等待当前执行任务执行完成,当前执行任务也在等待任务2执行完成,相互等待因此造成线程死锁

    3.3 其他示例

    3.3.1 interview02

    - (void)interview02 {
        // 问题:interview01中的sync,改成 async。会不会产生死锁?不会!
        /* 打印日志:
            执行任务1
            执行任务3
            执行任务2
         */
        NSLog(@"执行任务1");
        dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_async(queue, ^{
            NSLog(@"执行任务2");
        });
        NSLog(@"执行任务3");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    3.3.2 interview03

    - (void)interview03 {
        // 会不会产生死锁?会!
        /*
         分析:
         执行任务2 后,同步等待任务2 执行,但是因为queue是串行的,所以会相互等待
         */
        NSLog(@"执行任务1");
        dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue, ^{
            NSLog(@"执行任务2");
            dispatch_sync(queue, ^{
                NSLog(@"执行任务3");
            });
            NSLog(@"执行任务4");
        });
        NSLog(@"执行任务5");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.3.3 interview04

    - (void)interview04 {
        // interview03改为并发队列(DISPATCH_QUEUE_CONCURRENT),会不会死锁?不会!
        NSLog(@"执行任务1");
        dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queue, ^{
            NSLog(@"执行任务2");
            dispatch_sync(queue, ^{
                NSLog(@"执行任务3");
            });
            NSLog(@"执行任务4");
        });
        NSLog(@"执行任务5");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    4. 案例

    4.1 案例1

    如下代码打印什么:

    - (void)test {
        NSLog(@"2");
    }
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_async(queue, ^{
            NSLog(@"1");
            [self performSelector:@selector(test) withObject:nil afterDelay:0];
            NSLog(@"3");
        });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    打印结果:

    观察到,2不会打印

    去掉dispatch_async又会怎么样

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
        NSLog(@"1");
        [self performSelector:@selector(test) withObject:nil afterDelay:0];
        NSLog(@"3");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5


    这时候都有打印,只不过打印顺序1>3>2

    接着,回到dispatch_async里执行,但是把afterDelay去掉

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_async(queue, ^{
            NSLog(@"1");
            [self performSelector:@selector(test) withObject:nil];
            NSLog(@"3");
        });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8


    打印结果是1>2>3,这次打印顺序是正常的

    4.2 分析

    上面的例子中,主要是考察runloop多线程的相关知识

    • 首先,在dispatch_async中使用- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;方法来执行,使用afterDelay:0看似是在没有延迟的情况下执行,实际上因为该方法是基于 runloop的,相当于往runloop添加一个定时器,但是因为此时我们是在子线程中执行的,子线程中的runloop默认不会开启,所以test方法没有执行。我们尝试开启runloop
    dispatch_async(queue, ^{
        NSLog(@"1");
        [self performSelector:@selector(test) withObject:nil afterDelay:0];
        NSLog(@"3");
        
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    可以看到,2打印了

    • 接着,我们去掉了dispatch_async,发现打印结果是1>3>2,为什么3会比2先打印的?
      使用afterDelay:0看似是在没有延迟的情况下执行,实际上因为该方法是基于 runloop的定时器,虽然没有延迟设置为 0,但是runloop的定时器是在被唤醒的时候处理定时器的,但是在进入休眠之前会处理完点击事件,因此看到的打印结果是13先打印,然后打印2

    • 最后,回到dispatch_async中执行,只不过使用的是- (id)performSelector:(SEL)aSelector withObject:(id)object;方法来执行,查看源码

      该方法实际是直接使用objc_msgSend方法执行,相当于我们直接[self test]这样调用方法,所以这时候打印顺序是正常的1>2>3

    5. 拓展

    GNUstep

    GNUstep是GNU计划的项目之一,它将Cocoa的OC库重新开源实现了一遍

    源码地址:https://gnustep.github.io/resources/downloads.html

    虽然GNUstep不是苹果官方源码,但还是具有一定的参考价值

    @oubijiexi

  • 相关阅读:
    【javaweb】学习日记Day8 - Mybatis入门 Mysql 多表查询 事务 索引
    java基于springboot+vue+elementui的校园疫情防控系统 前后端分离
    SQL-按每天日期查询数据 没有数据缺失数据返回0 无需建表 若无当日数据也返回值 图表按天查询数据返回
    隔离和降级
    2023.11.17 hadoop之HDFS进阶
    一体机电脑辐射超标整改
    电磁波谱下的世界
    【EasyRL学习笔记】第二章 Markov Decision Process 马尔可夫决策过程
    粽子产线的速度提升
    第55篇-某did滑块流程分析-滑动验证码【2023-10-12】
  • 原文地址:https://blog.csdn.net/sharp521/article/details/138071626