iOS底层-- 进程、线程、队列

2020-07-21  本文已影响0人  Engandend

手动目录:

  • 基本概念
    进程
    线程
    任务
    队列
  • 相互之间的关联、区分、特点
    进程与线程的关系
    队列的特点
    线程与队列
    主线程和主队列
    多线程的意义
  • 队列与执行(同步异步)的化学反应
    主队列与同步/异步
    队列(串行/并发)与同步/异步
  • 用生活中的场景去理解队列与线程的关系与组合
  • 线程的生命周期
  • 疑问点
  • 练习题

基本概念





队列的区分:
队列从性质上来区分:并发队列、串行队列。
但是还有通常我们将队列分为4种:主队列、全局队列、并发队列、串行队列。

相互之间的关联、区分、特点


先了解这些。你才能更好的理解上面的概念

1、队列和线程没有必然联系,队列只是一系列任务的容器,而线程才是执行任务的载体。
所以,不要认为串行队列中的所有任务都在同一个线程中

2、线程之间 没有主次,主线程只不过是在线程 number = 1的线程上启动了RunLoop ,让这个number = 1的线程常驻,RunLoop启动后会让这个number = 1的线程有响应机制。

3、主队列只不过是一种约定俗成的概念,默认将系统帮我们建立的这个取名为main的队列叫做主队列

4、同步(sync) 不会启动新的线程

5、异步(async)有开启新线程的能力,但是不一定会开启新线程(在主线程上就不会开新线程)

6、多线程:在同一时刻,一个CPU只能处理1条线程,但CPU可以在多条线程之间快速的切换,只要切换的足够快,就造成了多线程一同执行的假象。

队列与执行(同步异步)的化学反应

  • 主队列
    1、主队列与异步: 主队列上的异步任务会被加在所有任务的最后, 所有任务执行完毕才会执行添加的任务。
    2、主队列与同步:死锁,形成相互等待。

  • 串行、并行、全局 与队列组合
    1、串行队列与异步:开启了新线程 (所有新任务在同一个新线程中执行),执行顺序固定
    2、串行队列与同步:在原来的线程执行,执行顺序确定
    3、并发队列与异步:不在同一个线程(每个任务都新开了线程),相同耗时的任务执行顺序不确定
    4、并发队列与同步:在原来的线程执行,执行顺序确定

主队列


其他队列

用生活中的场景去理解队列与线程的关系与组合

线程、队列之间没有任何关系,但是如果去理解他们之间的组合工作,在没有理解他们如何工作之前,用一个生活中的场景去带入,对理解很有帮助。以下是我的理解,如有错误,请指正:

演唱会门票售票处:


购票场景

在图中的规则下,我们来分析不同情况下,为什么是这种执行结果

线程的生命周期

线程的生命周期包括 : 创建 - 就绪 - 运行 - 阻塞 - 死亡

截屏2020-07-22 15.24.28副本.png

