浅谈线程和进程

线程和进程是程序员老生常谈的问题了,任何阶段的程序员都不敢轻视他。

事实上大部分程序员并没有系统化的学习过,也有很多人并没有机会好好运用它。所以,如果拉一个工作多年的程序员讨论,对方未必能说出个所以然。

本文是 Linux 下 C++ 多线程编程开发的系列文章之首,在介绍具体编程实现而言,先讲讲它的基础概念,并给予通俗化的解释,并在文章最后给出一个开放的思考题。

什么是线程?

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。1

上面的定义来自于百度百科,定义的很准确,但同时也很抽象。

线程可一看作是轻量级的进程,它依赖于进程。

所以,搞清楚线程前,我们先来看看进程是什么。

什么是进程?

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。2

上面的定义同样来自于百度百科,定义非常准确,但同时也非常抽象。

所以,为了很好的搞明白线程和进程,我尝试用一些通俗的比喻来解释。

我们把整个计算机比喻成一个公司,公司由一些基本的单元组成:

  1. 软硬件
  2. 设备资源
  3. 部门
  4. 员工
  5. 制度流程

公司要正常运作,依赖于制度流程。

制度流程可以等同于什么呢?

操作系统

正因为规章制度流程的存在,公司的软硬件、设备资源可以协调给每一个部门。

如果公司十分庞大,组织复杂,那么部门就是最基础的单元。

也就是说

进程可以看做是部门

部门依法使用公司规定的软硬件资源,进程在操作系统的只能也是类似。

上面的定义中有讲到,现代操作系统中,进程是一个容器

在这里插入图片描述

每个进程拥有自己的独立空间,相互间不干扰。

这个好理解,现实中,每个部门有自己的软硬件和员工,一般情况下相互不干涉。

那么线程呢?

进程和线程有什么区别?

线程依赖于进程,而不能独立存在。

线程是最基本的执行单元,对比现实生活就是是部门中的员工。

仔细体会一下。

部门提供资源,员工干活。

进程提供资源,线程干活。

但如果公司有重大决定,它一般不会直接针对个人,而是下发到部门,控制部门这一层就好了。

如果操作系统根据事情轻重缓急,它也会直接和进程交涉,进程受它的调度。

一个进程一般有一个或者多个线程。

同一个进程中的线程可以共享进程的数据。

但同时,其实线程也有自己的内存模型。

线程开发中可以利用 TLS(Thread-Local Storage,线程局部存储)来实现线程独有的内存数据不被同一个进程其它线程干扰,每一个线程维护一份共享变量的拷贝,所以基于拷贝上的操作不会影响其它线程。

那么,线程和进程有什么区别呢?

进程是资源分配的基本单位,线程是调度的基本单位。

这是一句名言,很好地概括了两者的区别。

用一句话来概括就是:

进程对应操作系统,线程对应 CPU。

我们常说的任务调度,其实通常讲 CPU 通过时间片轮转,调度线程。

也并不是说进程不能调度,是说线程更轻量化。

如果要了解更深入的话,可以带入到下一个问题。

什么时候用线程和进程?

我们用线程,提起的很多的就是多线程。

多线程的目的就是并行开发。

大家讲多线程的时候,总喜欢讲车站买票的例子。

窗口:@-@-@-@-@-@-@-@-@-@

现在有 10 个人买票。

如果这个工作效率很低,那么我们可以这样:

窗口:@-@-@-@-

窗口:@-@-

窗口:@-@-@-@-

增加类似的窗口,提供并行服务。

并行的目的是为了加速。

多线程开发也是基于上面 2 个原因:

  1. 并行
  2. 加速

并行就是,我要一边听歌,一边写文章。

加速就是,下载一个文件单线程太慢了,多个线程一起下载就能加速。

那是不是线程越多越好呢?

答案是否定的。

多线程能干的事情多进程也能干。

但两者都不是越多越好。

这涉及到上下文切换的问题。

上下文切换

上下文的英文单词是 Context。搞 Android 和 Java Web 开发的应该再熟悉不过了,它代表的是任务的同一语境。

上下文切换就是任务切换,可能是进程的切换也可能是线程的切换。

