python多线程协程并行并发概念解惑

线程与进程

进程

我们都知道计算机的核心是CPU,它承担了所有的计算任务;而操作系统是计算机的管理者,它负责任务的调度、资源的分配和管理,统领整个计算机硬件;应用程序侧是具有某种功能的程序,程序是运行于操作系统之上的。 (为了缓解头脑胀痛, 斜体字大体过一遍即可)
进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。进程一般由程序、数据集合和进程控制块三部分组成。程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块(Program Control Block,简称PCB),包含进程的描述信息和控制信息,是进程存在的唯一标志。

线程

在早期的操作系统中并没有线程的概念,在当时进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。进程间的通信与同步完全袭来操作系统内核转发与支持。
后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程,线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID、当前指令指针(PC)、寄存器和堆栈组成。而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。

进程与线程的区别

  1. 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位 (这句话一定要理解);
  2. 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
  3. 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),某进程内的线程在其它进程不可见;
  4. 调度和切换:线程上下文切换比进程上下文切换要快得多。

线程与进程关系的示意图:


某个进程中单线程与多线程的关系


从别处看到线程是进程的一个实体,是程序执行的最小单位(线程也被称为轻量级进程).
按照我的理解:QQ音乐正在运行,QQ音乐就是个进程,而这个进程中有多个线程在跑着(各有各的任务);我们平常编写的所有的Python程序,都是执行单任务的进程,也就是只有一个线程。
如果我们要同时执行多个任务怎么办:一种是启动多个进程,每个进程虽然只有一个线程,但多个进程可以一块执行多个任务;还有一种方法是启动一个进程,在一个进程内启动多个线程,这样,多个线程也可以一块执行多个任务(之后详细讲)。

线程与cpu核心数的关系

现在都是多核的处理器,那么在多核处理器的情况下,线程是怎样执行呢?这就需要了解内核线程。

多核(心)处理器是指在一个处理器上集成多个运算核心从而提高计算能力,也就是有多个真正并行计算的处理核心,每一个处理核心对应一个内核线程。内核线程(Kernel Thread, KLT)就是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。一般一个处理核心对应一个内核线程,比如单核处理器对应一个内核线程,双核处理器对应两个内核线程,四核处理器对应四个内核线程。
现在的电脑一般是双核四线程、四核八线程,是采用超线程技术将一个物理处理核心模拟成两个逻辑处理核心,对应两个内核线程,所以在操作系统中看到的CPU数量是实际物理CPU数量的两倍,如你的电脑是双核四线程,打开“任务管理器性能”可以看到4个CPU的监视器,四核八线程可以看到8个CPU的监视器。

上面的应该可以理解,但是有个注意点:程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口——轻量级进程. 那为什么要说出上面那段文字呢? 我心中的困惑是:一般我们买的CPU包装盒上写的 四核八线程的"线程" 到底是什么线程?跟系统的线程是一个小编么?上面说得很清楚了,内核线程与轻量级进程(即线程)的区别,即线程.

由于每个轻量级进程都需要一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。
下图中:K代表内核线程,LWP代表轻量级进程,U代表 用户线程

问题一:什么是用户线程?

用户线程是完全建立在用户空间的线程库(比如python的threading库),用户线程的创建、调度、同步和销毁全又库函数在用户空间完成,不需要内核的帮助。因此这种线程是极其低消耗和高效的。

问题二:请简单解释下图中流程?

还是上面的例子,当你运行一个python脚本时,等于创建可一个进程,光有进程没卵用,必须有程序执行的最小单位嘛,所以同时系统会创建一个LWP,程序就靠它运行,因为是第一个线程,也称它为主线程。然后,脚本跑着跑着,需要创建新的线程运行其他任务时,主线程程序安排一个”创建线程“的口号(比如调用一个线程库)来调用产生U。

问题二:系统中有时会有那CPU核心数的数量影响着什么嘛?

(不是很确定),多核就是系统同时可以运行多个线程,比如双核可以同时执行两个线程。单核只能一次执行一个线程。
如果答案真如上面那样:那为什么能有百来个多线程同时存在?那么就可以引申到下面的话题了。

并发和并行

并行(parallelism)

这个概念很好理解。所谓并行,就是同时执行的意思,无需过度解读(在日本跟台湾,翻译成平行,这就更好理解了)。判断程序是否处于并行的状态,就看同一时刻是否有超过一个“工作单位”在运行就好了。所以,单线程永远无法达到并行状态。
要达到并行状态,最简单的就是利用多线程和多进程。但是 Python 的多线程由于存在著名的 GIL,无法让两个线程真正“同时运行”,所以实际上是无法到达并行状态的。

