手动目录:
- 基本概念
进程
线程
任务
队列- 相互之间的关联、区分、特点
进程与线程的关系
队列的特点
线程与队列
主线程和主队列
多线程的意义- 队列与执行(同步异步)的化学反应
主队列与同步/异步
队列(串行/并发)与同步/异步- 用生活中的场景去理解队列与线程的关系与组合
- 线程的生命周期
- 疑问点
- 练习题
基本概念
- 进程
进程是指在系统中正在运行的一个应用程序
每个进程之间是独立的,每个进程运行在其专用的且受保护的内存
- 线程
线程是进程的基本单位,一个进程的所有任务都在线程中执行
进程要想执行任务,必须得有一个或多个线程
程序启动,默认开启一条线程,这条线程称之为主线程或者UI线程
-
进程、线程的其他解释
在IOS 进程、主线程、主队列 中有对进程和线程的 解释:
进程
是具有一定独立功能的程序,相对操作系统来说,操作系统分配资源给进程,所以进程作为系统资源分配和调度的基本单位,进程是可以独 立运行的一段程序。线程
线程是进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位。
相对于进程,线程拥有系统资源比较少,而且线程的生命周期是进程这个程序来控制的。在运行时,只是暂用一些计数器、寄存器和栈 。同时一个进程至少要有一个主线程。所以真正执行任务的是线程。进程是拥有资源的最小单位。线程是执行的最小单位。
- 任务
任务是 CPU 进行执行的基本单位
在 iOS 中可以简单地理解为一个函数/方法实现。
在GCD中可以认为任务等价于代码块( block)。
- 队列
队列是任务的集合,强调的是一种静态表示。
也有另一种说法:
队列是按先进先出(FIFO)管理对象的数据结构。iOS并行开发:从NSOperation和调度队列开始 这篇文章或许能帮助更好的理解
队列的区分:
队列从性质上来区分:并发队列、串行队列。
但是还有通常我们将队列分为4种:主队列、全局队列、并发队列、串行队列。
主队列:
dispatch_get_main_queue()
,一个特殊的串行队列
主队列的任务都在主线程来执行。
专门负责调度主线程度的任务,没有办法开辟新的线程。所以,在主队列下的任务不管是异步任务还是同步任务都不会开辟线程,任务只会在主线程顺序执行。
如果在主线程上已经有任务正在执行,主队列会等到主线程空闲后再调度任务
串行队列
dispatch_queue_create("queue.name", DISPATCH_QUEUE_SERIAL);
不要认为串行队列中的所有任务都在同一个线程中
【串行队列在同一个线程执行,并打队列会在不同的线程执行】
所有任务按顺序依次执行,结束顺序固定, 符合先进先出的基本原则。
队列后面的任务等待前面的任务执行完毕后才出队列并发队列
dispatch_queue_create("queue.name", DISPATCH_QUEUE_CONCURRENT);
所有任务可以同时执行,结束顺序不固定
只要有可用线程,则队列头部任务将持续出队列全局队列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
全局队列的本质就是并发队列,只是在后面加入了,“服务质量”,和“调度优先级” 两个参数
相互之间的关联、区分、特点
1、
进程与线程的关系
1)、线程是进程的基本单位。
2)、一个进程中 有一个或多个线程。
3)、线程只是一一个进程中的不同执行路径。
4)、进程和线程都是由操作系统所产生的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性
5)、一个进程崩溃后,在保护模式下,其他的进程不会受影响,但是一个线程崩溃会导致整个进程崩溃, 所以多进程的程序要比多线程的程序健壮。
6)、同一进程中的线程,资源、地址空间,是可以共享的(相互访问),但是进程不行
7)、线程的创建、销毁、切换等耗费资源较小,效率更高。
8)、但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。2、队列的特点:
先进先出,排在前面的任务最先执行;
串行队列:任务按照顺序被调度,前一个任务不执行完毕,队列不会调度;
并行队列:只要有空闲的线程,队列就会调度当前任务,交给线程去执行,不需要考虑前面是否有任务正在执行,只要有线程可以利用,队列就会调度任务;
主队列:专门用来在主线程调度任务的队列,所以主队列的任务都在主线程来执行,主队列会随着程 序的启动一起创建;
全局队列:是系统为了方便程序员开发提供的,其工作表现与并发队列一致;
3、线程与队列
队列和线程没有必然联系,队列只是一系列任务的容器,而线程才是执行任务的载体。4、主线程和主队列
这两个术语我们可以经常听到,不知道有没有人会把这两个概念等同化。主队列和主线程是有关联,但是它们是两个不同的概念。简单地说,主队列是主线程上的一个串行队列,是系统自动为我们创建的。换言之,主线程是可以执行除主队列之外其他队列的任务。
比如这段代码,主线程上添加一个串行队列,它们都在主线程中执行,
- (void)queueActivity {
dispatch_queue_t queut = dispatch_queue_create("asldkj", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queut, ^{
NSLog(@"1");
});
}
- 5、多线程的意义
优点:
1、能适当提高程序的执行效率
2、能适当提高资源利用率(CPU、内存)
3、线程上的任务执行完成后,线程会自动销毁
缺点:
1、开启线程需要招用一定的内存空间(默认情况下,每一个线程都占512KB)
2、如果开启大量线程,会占用大量的内存空间,降低程序的性能
3、线程越多,CPU在调用线程上开销就越大
4、程序设计更加复杂,比如线程间的通信、多线程的数据共享(比如2个人买同一张票)。
先了解这些。你才能更好的理解上面的概念
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、并发队列与同步:在原来的线程执行,执行顺序确定
主队列
- 1、主队列中放入异步任务
不是马上执行,而是等到主队列中的其它所有除我们使用代码添加到主队列的任务都执行完毕之后,才会执行我们使用代码添加的任务。
队列上的异步任务会被加在所有任务的最后, 所有任务执行完毕才会执行添加的任务。
- (void)test1 { NSLog(@"------------------1"); dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_async(queue, ^{ NSLog(@"主队列异步 %@",[NSThread currentThread]); }); sleep(1); // 模拟耗时任务 NSLog(@"------------------2"); } // 打印结果 2020-07-17 14:42:39.251507+0800 ------------------1 2020-07-17 14:42:41.981571+0800 ------------------2 2020-07-17 14:42:41.992595+0800 主队列异步 <NSThread: 0x600000df4580>{number = 1, name = main}
-
2、主队列中放入同步任务
死锁- (void)test2 { // 任务1 NSLog(@"------------------1"); dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_sync(queue, ^{ // 任务2 NSLog(@"主队列同步 %@",[NSThread currentThread]); }); NSLog(@"------------------2"); } // 结果 crash Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
任务2 会在所有的任务之后才开始执行,也就是说 任务1 执行完,才开始执行任务2,但是 任务2 是同步,会阻塞当前线程,(任务1、任务2在同一条线程上)导致任务1 不能执行完。这样就形成了相互等待 ,所以就死锁了
其他队列
-
1、串行队列与异步
NSLog(@"thread %@", [NSThread currentThread]); dispatch_queue_t serial_queue = dispatch_queue_create("def.test.serial", DISPATCH_QUEUE_SERIAL); for (NSUInteger i = 0; i < 5; i++) { dispatch_async(serial_queue, ^{ if (i == 2) { sleep(1); // 模拟耗时任务 } NSLog(@"liyc: %@ thread %@", @(i), [NSThread currentThread]); }); } // 打印结果 18:22:03.376220+0800 thread <NSThread: 0x60000262c240>{number = 1, name = main} 18:22:03.376517+0800 liyc: 0 thread <NSThread: 0x600002625800>{number = 7, name = (null)} 18:22:03.376767+0800 liyc: 1 thread <NSThread: 0x600002625800>{number = 7, name = (null)} 18:22:04.380290+0800 liyc: 2 thread <NSThread: 0x600002625800>{number = 7, name = (null)} // ⚠️与上一个任务的时间间隔 18:22:04.380521+0800 liyc: 3 thread <NSThread: 0x600002625800>{number = 7, name = (null)} 18:22:04.380721+0800 liyc: 4 thread <NSThread: 0x600002625800>{number = 7, name = (null)}
结果:
开启了新线程 (所有任务在同一个新线程中执行),执行顺序固定
各线程任务 互不影响
-
2、串行队列与同步
- (void)test5 { NSLog(@"thread %@", [NSThread currentThread]); dispatch_queue_t serial_queue = dispatch_queue_create("def.test.serial", DISPATCH_QUEUE_SERIAL); for (NSUInteger i = 0; i < 5; i++) { dispatch_sync(serial_queue, ^{ if (i == 2) { sleep(1); // 模拟耗时任务 } NSLog(@"liyc: %@ thread %@", @(i), [NSThread currentThread]); }); } } // 打印结果 18:31:33.910443+0800 thread <NSThread: 0x6000033e4380>{number = 1, name = main} 18:31:33.910562+0800 liyc: 0 thread <NSThread: 0x6000033e4380>{number = 1, name = main} 18:31:33.910659+0800 liyc: 1 thread <NSThread: 0x6000033e4380>{number = 1, name = main} 18:31:34.911927+0800 liyc: 2 thread <NSThread: 0x6000033e4380>{number = 1, name = main} // ⚠️与上一个任务的时间差 18:31:34.912374+0800 liyc: 3 thread <NSThread: 0x6000033e4380>{number = 1, name = main} 18:31:34.912624+0800 liyc: 4 thread <NSThread: 0x6000033e4380>{number = 1, name = main}
结果:
在原来的线程执行,执行顺序确定
-
3、并发队列与异步
NSLog(@"thread %@", [NSThread currentThread]); dispatch_queue_t serial_queue = dispatch_queue_create("def.test.serial", DISPATCH_QUEUE_CONCURRENT); // dispatch_queue_t serial_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); for (NSUInteger i = 0; i < 5; i++) { dispatch_async(serial_queue, ^{ if (i == 2) { sleep(1); // 模拟耗时任务 } NSLog(@"liyc: %@ thread %@", @(i), [NSThread currentThread]); }); } //打印结果 18:41:29.073834+0800 thread <NSThread: 0x600001e98dc0>{number = 1, name = main} 18:41:29.074103+0800 liyc: 0 thread <NSThread: 0x600001ea7b80>{number = 6, name = (null)} 18:41:29.074109+0800 liyc: 4 thread <NSThread: 0x600001edf200>{number = 7, name = (null)} 18:41:29.074154+0800 liyc: 3 thread <NSThread: 0x600001ed4680>{number = 3, name = (null)} 18:41:29.074179+0800 liyc: 1 thread <NSThread: 0x600001edc100>{number = 5, name = (null)} 18:41:30.076097+0800 liyc: 2 thread <NSThread: 0x600001ec4180>{number = 8, name = (null)}
结果:
不在同一个线程(每个任务都新开了线程),相同耗时的任务执行顺序不确定
- 4、并发队列与同步
结果:- (void)test5 { NSLog(@"thread %@", [NSThread currentThread]); dispatch_queue_t serial_queue = dispatch_queue_create("def.test.serial", DISPATCH_QUEUE_CONCURRENT); // dispatch_queue_t serial_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); for (NSUInteger i = 0; i < 5; i++) { dispatch_sync(serial_queue, ^{ if (i == 2) { sleep(1); // 模拟耗时任务 } NSLog(@"liyc: %@ thread %@", @(i), [NSThread currentThread]); }); } } // 打印结果 18:44:03.885406+0800 thread <NSThread: 0x600001e30dc0>{number = 1, name = main} 18:44:03.885524+0800 liyc: 0 thread <NSThread: 0x600001e30dc0>{number = 1, name = main} 18:44:03.885606+0800 liyc: 1 thread <NSThread: 0x600001e30dc0>{number = 1, name = main} 18:44:04.886723+0800 liyc: 2 thread <NSThread: 0x600001e30dc0>{number = 1, name = main} // ⚠️与上一个任务的时间差 18:44:04.887100+0800 liyc: 3 thread <NSThread: 0x600001e30dc0>{number = 1, name = main} 18:44:04.887347+0800 liyc: 4 thread <NSThread: 0x600001e30dc0>{number = 1, name = main}
在原来的线程执行,执行顺序确定
用生活中的场景去理解队列与线程的关系与组合
线程、队列之间没有任何关系,但是如果去理解他们之间的组合工作,在没有理解他们如何工作之前,用一个生活中的场景去带入,对理解很有帮助。以下是我的理解,如有错误,请指正:
演唱会门票售票处:
在图中的规则下,我们来分析不同情况下,为什么是这种执行结果
1、串行队列与异步
异步,异步任务要在新窗口买(同一个串行队列的异步 在同一个子线程
)
新开了一个窗口, + 原来的窗口,总共2个窗口,2个窗口相互不影响2、串行队列与同步:
同步不能开新的窗口。(窗口2、3、4 只能能暂停营业)
串行决定了任务一个一个来,所以结果就是 全部都在窗口1上 按顺序一个一个买票-
3、并发队列与异步
异步,所有人要在新窗口去买(需要开几个窗口?
有多条队伍(多个纵队),所以要开多个窗口)
纵队,可以一拥而上,多个任务会到不同的窗口去买票,任务复杂度高:每个人买的票越复杂(多张票等),就最后才执行完毕。
任务复杂度相同:在这个场景下,可以理解为 同样是买一张票,但是可能有人钱准备好了,有人没准备好,所以时间也不同, 对于CPU来说,是有时间片来决定的(CPU在不同的线程中来回切换),所以具有随机性。
4、并发队列与同步
同步不能开新的窗口。(窗口2、3、4 只能能暂停营业)
即使是并发(排队的人可以一拥而上),但是窗口只有一个,大厅只能容纳一个,所以剩下的人也要在外面等,要一个一个来,所以结果是 在原来的窗口,按顺序一个一个的执行
线程的生命周期
线程的生命周期包括 : 创建 - 就绪 - 运行 - 阻塞 - 死亡
- 创建:
t = [[NSThread alloc]initWithTarget:self selector:@selector(threadMethod:) object:nil];
- 就绪:
[t start];
- 运行:
运行是有CPU区调度的,开发者不能控制这个状态 - 阻塞
当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥锁)。 - 死亡
正常死亡:线程执行完毕。
非正常死亡:当满足某个条件后,在线程内部中止执行/在主线程中止线程对象
就绪和运行:
先要了解 单核CPU一次只执行一条线程上的任务,
当线程池中有多个可调度的线程,CPU会来回切换线程。所以对于一个线程来讲,线程执行完成之前,状态可能会在就绪和运行之间来回切换。(调度的线程是运行状态
,没有调度的线程是就绪状态
)
疑问点
- 1、 队列的先进先出(FIFO)
队列的先进先出 和任务哪个先执行完毕是2回事,队列与线程之间组合输出的时候 看到输出结果的顺序 不要混淆。
队列的先进先出:相当于 排队买票,谁先来,谁先排在前面,买票也就先去买
任务执行完毕的顺序:你先去买票,但是不一定是你先买完,任务输出表示的就是 谁买完票了。
练习题
- 1、 并发队列下 同步异步的嵌套
- (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 他们之间谁先谁后 是不确定的
- 2、 并发队列下,同步异步组合
- (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之前。
- 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_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 在同一个子线程按顺序执行
知识点:
串行队列 异步任务在同一个子线程上 顺序执行
- 4、
- (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之后
- 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
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才算执行完,就形成了相互等待,最后就是死锁。