进程、线程、上下文
进程
进程是操作系统对一个正在运行的程序的一种抽象。在一个系统上可以运行多个进程,而每个进程都好像在独占地使用硬件。
例如,运行一个c程序,操作系统会提供一种假象,就好像系统上只有这个程序运行。程序看上去是独占地使用处理器、主存、和I/o设备。处理器看上去就像在不间断地一条接一条的执行程序中的指令,即该程序的代码和数据是系统内存中唯一的对象。
上下文
操作系统保持跟踪进程运行所需的所有状态信息。这种状态,也就是上下文,包括许多信息,比如PC和寄存器文件的当前值,以及主存的内容。在任何一个时刻,单处理器系统都只能执行一个进程的代码。当操作系统决定要把控制权从当前进程转移到某个新进程时,就会进行上下文切换,即保持当前进程的上下文,恢复新进程的上下文,然后将控制权传递到新进程,新进程就会从它上次停止的地方开始。
线程
在现代操作系统中,一个进程实际上可以由多个称为线程的执行单元组成,每个线程都运行在进程的上下文中,并共享同样的代码和全局数据。
基本概念
多线程编程会遇到许多问题:
我们利用GCD进行多线程编程来简化问题
下来我们说回 GCD 。什么是GCD?以下摘自苹果的官方说明:
Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的 Dispatch Queue 中,GCD 就能生成必要的线程并执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。
GCD大大简化了偏于复杂的多线程编程的源代码。
任务、队列
学习 GCD 之前,先来了解 GCD 中两个核心概念:『任务』 和 『队列』。
任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:『同步执行』 和 『异步执行』。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
- 同步执行(sync):
同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
只能在当前线程中执行任务,不具备开启新线程的能力。 - 异步执行(async):
异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
可以在新的线程中执行任务,具备开启新线程的能力。
注意:异步执行(async)虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关(下面会讲)。
队列即数据结构中的队列,有先进先出的特点。
- 串行队列(Serial Dispatch Queue):
每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
- 并发队列(Concurrent Dispatch Queue):
可以让多个任务并发执行。(可以开启多个线程,并且同时执行任务)
注意:并发队列 的并发功能只有在异步(dispatch_async)方法下才有效。
这个队列中的任务也是按照FIFO(先来后到的顺序)开始执行,注意是开始,但是它们的执行结束时间是不确定的,取决于每个任务的耗时。
理解并发和并行的概念
一个逻辑流的执行时间上与另一个流重叠,称为并发流,这俩个流被称为并发的运行。更准确的说,流X与Y相互并发,当前仅当X在Y开始之后和Y结束之前开始。
这张图中,B和A并发,A和C并发,B和C不并发。
传统意义上的并发就是通过使一台计算机在它正在执行的进程间快速切换来实现的,就好像一个杂耍艺人保持多个球在空中飞舞一样。
可以从上图发现,在某一个时刻,只有一个任务在执行,当前任务还没结束,就可以执行新的任务,这就是并发。
并行才是多个任务同一时刻都执行。
GCD使用
1.创建一个队列(串行队列或并发队列);
2.将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)。
队列的创建
可以使用 dispatch_queue_create 方法来创建队列。该方法需要传入两个参数:
- 第一个参数表示队列的唯一标识符,用于 DEBUG,可为空。队列的名称推荐使用应用程序 ID 这种逆序全程域名。
- 第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列。
// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
队列获取
对于串行队列,GCD 提供了的一种特殊的串行队列:『主队列(Main Dispatch Queue)』。
所有放在主队列中的任务,都会放到主线程中执行。
可使用 dispatch_get_main_queue() 方法获得主队列。
// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();
对于并发队列,GCD 默认提供了 『全局并发队列(Global Dispatch Queue)』。
可以使用 dispatch_get_global_queue 方法来获取全局并发队列。需要传入两个参数。第一个参数表示队列优先级,一般用 DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用 0 即可。
// 全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_set_specific和dispatch_get_specific设置标识符和获取标识
dispatch_queue_set_specific(queue1, queueKey1, &queueKey1,NULL);
就是向queue1对了中设置一个queueKey1标识。
dispatch_get_specific就是在当前队列中取出标识,注意iOS中线程和队列的关系,所有的动作都是在队列中执行的!
任务创建
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
});
任务和队列不同组合方式
注意:从上边可看出: 『主线程』 中调用 『主队列』+『同步执行』 会导致死锁问题。
这是因为 主队列中追加的同步任务 和 主线程本身的任务 两者之间相互等待,阻塞了 『主队列』,最终造成了主队列所在的线程(主线程)死锁问题。
而如果我们在 『其他线程』 调用 『主队列』+『同步执行』,则不会阻塞 『主队