1. 线程的开始
1.1 线程的含义
学习了进程相关的知识内容,对进程有了一个比较全面的认识和理解,从今开始呢,我们要学习一个新的概念,叫做线程!
那什么是线程呢?
与进程类似,线程是允许应用程序并发执行多个任务的一种机制,线程是参与系统调度的最小单位。它被包含在进程之中,是进程中的实际运行单位。一个线程指的是进程中一个单一顺序的控制流(或者说是执行路线、执行流),一个进程中可以创建多个线程,多个线程实现
并发运行,每个线程执行不同的任务。譬如某应用程序设计了两个需要并发运行的任务 task1 和 task2,可将两个不同的任务分别放置在两个线程中,虽然线程的概念比较简单,但是其所涉及到的内容比较多,所以我会和进程一样,分出很多个小的知识点,慢慢总结!
1.2 线程是如何创建起来的?
当一个程序启动时,就有一个进程被操作系统(OS)创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程(Main Thread),因为它是程序一开始时就运行的线程。应用程序都是以 main()做为入口开始运行的,所以 main()函数就是主线程的入口函数,main()函数所执行的任务就是主线程需要执行的任务。
所以由此可知,任何一个进程都包含一个主线程,只有主线程的进程称为单线程进程,譬如前面章节内容中所编写的所有应用程序都是单线程程序,它们只有主线程;既然有单线程进程,那自然就存在多线程进程,所谓多线程指的是除了主线程以外,还包含其它的线程,其它线程通常由主线程来创建(调用pthread_create 创建一个新的线程),那么创建的新线程就是主线程的子线程。
主线程的重要性体现在两方面:
- 其它新的线程(也就是子线程)是由主线程创建的;
- 主线程通常会在最后结束运行,执行各种清理工作,譬如回收各个子线程。
1.3 线程的特点
同一进程中的多个线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack,我们称为线程栈),自己的寄存器环境(register context)、自己的线程本地存储(thread-local storage)。(这恰恰是我们后续学习需要重点关注和学习的)
在多线程应用程序中,通常一个进程中包括了多个线程,每个线程都可以参与系统调度、被 CPU 执行,线程具有以下一些特点:
- 线程不单独存在、而是包含在进程中;
- 线程是参与系统调度的基本单位;
- 可并发执行。同一进程的多个线程之间可并发执行,在宏观上实现同时运行的效果;
- 共享进程资源。同一进程中的各个线程,可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量等等。
1.4 进程与线程的比较
进程创建多个子进程可以实现并发处理多任务(本质上便是多个单线程进程),多线程同样也可以实现(一个多线程进程)并发处理多任务的需求,那我们究竟选择哪种处理方式呢?首先我们就需要来分析下多进程和多线程两种编程模型的优势和劣势。
多进程编程的劣势:
- 进程间切换开销大。多个进程同时运行(指宏观上同时运行,无特别说明,均指宏观上),微观上依然是轮流切换运行,进程间切换开销远大于同一进程的多个线程间切换的开销,通常对于一些中小型应用程序来说不划算。
- 进程间通信较为麻烦。每个进程都在各自的地址空间中、相互独立、隔离,处在于不同的地址空间中,因此相互通信较为麻烦,需要管道,信号等。
多线程能够弥补上面的问题: - 同一进程的多个线程间切换开销比较小。
- 同一进程的多个线程间通信容易。它们共享了进程的地址空间,所以它们都是在同一个地址空间中,通信容易。
- 线程创建的速度远大于进程创建的速度。
- 多线程在多核处理器上更有优势!
我怎么感觉有点像在黑进程的意思,不过,总而言之,多线程无论是处理各个程序的信息,都是一个相当很好的选择,开销小,灵活多变。
2. 并发和并行
2.1 概念
在前面的内容中,曾多次提到了并发这个概念,与此相类似的概念还有并行、串行,这里和大家聊一聊这些概念含义的区别吧。
对于串行比较容易理解,它指的是一种顺序执行,譬如先完成 task1,接着做 task2、直到完成 task2,然后做 task3、直到完成 task3……依次按照顺序完成每一件事情,必须要完成上一件事才能去做下一件事,只有一个执行单元,这就是串行运行。
并行与串行则截然不同,并行指的是可以并排/并列执行多个任务,这样的系统,它通常有多个执行单元,所以可以实现并行运行,譬如并行运行 task1、task2、task3。
并行运行并不一定要同时开始运行、同时结束运行,只需满足在某一个时间段上存在多个任务被多个执行单元同时在运行着,譬如:
相比于串行和并行,并发强调的是一种时分复用,与串行的区别在于,它不必等待上一个任务完成之后在做下一个任务,可以打断当前执行的任务切换执行下一个任何,这就是时分复用。在同一个执行单元上,将时间分解成不同的片段(时间片),每个任务执行一段时间,时间一到则切换执行下一个任务,依次这样轮训(交叉/交替执行),这就是并发运行。如下图所示:
举几个简单的小案例,方便理解一下吧:
- 你吃饭吃到一半,电话来了,你一直到吃完了以后才去接电话,这就说明你不支持并发也不支持并行,仅仅只是串行。
- 你吃饭吃到一半,电话来了,你停下吃饭去接了电话,电话接完后继续吃饭,这说明你支持并发。
- 你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
需要注意的是,并行运行情况下的多个执行单元,每一个执行单元同样也可以以并发方式运行。从通用角度上介绍完这三个概念之后,类到计算机系统中,首先我们需要知道两个前提条件:
- 多核处理器和单核处理器:对于单核处理器来说,只有一个执行单元,同时只能执行一条指令;而对于多核处理起来说,有多个执行单元,可以并行执行多条指令,譬如8核处理器,那么可以并行执行8条不同的指令。
- 计算机操作系统中,通常同时运行着几十上百个不同的线程,在单核或多核处理系统中都是如此!
tips:像普通的Linux单片机,像全志的,正点原子的,基本都是单核的soc,所以都是我们常说的单核处理器,只能采用并发运行系统中的线程,而肯定不可能是串行,而事实上确实如此。内核实现了调度算法,用于控制系统中所有线程的调度,简单点来说,系统中所有参与调度的线程会加入到系统的调度队列中,它们由内核控制,每一个线程执行一段时间后,由系统调度切换执行调度队列中下一个线程,依次进行。
2.2 什么是同时运行
计算机处理器运行速度是非常快的,在单个处理核心虽然以并发方式运行着系统中的线程(微观上交替/交叉方式运行不同的线程),但在宏观上所表现出来的效果是同时运行着系统中的所有线程,因为处理器的运算速度太快了,交替轮训一次所花费的时间在宏观上几乎是可以忽略不计的,所以表示出来的效果就是同时运行着所有线程。
这就好比现实生活中所看到的一些事情,它所给带来的视角效果,譬如一辆车在高速上行驶,有时你会感觉到车的轮毂没有转动,一种视角暂留现象,因为车轮转动速度太快了,人眼是看不清的,会感觉车轮好像是静止的,事实上,车轮肯定是在转动着。
本章主要帮助大家充分理解线程与进程的一个不同,帮助大家构建进程与线程的包容关系,建立起一个框架,在后面的学习中,我们通过编程来感受线程的神奇!
本文参考正点原子的嵌入式LinuxC应用编程。