就绪和运行:
先要了解 单核CPU一次只执行一条线程上的任务,
当线程池中有多个可调度的线程,CPU会来回切换线程。所以对于一个线程来讲,线程执行完成之前,状态可能会在就绪和运行之间来回切换。(调度的线程是运行状态,没有调度的线程是就绪状态

CPU调度过程

疑问点

练习题

- (void)test7 {
    
    NSLog(@"1 thread %@", [NSThread currentThread]);
    dispatch_queue_t serial_queue = dispatch_queue_create("def.test.serial", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(serial_queue, ^{             // 任务1
         NSLog(@"2 thread %@", [NSThread currentThread]);
        dispatch_sync(serial_queue, ^{          // 任务2
            NSLog(@"3 thread %@", [NSThread currentThread]);
        });
        NSLog(@"4");
    });
    
    dispatch_sync(serial_queue, ^{              // 任务3
        NSLog(@"5 thread %@", [NSThread currentThread]);
    });
    NSLog(@"6");
}
// 打印结果
10:32:51.776185+0800  1 thread <NSThread: 0x600001664480>{number = 1, name = main}
10:32:51.776447+0800  5 thread <NSThread: 0x600001664480>{number = 1, name = main}
10:32:51.776486+0800  2 thread <NSThread: 0x6000016363c0>{number = 3, name = (null)}
10:32:51.776575+0800  6
10:32:51.776617+0800  3 thread <NSThread: 0x6000016363c0>{number = 3, name = (null)}
10:32:51.776698+0800  4

分析:打印结果是 1、5、2、6、3、4 这只是理想情况。因为其任务复杂度是相似的。

分析:

  • 打印 1 :这个不用分析
  • 打印5、2:
    dispatch_async(任务1) 不阻塞线程,dispatch_sync(任务3)、dispatch_async(任务1) 同时执行。所以先打印2、5,理想情况下,2、5任务复杂度相同、但是dispatch_async 要开子线程,更耗时,所以 5在2之前
  • 打印 2、3、4:
    dispatch_sync(任务2) 会阻塞线程,所以2、3、4 顺序打印
  • 打印6:
    6 一定在5之后。
  • 正确的应该是:
    1
    2、3、4 (这3个的前后顺序是明确的)
    5、6 (这2个顺序是明确的)
    5、6 和2、3、4 他们之间谁先谁后 是不确定的
- (void)test7 {
    NSLog(@"1 thread %@", [NSThread currentThread]);
    dispatch_queue_t serial_queue = dispatch_queue_create("def.test.serial", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(serial_queue, ^{             // 任务1
         NSLog(@"2 thread %@", [NSThread currentThread]);
        dispatch_sync(serial_queue, ^{          // 任务2
            NSLog(@"3 thread %@", [NSThread currentThread]);
        });
        NSLog(@"4");
    });
    
    dispatch_async(serial_queue, ^{              // 任务3
        NSLog(@"5 thread %@", [NSThread currentThread]);
    });
    NSLog(@"6");
}

// 打印结果
11:01:25.524600+0800  1 thread <NSThread: 0x600001eccd40>{number = 1, name = main}
11:01:25.524765+0800  2 thread <NSThread: 0x600001eccd40>{number = 1, name = main}
11:01:25.524931+0800  3 thread <NSThread: 0x600001eccd40>{number = 1, name = main}
11:01:25.525015+0800  4
11:01:25.525115+0800  6
11:01:25.525152+0800  5 thread <NSThread: 0x600001e9a900>{number = 6, name = (null)}

分析
这个就很简单,虽然串行,但是同步执行,从上往下执行到任务3。前面的1、2、3、4顺序很明确
5、6互不影响,但是 dispatch_async 开子线程,要耗时,所以6在5之前。

- (void)test7 {
    NSLog(@"1 thread %@", [NSThread currentThread]);
    dispatch_queue_t serial_queue = dispatch_queue_create("def.test.serial", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(serial_queue, ^{             // 任务1
        sleep(1);
         NSLog(@"2 thread %@", [NSThread currentThread]);
    });
    
    dispatch_async(serial_queue, ^{              // 任务2
        NSLog(@"3 thread %@", [NSThread currentThread]);
    });
    
    NSLog(@"4");
}

// 打印结果
14:35:35.964771+0800  1 thread <NSThread: 0x600003a34580>{number = 1, name = main}
14:35:35.964929+0800  4
14:35:36.969227+0800  2 thread <NSThread: 0x600003a60640>{number = 3, name = (null)}
14:35:36.969609+0800  3 thread <NSThread: 0x600003a60640>{number = 3, name = (null)}

关键点在2、3 :
因为串行,所以2、3 在同一个子线程按顺序执行

知识点: 串行队列 异步任务在同一个子线程上 顺序执行

- (void)test7 {
    NSLog(@"1 thread %@", [NSThread currentThread]);
    dispatch_queue_t serial_queue = dispatch_queue_create("def.test.serial", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(serial_queue, ^{             // 任务1
        sleep(1);
         NSLog(@"2 thread %@", [NSThread currentThread]);
    });
    
    dispatch_sync(serial_queue, ^{              // 任务2
        NSLog(@"3 thread %@", [NSThread currentThread]);
    });
    
    NSLog(@"4");
}

// 打印结果
15:38:56.774591+0800  1 thread <NSThread: 0x6000019800c0>{number = 1, name = main}
15:38:57.775468+0800  2 thread <NSThread: 0x6000019c7900>{number = 4, name = (null)}
15:38:57.775894+0800  3 thread <NSThread: 0x6000019800c0>{number = 1, name = main}
15:38:57.776144+0800  4

分析
打印结果是固定的,没有其他可能

2、3 在由同一个队列调度,串行队列,不管是不是在同一个线程都是顺序执行,所以先执行2、在执行3,3是同步,会阻塞线程,所以4在3之后

- (void)test7 {
    
    NSLog(@"1 thread %@", [NSThread currentThread]);
    dispatch_queue_t serial_queue = dispatch_queue_create("def.test.serial", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(serial_queue, ^{             // 任务1
         NSLog(@"2 thread %@", [NSThread currentThread]);
        dispatch_sync(serial_queue, ^{          // 任务2
            NSLog(@"3 thread %@", [NSThread currentThread]);
        });
        NSLog(@"4");
    });
    
    dispatch_async(serial_queue, ^{              // 任务3
        NSLog(@"5 thread %@", [NSThread currentThread]);
    });
    
    NSLog(@"6");
}

// 打印结果
15:56:11.330565+0800  1 thread <NSThread: 0x600002c3cf40>{number = 1, name = main}
15:56:11.330697+0800  6
15:56:11.330744+0800  2 thread <NSThread: 0x600002c68800>{number = 5, name = (null)}
❌Thread 7: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

这个题如果能分析出来,那么 串行队列就基本完全搞懂了
任务1、任务2、任务3 都在同一个串行队列上,所以他们要按顺序执行,任务1执行完之后 才开始执行2,但是执行2的前提是任务1执行完毕,最后的结果就是:任务2等任务1执行完,任务1等任务2执行完之后任务1才算执行完,就形成了相互等待,最后就是死锁。

上一篇 下一篇

猜你喜欢

热点阅读