那么对于上面的问题三,我们是否可以认为,一个四核八线程的CPU,最多允许八个线程同时执行?某个时间点就只有八个,然后下一时间,其他八个再执行=.=(不展开。有点晕)

在并行性开发时,不但可以考虑多进程并行执行,而且还可以开发出进程内多线程并行的程序,如下图。


并发(concurrency)

要理解“并发”这个概念,必须得清楚,并发指的是程序的“结构”。当我们说这个程序是并发的,实际上,这句话应当表述成“这个程序采用了支持并发的设计”。好,既然并发指的是人为设计的结构,那么怎样的程序结构才叫做支持并发的设计
正确的并发设计的标准是:使多个操作可以在重叠的时间段内进行(two tasks can start, run, and complete in overlapping time periods)。

这句话的重点有两个。我们先看“(操作)在重叠的时间段内进行”这个概念。它是否就是我们前面说到的并行呢?是,也不是。并行,当然是在重叠的时间段内执行,但是另外一种执行模式,也属于在重叠时间段内进行。这就是协程(或者简单点多线程也可以,然后把task1,task2改成线程1,线程2)。
使用协程时,程序的执行看起来往往是这个样子:

task1, task2 是两段不同的代码,比如两个函数,其中黑色块代表某段代码正在执行。注意,这里从始至终,在任何一个时间点上都只有一段代码在执行,但是,由于 task1 和 task2 在重叠的时间段内执行,所以这是一个支持并发的设计。与并行不同,单核单线程能支持并发(特指协程)。

那么,如何实现支持并发的设计?两个字:拆分。
之所以并发设计往往需要把流程拆开,是因为如果不拆分也就不可能在同一时间段进行多个任务了。这种拆分可以是平行的拆分,比如抽象成同类的任务,也可以是不平行的,比如分为多个步骤。

并发和并行的区别
并行指物理上同时执行,并发指能够让多个任务在逻辑上交织执行的程序设计

同步和异步理解

这里我就简单举个例子就好了:web网页,点一下发送邮件按钮,然后发送(发送过程5秒),发送成功之网页显示跳转成功。
同步:点了发送之后,慢慢等五秒,然后显示成功
异步:点了发送之后,直接显示成功,后台默默的还在发送,直到发送完毕。
关于阻塞啥的我这里就不提了,已经有些晕了。毕竟在python中有些集成的库已经将这些问题考虑进去了,不需要重复造轮子,有兴趣的可以自己去查一查。

python的相关模块

  • threading 多线程库
  • concurrent.futures中的ThreadpoolExecutor 线程池
  • concurrent.futures中的ProcesspoolExecutor 进程池
  • subprocess 子进程
  • yield关键字 协程
  • async和await(python3.5新加入) 协程库

其他我想要说的几个点

  1. 在多线程这方面我代码中用的比较少.有时偶尔用到,我会直接选用ThreadpoolExecutor类。这个接口抽象层级很高,多线程直接使用,无需关心任何实现细节,啥线程死锁等问题都不需要担心。

  2. 多线程在单cpu中其实也是顺序执行的,不过系统可以帮你切换那个执行而已,其实并没有快(反而慢)
    ;多个cpu的话就可以在两个cpu中同时执行了。

  3. 接上面那一条,虽然python有GIL锁,启用多线程的时候只能用一个cpu核心,但python线程仍然适合I/O密集型应用:标准库中每个使用 C 语言编写的 I/O 函数都会释放 GIL,因此,当某个线程在等待 I/O 时, Python 调度程序会切换到另一个线程。 (意思就是说I/O操作的延迟大于过程中程序运行时间,我完全可以在延迟的时候,切换到其他线程,做其他操作,如下图情况C)


版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


linux常用进程通信方式包括管道(pipe)、有名管道(FIFO)、信号(signal)、消息队列、共享内存、信号量、套接字(socket)。管道用于具有亲缘关系的进程间通信,有名管道的每个管道具有名字,使没有亲缘关系的进程间也可以通信。信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除
Linux性能观测工具按类别可分为系统级别和进程级别,系统级别对整个系统的性能做统计,而进程级别则具体到进程,为每个进程维护统计信息。

按实现原理分,可分为基于计数器和跟踪以及剖析。含义如下:

