• 多线程学习(一)


    开局几道面试题:

    你理解的多线程?
    iOS的多线程方案有哪几种?你更倾向于哪一种?
    你在项目中用过GCD吗?
    GCD的队列类型
    说一下operationQueue和GCD的区别,以及各自的优势
    线程安全的处理手段有哪些?

    OC你了解的锁有哪些?
    自旋锁和互斥锁对比
    使用以上锁需要注意哪些?
    用C/OC/C++,任选其一,实现自选或互斥

    iOS中常见多线程方案
    在这里插入图片描述

    GCD

    同步、异步

    GCD中有2个用来执行任务的函数:同步、异步

    用同步的方式执行任务
    dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

    用异步的方式执行任务
    dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

    其中,
    queue:队列
    block:任务

    队列

    GCD的队列可以分为2大类型:并发队列、串行队列
    并发队列(Concurrent Dispatch Queue)
    可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
    并发功能只有在异步(dispatch_async)函数下才有效

    串行队列(Serial Dispatch Queue)
    让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

    GCD带有creat的标识,不需要release
    CF开头带有creat的标识,需要release

    容易混淆的术语

    同步、异步、并发、串行

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

    并发串行主要影响:任务的执行方式 队列
    并发:多个任务并发(同时)执行
    串行:一个任务执行完毕后,再执行下一个任务

    dispatch_sync和dispatch_async用来控制是否要开启新的线程
    队列的类型,决定了任务的执行方式

    主队列其实就是一个特殊的串行队列
    dispatch_get_global_queue队列是一个全局并发队列
    在这里插入图片描述

    问:以下代码会执行吗?

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        NSLog(@"执行任务1");
        dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_sync(queue, ^{
            NSLog(@"执行任务2");
        });
        NSLog(@"执行任务3");
    }
    

    以上代码是有问题的
    首先,我们知道queue是一个队列,既然是队列就要遵守FIFO规则。
    而viewDidLoad函数的执行也是一个任务,该任务在主队列里面执行。

    队列里面装的是 任务
    dispatch_sync:立马在当前线程执行任务,执行完毕才能继续往下执行
    dispatch_async:不需要立马执行完任务,不需要等待返回,就可以继续执行下面的任务

    那么,

    dispatch_sync(queue, ^{
    	NSLog(@"执行任务2");
    });
    

    必须要执行完,才能执行后面的任务NSLog(@"执行任务3");

    dispatch_sync(queue, ^{
    	NSLog(@"执行任务2");
    });
    

    想要执行完,必须把里面的blockNSLog(@"执行任务2");执行完
    而NSLog(@"执行任务2");是加在队列的最后面的,必须等队列中前面的任务执行完毕后,才能执行NSLog(@"执行任务2");。而队列中NSLog(@"执行任务2");的前一个任务是{内容},{内容} 想执行完毕的标志是NSLog(@"执行任务3");执行完毕。

    也就是
    dispatch_sync(queue, ^{ NSLog(@"执行任务2"); });等着NSLog(@"执行任务2");,NSLog(@"执行任务2");等着NSLog(@"执行任务3");,NSLog(@"执行任务3");等着dispatch_sync(queue, ^{ NSLog(@"执行任务2"); });
    造成了循环等待
    在这里插入图片描述

    问:以下代码执行情况?

    在这里插入图片描述

    dispatch_async异步执行,不需要返回即可执行下面的代码,因此,不阻塞。

    在这里插入图片描述

    在这里插入图片描述

    上面两个同样的代码,执行结果不一样,表明,任务2可以开启子线程。而任务2和任务3哪个先执行,不一定。

    在这里插入图片描述

    执行任务1被执行没问题。
    由于第26行是一个dispatch_async异步,因此执行任务5打印也没问题。

    由于25行创建的队列是一个串行队列,因此,任务是一个一个执行。
    27行执行任务2打印没问题,并且开启了子线程。

    28行是dispatch_sync同步,也就是必须等执行任务3执行完毕,才会执行任务4,而由于是串行队列,执行顺序是27-28-31执行完毕后才会执行29。造成循环等待,跟之前讲的在主线程阻塞是一个道理,只不过这次不是阻塞在主线程,而是阻塞在子线程。

    总结

    使用dispatch_sync往 当前 — 串行队列 中添加任务,会卡住当前的串行队列,从而产生死锁。

    在这里插入图片描述

    从上图中可以看出,dispatch_get_global_queue取出的队列是全局队列,并且是同一个。
    dispatch_get_global_queue队列是一个全局并发队列

    问:下面代码运行结果

    在这里插入图片描述

    结果:不会崩溃,但是不会开启100个线程


    问:下面代码的运行结果是什么?为什么?

    在这里插入图片描述

    只有任务1和任务3被执行打印,而任务2没有执行。

    在这里插入图片描述

    使用[self performSelector:@selector(test) withObject:self];就可以顺序打印任务1-2-3。

    如果将[self performSelector:@selector(test) withObject:self afterDelay:0.2];放在主线程,而不放在子线程,又会如何?

    在这里插入图片描述

    可以看出:在主线程可以正常打印,这是

    为什么呢?

    通过源码我们可以看到:

    + (id)performSelector:(SEL)sel withObject:(id)obj {
        if (!sel) [self doesNotRecognizeSelector:sel];
        return ((id(*)(id, SEL, id))objc_msgSend)((id)self, sel, obj);
    }
    

    其就是一个objc_msgSend,也就是[self performSelector:@selector(test) withObject:self];就是objc_msgSend(self, test)。因此,可以打印。

    在这里插入图片描述

    可以看到- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;是在runloop文件下的。
    也就是- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;有定时器功能,因此依赖于runloop下的定时器。然而,子线程默认runloop是没有打开的,需要自己手动去打开,因为没有打开runloop的执行,因此这句代码没有执行。即使时间是0.0,也是要加定时器,还是跟runloop有关。
    因此,将runloop启动,即可执行定时器任务。
    在这里插入图片描述

    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];这段代码去掉,也会执行runloop,因此,runloop里面有timer,不为空,因此,可以执行。

    面试题:在这里插入图片描述

    结果分析:
    由于[thread start]一运行,36行代码就被执行了,运行完这个block,该thread就结束了。再执行39行代码,则target thread exited。Crash

    在这里插入图片描述

    结果分析:
    由于加上了37-38两行代码,使得runloop被添加到线程里面,使得线程没有被销毁,因此可以执行任务2。当然,你把37行代码去掉,只保留38行代码也是可以达到同样的效果。

    疑问:

    为何3在最前面?

    为何打印顺序是2-4,而不是4-2?

    面试题

    如何用GCD实现:
    异步并发执行任务1、任务2
    等任务1、任务2都执行完毕后,再回到主线程执行任务3

    在这里插入图片描述

    执行结果:
    2020-06-12 11:18:27.812550+0800 GCD[43772:1429112] 执行任务2-<NSThread: 0x600000138f40>{number = 8, name = (null)}
    2020-06-12 11:18:27.812607+0800 GCD[43772:1423846] 执行任务1-<NSThread: 0x6000001334c0>{number = 3, name = (null)}
    2020-06-12 11:18:27.812778+0800 GCD[43772:1429112] 执行任务2-<NSThread: 0x600000138f40>{number = 8, name = (null)}
    2020-06-12 11:18:27.813070+0800 GCD[43772:1423846] 执行任务1-<NSThread: 0x6000001334c0>{number = 3, name = (null)}
    2020-06-12 11:18:27.813158+0800 GCD[43772:1423846] 执行任务1-<NSThread: 0x6000001334c0>{number = 3, name = (null)}
    2020-06-12 11:18:27.813175+0800 GCD[43772:1429112] 执行任务2-<NSThread: 0x600000138f40>{number = 8, name = (null)}
    2020-06-12 11:18:27.813358+0800 GCD[43772:1423846] 执行任务1-<NSThread: 0x6000001334c0>{number = 3, name = (null)}
    2020-06-12 11:18:27.813491+0800 GCD[43772:1429112] 执行任务2-<NSThread: 0x600000138f40>{number = 8, name = (null)}
    2020-06-12 11:18:27.813768+0800 GCD[43772:1429112] 执行任务2-<NSThread: 0x600000138f40>{number = 8, name = (null)}
    2020-06-12 11:18:27.816792+0800 GCD[43772:1423846] 执行任务1-<NSThread: 0x6000001334c0>{number = 3, name = (null)}
    2020-06-12 11:18:27.817313+0800 GCD[43772:1423846] 执行任务3-<NSThread: 0x6000001334c0>{number = 3, name = (null)}
    2020-06-12 11:18:27.817402+0800 GCD[43772:1423846] 执行任务3-<NSThread: 0x6000001334c0>{number = 3, name = (null)}
    2020-06-12 11:18:27.817503+0800 GCD[43772:1423846] 执行任务3-<NSThread: 0x6000001334c0>{number = 3, name = (null)}
    2020-06-12 11:18:27.817584+0800 GCD[43772:1423846] 执行任务3-<NSThread: 0x6000001334c0>{number = 3, name = (null)}
    2020-06-12 11:18:27.817696+0800 GCD[43772:1423846] 执行任务3-<NSThread: 0x6000001334c0>{number = 3, name = (null)}
    

    可以看到,任务1和任务2并发执行,任务3在任务1和任务2都执行完毕后再执行。
    如果要回到主线程执行任务3,将最后的通知部分,换为:

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            for (int i = 0; i<5; i++) {
                NSLog(@"执行任务3-%@", [NSThread currentThread]);
            }
    });
    

    即可满足题目要求。

    如果你想看一些苹果没有开源的代码是怎么执行的,你可以通过打断点,然后一步一步进,查看混编,看是如何执行的,这个难度*****五颗星。

    为此,互联网人群通过一些列手段,制作了类似开源代码,也就是GUNstep。

    GUNstep

    GUNstep是GUN计划的项目之一,它将Cocoa的OC库重新开源实现了一遍
    GUNstep下载地址

    在这里插入图片描述

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

  • 相关阅读:
    线程+IO流
    jiava trim()
    statement 的延伸 ----》PreparedStatement
    java中math的用法
    java中获取所有文件--(递归调用)
    编写一个JAVA类,用于计算两个日期之间的周数。
    java中数组排序.知识点
    javascript 常用功能总结
    jquery
    创建 HTML内容
  • 原文地址:https://www.cnblogs.com/r360/p/15927426.html
Copyright © 2020-2023  润新知