上下文的切换是一个很深的话题,我们大可不必在此过多讨论,我们可以简单看待:

上下文的切换就是任务状态的保存与恢复。

当 CPU 挂起一个任务时,它需要将 CPU 寄存器里面的信息保存到任务的堆栈当中,然后从下一个任务的堆栈中读取对应的 CPU 寄存器信息并恢复,那么下一个任务就可以执行了。

在这里插入图片描述

图片来源于网络3

但上下文切换的开销很昂贵,它有大量的工作需要处理,比如寄存器和内存页表的存储和恢复、内核数据结构的更新等等。

所以,线程并不是越多越好,因为线程越多,上下文切换越频繁,极端情况下效率不升反降。

并且,进程的上下文切换比线程开销的要大。

注意:在同一个进程中,线程的切换不会引起进程的切换,不同进程中,线程的切换会引起进程的切换。

所以,回到问题什么时候用进程什么时候用线程这个问题上来,一般认为:

  1. 相互独立的任务用进程

    因为内存独立的原因,资源相互不干扰,比如播放音乐用一个进程,图像绘制用一个进程。音乐出故障了,图像还能正常绘制。如果用线程的话,单线程发生故障可能导致整个进程一起被干掉。

2.相关性高的并行需求用线程

​ 因为线程上下文切换开销小,所以同一个任务或者同一个业务逻辑的代码可以尝试用线程开发。

线程开发的基础概念

线程和进程的基本差别上面内容介绍的差不多了,下面提一些线程开发中常出现的基础概念。

用户态线程和内核态线程

用户态线程指的是用户层面自己创建的线程,自己管理生命周期,包括创建、切换、销毁。比如,最近流行的协程就属于此,一般通过线程库实现。

内核态线程指的是由操作系统的内核管理生命周期的线程,如用 pthread 创建的线程。

POSIX 标准

POSIX(Portable Operating System Interface of UNIX)是可移植操作系统接口,定义了操作系统应该为应用程序提供的接口标准。

什么意思呢?

操作系统有很多种,有些编程语言可以跨平台开发,有些则不能。

比如,Android 成立之初就选用 Java,原因就是 Java 跨平台,所以那些做 Java 开发的工程师可以很快投入到新的领域。但是 Java 的跨平台是建立在虚拟机上,虚拟机屏蔽了操作系统的不同,提供了统一的 API,但一定程度上牺牲了性能。我们很多年感觉的 Android 卡,和这有很大关系。

而 POSIX 意在获得操作系统源码级的 API 支持。

并且,POSIX 标准中定义了进程和线程相关标准。

Linux 是支持 POSIX 标准的,我们用 pthread 创建线程就属于此。

线程库

在 Linux 开发中,线程的实现可以通过系统调用。

但系统调用太麻烦了,一般用线程的封装好的线程库,目前常用的有:

  • posix pthread

  • C++11 Thread

  • Boost 线程库

思考题

文章最后,抛出一个问题。

我们知道,多线程开发是为了并行。

并行是通过 CPU 时间片调度而来。

线程 A: ++ + +  +
线程 B:   - - -- -
CPU  : ++-+-+--+-

也有同学知道,所谓并行其实只是看起来并行。

那问题是:

可以串行的任务,非要人为并行吗?

引用

  1. https://baike.baidu.com/item/%E7%BA%BF%E7%A8%8B/103101?fr=aladdin ↩︎

  2. https://baike.baidu.com/item/%E8%BF%9B%E7%A8%8B/382503?fr=aladdin ↩︎

  3. https://baike.baidu.com/pic/%E4%B8%8A%E4%B8%8B%E6%96%87%E5%88%87%E6%8D%A2/4842616/0/37d12f2eb9389b504fc24c9c577df2dde71191efc0ee?fr=lemma&ct=single#aid=0&pic=37d12f2eb9389b504fc24c9c577df2dde71191efc0ee ↩︎

frank909 CSDN认证博客专家 CV(computer vision)
爱阅读的程序员,专注于技术思考和分享。关注架构设计、Android 开发、AI、数学、自动驾驶领域,个人公号:Frankcall
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付 19.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值