计数器:内核维护的统计数据,通常为无符号整型,用于对发生的事件计数,比如,网络包接收计数器,磁
本文详细介绍了curl命令基础和高级用法,包括跳过https的证书验证,详细追踪整个交互过程,可用于调用网络后端接口,诊断http和https网络服务故障。
本文包含作者工作中常用到的一些命令,用于诊断网络、磁盘占满、fd泄漏等问题。命令包括ping、fping、tcpdump、lsof、netstat、/proc/$pid/fd、du、grep、traceroute、dig。
linux的平均负载表示运行态和就绪态及不可中断状态(正在io)的进程数目,用uptime查看到负载很高,既有可能是CPU利用率高,也可能是大量在等待io的进程导致,用mpstat查看每个CPU的使用情况,查看CPU的使用率或者CPU花在等待io的时间,接着用pidstat定位具体的进程
CPU上下文频繁切换会导致系统性能下降,切换分为进程切换、线程切换及中断切换,进程切换的开销较大,除了需要保存寄存器和程序计数器中的值还需保存全局变量、栈等到内存中,以便下次运行恢复,而同一进程中的线程切换开销会小很多,只需更新寄存器和线程独有的栈,共享资源如打开的文件、全局变量等无需切换,当硬件中
1.top命令 作用:该命令可以按CPU使用.内存使用和执行时间对任务进行排序,常用来监控系统中占用CPU或内存较高的程序及CPU和内存的负载。 默认视图: 当想看系统负载时,可观察汇总的%CPU中的us用户进程和sy系统进程是否占用CPU很高,相加接近100%就说明占用很高了,有些程序可能得不到及
文章浏览阅读1.8k次,点赞63次,收藏54次。Linux下的目录权限!!!粘滞位!!!超详解!!!
文章浏览阅读1.6k次,点赞44次,收藏38次。关于Qt的安装、Windows、Linux、MacBook_mack book 安装qt
本文介绍了使用shell脚本编写一个 Hello
文章浏览阅读1.5k次,点赞37次,收藏43次。【Linux】初识Linux——了解操作系统的发展历史以及初次体验Linux编程环境
文章浏览阅读3k次,点赞34次,收藏156次。Linux超详细笔记,个人学习时很认真的记录的,觉得好的麻烦点个赞。
文章浏览阅读6.8k次,点赞109次,收藏114次。【Linux】 OpenSSH_9.3p1 升级到 OpenSSH_9.5p1(亲测无问题,建议收藏)_openssh_9.5p1
文章浏览阅读3.5k次,点赞93次,收藏78次。初识Linux中的线程,理解线程的各种概念,理解进程地址空间中的页表转换,介绍pthread线程库并理解线程库!
文章浏览阅读863次。出现此问题为Linux文件权限问题,解决方案为回到引擎目录执行命令。输入用户密码后运行./UnrealEditor。_increasing per-process limit of core file size to infinity.
文章浏览阅读2.9k次。使用文本编辑器:打开CSV文件,并使用文本编辑器(如Notepad++、Sublime Text、Visual Studio Code等)来查看文件的字符编码格式。通常在编辑器的底部状态栏或设置中可以找到当前编码的显示。请注意,上述方法并非绝对准确,特别是当文件没有明确的编码标识时。因此,如果你发现CSV文件在不同的工具或方法中显示不同的编码格式,可能需要进行进一步的分析和判断,或者尝试使用不同的编码转换方法。该命令将输出文件的MIME类型和编码信息。使用命令行工具:在命令行中,你可以使用。_shell读取csv文件逐行处理
本文介绍了如何在Linux系统中升级gcc版本,以便更好地支持C++11及以上版本的新特性。通过升级gcc,可以提升编译器的功能和性能,获得更好的开发体验。详细的步骤和方法请参考原文链接。
文章浏览阅读4.4k次,点赞6次,收藏19次。Mosquitto是一个开源的MQTT消息代理服务器。MQTT是一个轻量级的、基于发布/订阅模式的消息传输协议。 mosquitto的安装使用比较简单,可以方便的来进行一些测试。_linux mosquitto
文章浏览阅读7.2k次,点赞2次,收藏12次。Linux中,用于根目录下有一个.ssh目录,保存了ssh相关的key和一些记录文件。_~/.ssh/
文章浏览阅读4.5k次,点赞5次,收藏18次。首先需要安装 snmp ,使用下面的命令进行安装安装完毕之后,使用下面的命令查看是否安装成功当命令行显示如图即为安装成功。_snmp工具