当前位置:首页 » 《关注互联网》 » 正文

iOS多线程面试题汇总与解析_Mr_yu__的博客

25 人参与  2021年08月23日 08:23  分类 : 《关注互联网》  评论

点击全文阅读


前言

其实在我写这边博客之前,也在查阅不好资料,但是发现网上很多人说的内容总结,其实并不正确,导致自己也踩了不少坑,所以才想着重新总结一下,给自己做个参考,也当是复习一下,当然我也可能有不对的地方,希望可以得到改正

知识点梳理

基本概念简述

  • 1.同步函数dispatch_sync 必须等待当前语句执行完毕,才会进行下一条,在当前执行block任务

  • 2.异步函数dispatch_async 不用等当前语句执行完毕,就可以执行下一条语句,会开启线程执行block,异步多线程的代名词,主队列例外

还原最基础的写法,很重要

- (void)syncTest{
    // 把任务添加到队列 --> 函数
    // 任务 _t ref c对象
    dispatch_block_t block = ^{
        NSLog(@"hello GCD");
    };
    //串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.ycx.cn", NULL);
    // 函数
    dispatch_async(queue, block);
}

上面就是把同步函数步骤进行拆分,也是为了便于理解

而接下来会以实际例子去测试再结合理论知识去总结概括,为了方便大家查阅,我全部是都代码形式复制过来,没有截图,也方便大家更快的找到自己想看的题目类型,当然也希望有大佬给出意见和新的题型让我补充🙏🙏🙏🙏

建议大家先分析之后在看结果,我验证的时候翻车不少😂😂😂😂

例题1:

/**
 主队列同步
 不会开线程
 */
- (void)mainSyncTest{
    
    NSLog(@"0");
    // 等
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"1");
    });
    NSLog(@"2");
}

解析:主队列 + 同步函数(sync函数) 产生死锁 1 2互相等待

例题2:

/**
 串行同步队列 : FIFO: 先进先出
 */
- (void)serialSyncTest{
    //1:创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("ycx", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i<20; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        });
    }
}

解析:串行队列 先进先出 虽然连续添加了20次任务 但是必须依次执行,所以结果是依次打印1-19

例题3:

/**
 主队列异步
 不会开线程 顺序
 */
- (void)mainAsyncTest{
    NSLog(@"0");
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"1---%@",[NSThread currentThread]);
    });
    NSLog(@"2---%@",[NSThread currentThread]);
}

解析:主队列 串行 + 异步 还是依次执行 但是 1 必须等 2执行

例题4:

/**
 全局异步
 全局队列:一个并发队列
*/
- (void)globalAsyncTest{
    
    for (int i = 0; i<20; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"hello queue");
}

解析:全局队列 并发 + 异步 多线程同步执行 执行次数还是20次 只不过不同线程执行次数随机

例题5:

/**
 异步并发: 有了异步函数不一定开辟线程
 */
- (void)concurrentAsyncTest{
    //1:创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("ycx", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i<20; i++) {
        dispatch_async(queue, ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"hello queue");
}

解析:结果和上面差不多 会另开线程,但是开几条线程不定,看线程池调度状况

例题6:

/**
 全局同步
 全局队列:一个并发队列
 */
- (void)globalSyncTest{
    for (int i = 0; i<20; i++) {
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"hello queue");
}

解析:全局队列 并发 + 同步 依次执行 NSLog 最后执行,不开辟新线程

例题7:

/**
 同步并发 : 堵塞 同步锁  队列 : resume supend   线程 操作, 队列挂起 任务能否执行
 */
- (void)concurrentSyncTest{

    //1:创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("ycx", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i<20; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"hello queue");
}

解析:上面全局队列结果一样

例题8:

