美文网首页
2021-02-04 多线程

2021-02-04 多线程

作者: 胖渣大橘子 | 来源:发表于2021-02-04 08:02 被阅读0次

    为啥要并发

    • 让多个任务同时执行
    • 提高app运行的性能,保证App实时响应
      1因为UI界面运行在主线程之上,它是一个串行线程。如果将所有代码都放在主线程上运行,那么主线程将承担网络请求,数据处理,图像渲染等操作,无论是GPU还是计算机内存,都会性能耗尽,从而影响用户体验。

    线程和进程的区别

    同步和异步,串行和并行。多任务和阻塞

    异步:异步和同步是相对的,同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。线程就是实现异步的一个方式。异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情。
    异步和多线程并不是一个同等关系,异步是最终目的,多线程只是我们实现异步的一种手段。异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其它的事情。实现异步可以采用多线程技术或则交给另外的进程来处理。
    Sync Async Serial Concurrent

    • Serial/Concurrent声明队列的属性是串行的还是并行的。串行队列(Serial Queue)指在同一时间内,队列中只能执行一个任务,当前任务执行完后才能执行下一个任务。
      在串行队列中只有一个线程。
      并行队列(Concurrent Queue)允许多个任务在同一个时间同时进行,在并行队列中有多个线程。串行队列的任务一定是按开始的顺序结束。而并行队列的任务并不一定会按照开始的顺序结束。

    任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:『同步执行』 和 『异步执行』。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。

    同步执行(sync):
    同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
    只能在当前线程中执行任务,不具备开启新线程的能力。
    异步执行(async):
    异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
    可以在新的线程中执行任务,具备开启新线程的能力。
    串行队列(Serial Dispatch Queue):
    每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
    并发队列(Concurrent Dispatch Queue):
    可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
    注意:并发队列 的并发功能只有在异步(dispatch_async)方法下才有效。

    • Sync/Async声明任务是同步还是异步执行的。
      同步(Sync)会把当前的任务加到队列中,等到任务执行完成,线程才会返回继续运行。
      也就是说,同步会阻塞线程。
      异步(Async)也会把当前的任务添加到队列中,但它会立刻返回,无须等任务执行完成,也就是说异步不会阻塞线程。
      无论是串行队列还是并行队列,都可以执行异步或者同步操作。
      注意,在串行队列中执行同步操作容易造成死锁。
      在并行队列中则不用担心这个问题。异步操作无论是在串行队列中执行还是在并行队列中执行,都可能出现竞态问题;
      同时,异步操作经常与逃逸闭包一起出现在API的设计中

    串行队列的代码实战

    // 串行同步
    serialQueue.sync {
    print(1);
    }
    print(2);
    serialQueue.sync {
    print(3);
    }
    print(4);
    1
    2
    3
    4
    串行队列中同步操作时打印1,2,3,4

    // 串行异步
    serialQueue.async {
    print(1);
    }
    print(2);
    serialQueue.asyn {
    print(3);
    }
    print(4);
    serialQueue mainqueue
    1 2不用等1
    3等1 3等2 4等2
    1 不用等我执行完 12 21
    2 主队列,同步 1243 1234 2134 2143 2413
    3 不用等我执行完
    4 主队列,同步

    同步

    同步执行:比如这里的dispatch_sync,这个函数会把一个block加入到指定的队列中,而且会一直等到执行完blcok,这个函数才返回。因此在block执行完之前,调用dispatch_sync方法的线程是阻塞的。

    异步

    异步执行:一般使用dispatch_async,这个函数也会把一个block加入到指定的队列中,但是和同步执行不同的是,这个函数把block加入队列后不等block的执行就立刻返回了。

    // 串行异步嵌套同步 1
    外边异步块 2
    5 3阻塞线程等3完成
    print(1) 4
    serialQueue.async {
    print(2)
    serialQueue.sync {
    print(3)
    }
    print(4)
    }
    print(5)
    dispatch_sync() 同步执行,完成了它预定的任务后才返回,阻塞当前线程
    dispatch_async() 异步执行,会立即返回,预定的任务会完成但不会等它完成,不阻塞当前线程
    mainqueue serialQueue不同等
    1 2 不同等
    5 3 3进行的条件是整个block块结束,3等块结束
    4 4等3结束
    1,2

    串行队列+同步任务:不会开启新的线程,任务逐步完成。
    不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。

    串行队列+异步任务:开启新的线程,任务逐步完成。
    并发队列+同步任务:不会开启新的线程,任务逐步完成。

    • 任务按顺序执行的。按顺序执行的原因:虽然 并发队列 可以开启多个线程,并且同时执行多个任务。但是因为本身不能创建新线程,只有当前线程这一个线程(同步任务 不具备开启新线程的能力),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务 需要等待队列的任务执行结束)。所以任务只能一个接一个按顺序执行,不能同时被执行。

    并发队列+异步任务:可以开启多个线程,任务交替(同时)执行。
    |
    串 主
    1 2
    3 4

    1一定在3之前被打印出来,因为1在3之前派发,串行队列一次只能执行一个任务。所以一旦派发完就执行任务。
    2一定在4之前被打印出来
    2一定在3之前被打印出来

    多线程的实现方式?区别?每种方式的适用场景

    1Pthread

    pthread 是一套通用的多线程的 API,可以在Unix / Linux / Windows 等系统跨平台使用,使用 C 语言编写,需要程序员自己管理线程的生命周期,使用难度较大,我们在 iOS 开发中几乎不使用 pthread,但是还是来可以了解一下的。
    场景 获取堆栈信息
    https://blog.csdn.net/wxs0124/article/details/104961809
    /_np 是指 not POSIX ,这里的 POSIX 是指操作系统的一个标准,特别是与 Unix 兼容的操作系统。np 表示与标准不兼容
    pthread_t pt = pthread_from_mach_thread_np(list[i]);
    获取当前调用栈的信息

    #pragma mark - Interface
    + (NSString *)callStackWithType:(SMCallStackType)type {
    // 所有线程
    if (type == SMCallStackTypeAll) {
    thread_act_array_t threads; //int 组成的数组比如 thread[1] = 5635
    

    2NSThread

    最大限度的掌控每个线程的生命周期。但是,也需要开发者手动管理所有的进程活动,
    比如创建、同步、暂停、取消,其中手动加锁操作的挑战性很大。
    NSThread总体使用场景很小,基本是在开发底层的开源软件或是测试时使用。
    还有就是获取一些线程信息,如当前线程,主线程,线程名称。

    NSThread *nsthread = [NSThread currentThread]; //当前执行的指令
    

    3GCD

    4Operation

    // A、B、C、D、E、F六个任务
    // A、B、C 并发执行
    // D--->A,B
    // E--->B,C
    // F--->D、E
    // 1A、B、C没有依赖关系
    

    NSOperation实现

     NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        queue.maxConcurrentOperationCount = 6;
        NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"A任务");
            }
        }];
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"B任务");
            }
        }];
        NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"C任务");
            }
        }];
        NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"D任务");
            }
        }];
        NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"E任务");
            }
        }];
        NSBlockOperation *op6 = [NSBlockOperation blockOperationWithBlock:^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"F任务");
            }
        }];
        [op4 addDependency:op1];
        [op4 addDependency:op2];
        [op5 addDependency:op2];
        [op5 addDependency:op3];
        [op6 addDependency:op4];
        [op6 addDependency:op5];
        [queue addOperation:op1];
        [queue addOperation:op2];
        [queue addOperation:op3];
        [queue addOperation:op4];
        [queue addOperation:op5];
        [queue addOperation:op6];
    

    dispatch实现

    / D--->A,B
        dispatch_group_t g1 = dispatch_group_create();
        // E--->B,C
        dispatch_group_t g2 = dispatch_group_create();
        // F--->D、E
        dispatch_group_t g3 = dispatch_group_create();
        dispatch_group_enter(g1);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"A任务");
            }
            dispatch_group_leave(g1);
        });
        dispatch_group_enter(g1);
        dispatch_group_enter(g2);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"B任务");
            }
            dispatch_group_leave(g1);
            dispatch_group_leave(g2);
        });
        dispatch_group_enter(g2);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"C任务");
            }
            dispatch_group_leave(g2);
        });
        dispatch_group_enter(g3);
        dispatch_group_notify(g1, dispatch_get_global_queue(0, 0), ^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"D任务");
            }
            dispatch_group_leave(g3);
        });
        dispatch_group_enter(g3);
        dispatch_group_notify(g2, dispatch_get_global_queue(0, 0), ^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"E任务");
            }
            dispatch_group_leave(g3);
        });
        dispatch_group_notify(g3, dispatch_get_global_queue(0, 0), ^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"F任务");
            }
        });
    

    dispatch_group_t底层
    底层是一个结构体
    类似一个链表的形式存储当前的任务
    struct dispatch_group_s {
    DISPATCH_SEMAPHORE_HEADER(group, dg)
    int_volatile dg_waiters;
    struct dispatch_continuation_s * volatitile dg_notify_head;
    struct dispatch_continuation_s * volatitile dg_notify_tail;
    }
    enter函数原子自增,dg_value做加 两次enter之后就是等于2
    leve函数原子自减,dg_value做减,每次减1
    当dg_value为0的时候,就是执行_dispatch_group_wake(dg, false)
    (dispatch源码苹果官网可下载)
    enter,leave类似于信号量的标记
    标记当前有多少个任务要执行
    notify就是等group的任务为0,就是dg_value的任务为0的时候

    BlockOperation是基于状态机制实现的
    队列的任务是isReady状态时,才会被队列调度
    operationqueue是一个队列
    adddependency其实就是在底层建立一个依赖关系
    比如D任务依赖于A,B其实实现就是,等A任务B任务的状态都编程isFinish后,D任务的状态变成isReady,然后isReady的任务就可以被调度了
    所以D需要监听A,B的状态值变化,就是一个KVO
    A依赖于B任务,A会存在一个dpendencies数组,里面存放着B
    B有downdenpendices数组,存放着B
    isReady的判断是根据当前计数器判断
    然后最底层还是根据GCD来调度
    operation源码在gihub上

    多线程和runloop的关系

    1. 一一对应的关系
      -通过k-value的形式存储在一个全局的字典中,来对应当前的线程和
      runloop
      -runloop源码
      TSL是线程局部存储空间
      当前线程访问autoreleasepool
      return os_mac
      pthread_getspecific
      return os_linux
      pthread_getspecific
      return os_win32
      pthread_getspecific
      私有函数,当前线程的局部存储空间只能被当前线程所访问到,其他线程访问不到。
      runloop没有就会创建,放在全局字典里的同时也会在TSL里做一个缓存。
      当前线程的保活
      子线程执行完任务后就会销毁掉,
      线程常驻,就是
      _port = [NSMacPort port];
      _thread = [[LXJThread alloc] initWithTarget:self selector:@selector(threadTest) object:nil]];
      [_thread start];
      @autoreleasepool {
      // port mach_msg 消息队列的收发,决定当前runloop是休眠还是执行任务
      // port是和mac_os内核有关的东西
      // 端口收发消息队列
      // 先获取当前子线程的runloop
      NSRunloop *runloop = [NSRunloop currentRunloop];
      // 向当前的子线程runloop add port
      // port用来收发消息
      [self registerobserver]
      [[runloop addPort:port forMode:nsrunloopdefaultnmode]];
      [runloop run];
      线程常驻后如何销毁子线程
      子线程能存活就是依赖与port事件源
      是否可以移除事件源头,然后解除
      // runloop不会退出 [[runloop remove:port forMode:nsrunloopdefaultnmode]];
      如果控制器销毁,runloop会不会退出
      // runloop不会退出,还会有野指针问题
      // 因为移除当前线程的事件源时,并不能保证系统不往线程里添加一些额外的事件源。所以没有办法通过移除自己事件源来移除常驻线程。
      // 当前直接去退出当前线程
      // [NSThread exit];线程无法响应,当前线程销毁,野指针。程序崩溃,线程销毁了,runloop不销毁还会造成内存泄漏。
      // 看源码CFRunloop怎么stop的
      // 判断CFRunloop finished是通过
      // CFRunloop isFinished
      // 如果当前runloop的mode是空的或者runloopmode是空,isfinish就是true。没有runloop mode 就finish了但是对现在没有用,因为当前runloop run 肯定有mode
      // 如果mode name == null或者是commonmode(commomode只是一个标记)
      // runloop run起来本质就是一个while循环
      // 循环结束依赖于。。。
      // _cfrunllop stop就相当于有了退出标记
      // CFRunloopStop([CFRunloop current])当前的runloop还是退出不了
      }
      // 仿照源码
      runloop runmode:nsdefaultmode beforedate: [snadate distantfuture]
    1. 有序列表第二项
    
    # iOS 并发编程中的三大问题
    * 竞态条件
      两个或两个以上线程对共享的数据进行读写操作时,最终的数据结果不确定的情况。
    * 优先倒置
      低优先级的任务会因为各种原因先于高优先级的任务执行。
    * 死锁问题
      两个或者两个以上的线程,它们之间互相等待彼此停止执行,以获得某种资源,但是没有一方会提前退出的情况。在iOS开发中,**有一个经典的例子就是两个Operation互相依赖。
    在对同一个串行队列中进行异步、同步嵌套时:
    #如何Debug并发编程问题

    相关文章

      网友评论

          本文标题:2021-02-04 多线程

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