- (void)textDemo1{
    dispatch_queue_t queue = dispatch_queue_create("ycx", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

解析:并发队列 异步+同步 结果:1 5和(234)并发 也就是说234顺序不会变 而5可能在他们之间任意位置出现 大概率15234

例题9:

- (void)textDemo{
    
    dispatch_queue_t queue = dispatch_queue_create("ycx", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    // 耗时
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

解析:并发队列 异步+异步 结果:1 首位 2先于34,34位置不定 而5可在234任意位置 大概率是15243 一般情况下主线程肯定执行更稳定 另开线程在执行任务会稍微慢些 虽然是并发,但是本质CPU快速切换的过程 所以才说大概率是15243

例题10:

/**
 串行异步队列
 */
- (void)serialAsyncTest{
    //1:创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("ycx", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i<20; i++) {
        dispatch_async(queue, ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"hello queue");
}

解析:其实和第一个例子相似,主队列也是串行队列,所以串行队列异步,不开线程,并且异步函数要在当前线程函数任务执行完毕在执行,所以NSLog永远先执行

例题11:

/**
 可变数组 线程不安全 解决办法
 */
- (void)demo3{
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
    //答案不确定
    
    //dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);  答案1000
    for (int i = 0; i<1000; i++) {
        dispatch_async(concurrentQueue, ^{
            NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
            NSURL *url = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];
            
            dispatch_barrier_async(concurrentQueue , ^{
                [self.mArray addObject:image];
            });
        });
    }
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"数组的个数:%zd",self.mArray.count);
}

解析:栅栏函数 配合并发队列可以对异步并发起到拦截作用,确保在数组插入数据过程的线程安全,依次插入数据,但是栅栏函数只能对自定义队列有效,系统队列不行,因此答案不确定,如果是自定义并发队列,就是数组个数就是1000

同时这里也体现了栅栏函数使用的局限性,它只能对同一个队列进行拦截操作,如果是不同队列就行不通了,比如在我们使用一些第三方库的方法时,你也不知道他们底层是使用的什么队列,自然而然没办法使用栅栏函数处理

例题12:

- (void)demo2{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
        dispatch_queue_t concurrentQueue1 = dispatch_queue_create("kc", DISPATCH_QUEUE_CONCURRENT);

//    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
    // 这里是可以的额!
    /* 1.异步函数 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"1");
    });
    
    dispatch_async(concurrentQueue, ^{
        sleep(1);
        NSLog(@"2");
    });
    /* 2. 栅栏函数 */ // - dispatch_barrier_sync
    dispatch_barrier_sync(concurrentQueue, ^{
        NSLog(@"----%@-----",[NSThread currentThread]);
    });
    /* 3. 异步函数 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"3");
    });
    // 4
    NSLog(@"4");
 
}

解析:栅栏函数同步+并发队列 依然可以起到同步的作用,但是会阻塞当前线程,也就是说3 要等 1和2执行完毕,且栅栏函数的任务也执行完毕 ,才能执行,同时4也必须等栅栏函数执行完毕,所以12随机 先于 34随机

例题13:

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待
        sleep(2);
        NSLog(@"执行任务1");
        NSLog(@"任务1完成");
    });
    
    //任务2
    dispatch_async(queue, ^{
        sleep(2);

        NSLog(@"执行任务2");
        NSLog(@"任务2完成");
        dispatch_semaphore_signal(sem); // 发信号
    });
    
    //任务3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        
        NSLog(@"执行任务3");
        NSLog(@"任务3完成");
        dispatch_semaphore_signal(sem);
    });

解析:初始信号量0,全局并发队列异步函数,按理说并发执行,但是初始信号量0,所以任务1和任务3都在等待,任务2执行完毕后,发送信号,先等待的先接受,所以大概率执行任务1,任务3不执行

例题14:

    dispatch_queue_t queue = dispatch_queue_create("com.ycx.cn", DISPATCH_QUEUE_CONCURRENT);
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);

    //任务1
    dispatch_async(queue, ^{
        sleep(10);
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待
        NSLog(@"执行任务1");
        NSLog(@"任务1完成");
    });
    
    //任务2
    dispatch_async(queue, ^{
        NSLog(@"执行任务2");
        NSLog(@"任务2完成");
        dispatch_semaphore_signal(sem); // 发信号
    });
    
    //任务3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务3");
        NSLog(@"任务3完成");
        dispatch_semaphore_signal(sem);
    });


    //任务4
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务4");
        NSLog(@"任务4完成");
        dispatch_semaphore_signal(sem);
    });

解析:首先任务2先执行毫无疑问,任务3和4会先于1,因为1卡线程10秒,等待会比3和4晚,而3和4执行后会在发送一下信号,而任务1呢,因为信号的机制,尽管等待10秒,但只要执行等待信号函数,就能接收到信号量现在还是1,所以还是会执行

例题15:

    dispatch_queue_t queue = dispatch_queue_create("com.ycx.cn", DISPATCH_QUEUE_CONCURRENT);
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    dispatch_queue_t queue1 = dispatch_queue_create("ycx", NULL);

    //任务1
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待
        sleep(2);
        NSLog(@"执行任务1");
        NSLog(@"任务1完成");
    });
    
    //任务2
    dispatch_async(queue1, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        sleep(2);
        NSLog(@"执行任务2");
        NSLog(@"任务2完成");
    });
    
    //任务3
    dispatch_async(queue1, ^{
        sleep(2);
        NSLog(@"执行任务3");
        NSLog(@"任务3完成");
        dispatch_semaphore_signal(sem);
    });
    
    //任务4
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        sleep(2);
        NSLog(@"执行任务4");
        NSLog(@"任务4完成");
        dispatch_semaphore_signal(sem);
    });

解析:首先要看清楚queue和queue1两个自定义队列,一个并发,一个串行,因为任务2和3是在串行队列执行,尽管是异步函数,但是没有另开线程,而任务2又有信号等待造成线程阻塞,无法执行完毕,那么任务3也就没办法执行,也就没发发送信号,所以没有任务能够执行

例题16:
实现一个场景:图片异步加载,确保全部加载完成后,再使用,保证数据安全

第一个方案:

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.ycx.cn", DISPATCH_QUEUE_CONCURRENT);
    
    __weak typeof(self) weakSelf = self;
    
    dispatch_group_async(group, queue, ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        //创建调度组
        NSString *logoStr1 = @"https://img1.baidu.com/it/u=1313595101,2257100959&fm=253&fmt=auto&app=120&f=GIF?w=450&h=300";
        NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr1]];
        UIImage *image1 = [UIImage imageWithData:data1];
        dispatch_barrier_async(queue, ^{
            [strongSelf.mArray addObject:image1];
            NSLog(@"showTime----111111111");
        });
        
    });

    dispatch_group_async(group, queue, ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        //创建调度组
        NSString *logoStr2 = @"https://img1.baidu.com/it/u=1313595101,2257100959&fm=253&fmt=auto&app=120&f=GIF?w=450&h=300";
        NSData *data2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr2]];
        UIImage *image2 = [UIImage imageWithData:data2];
        dispatch_barrier_async(queue, ^{
            [strongSelf.mArray addObject:image2];
            NSLog(@"showTime----222222222");
        });
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
       NSLog(@"数组个数:%ld",self.mArray.count);
    });

解析:常见调度组的使用场景,确保图片异步加载完毕,再统一处理,使用栅栏函数确保数组写入安全

第二个方案:

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_queue_t queue1 = dispatch_queue_create("com.ycx.cn", DISPATCH_QUEUE_CONCURRENT);
    
    __weak typeof(self) weakSelf = self;
    
    dispatch_group_async(group, queue, ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        //创建调度组
        NSString *logoStr1 = @"https://img1.baidu.com/it/u=1313595101,2257100959&fm=253&fmt=auto&app=120&f=GIF?w=450&h=300";
        NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr1]];
        UIImage *image1 = [UIImage imageWithData:data1];
        dispatch_barrier_sync(queue1, ^{
            [strongSelf.mArray addObject:image1];
            NSLog(@"showTime----111111111");
        });
    });
    
    dispatch_group_async(group, queue, ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        //创建调度组
        NSString *logoStr2 = @"https://img1.baidu.com/it/u=1313595101,2257100959&fm=253&fmt=auto&app=120&f=GIF?w=450&h=300";
        NSData *data2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr2]];
        UIImage *image2 = [UIImage imageWithData:data2];
        dispatch_barrier_sync(queue1, ^{
            [strongSelf.mArray addObject:image2];
            NSLog(@"showTime----222222222");
        });
    });
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"数组个数:%ld",self.mArray.count);

解析:dispatch_group_wait函数会等待前面group里面的任务执行完毕在执行,后面的任务,会阻塞当前线程,从而达到统一处理,同时使用dispatch_barrier_sync和并发队列,确保数据写入的安全,切记使用dispatch_barrier_sync是一定要另外再创建一个队列,如果和调度组使用同一个队列,就会造成死锁,互相等待执行,如下

例题17:

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.ycx.cn", DISPATCH_QUEUE_CONCURRENT);
    
    __weak typeof(self) weakSelf = self;
    
    dispatch_group_async(group, queue, ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        //创建调度组
        NSString *logoStr1 = @"https://img1.baidu.com/it/u=1313595101,2257100959&fm=253&fmt=auto&app=120&f=GIF?w=450&h=300";
        NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr1]];
        UIImage *image1 = [UIImage imageWithData:data1];
        dispatch_barrier_sync(queue, ^{
            [strongSelf.mArray addObject:image1];
            NSLog(@"showTime----111111111");
        });
        NSLog(@"showTime----333333");
    });

    dispatch_group_async(group, queue, ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        //创建调度组
        NSString *logoStr2 = @"https://img1.baidu.com/it/u=1313595101,2257100959&fm=253&fmt=auto&app=120&f=GIF?w=450&h=300";
        NSData *data2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr2]];
        UIImage *image2 = [UIImage imageWithData:data2];
        dispatch_barrier_sync(queue, ^{
            [strongSelf.mArray addObject:image2];
            NSLog(@"showTime----222222222");
        });
        NSLog(@"showTime----444444");
    });

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"数组个数:%ld",self.mArray.count);

解析:死锁在于 dispatch_barrier_sync 会堵塞当前线程执行,而栅栏函数又必须等待前面加入队列queue的任务执行完毕才可以执行,最终就会在queue队列中的某个线程死锁,group调度组任务永远无法执行完毕,dispatch_group_wait就一直等待

例题18:
接着例题16的第三个方案

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.ycx.cn", DISPATCH_QUEUE_CONCURRENT);
    
    __weak typeof(self) weakSelf = self;
    
    dispatch_group_enter(group);
   
    dispatch_async(queue, ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        //创建调度组
        NSString *logoStr1 = @"https://img1.baidu.com/it/u=1313595101,2257100959&fm=253&fmt=auto&app=120&f=GIF?w=450&h=300";
        NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr1]];
        UIImage *image1 = [UIImage imageWithData:data1];
        dispatch_barrier_async(queue, ^{
            [strongSelf.mArray addObject:image1];
            NSLog(@"showTime----111111111");
            dispatch_group_leave(group);
        });
    });

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        //创建调度组
        NSString *logoStr2 = @"https://img1.baidu.com/it/u=1313595101,2257100959&fm=253&fmt=auto&app=120&f=GIF?w=450&h=300";
        NSData *data2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr2]];
        UIImage *image2 = [UIImage imageWithData:data2];
        dispatch_barrier_async(queue, ^{
            [strongSelf.mArray addObject:image2];
            NSLog(@"showTime----222222222");
            dispatch_group_leave(group);
        });
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
       NSLog(@"数组个数:%ld",self.mArray.count);
    });

解析:dispatch_group_enter和dispatch_group_leave 进组出组配合使用,有点像信号量,也是必须进出组刚好对等,才可以执行dispatch_group_notify里面的任务

如果后续发现一些有意思的题目,也会继续收录补充,主要也是为了加深对于多线程的理解和运用

今天的分享就到次为止,下一篇会着重分析GCD的底层源码


点击全文阅读


本文链接:http://zhangshiyu.com/post/25937.html

队列  执行  例题  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1