一、多任务
之所以需要使用多进程或者是多线程,归根结底就是为了进行多任务的处理。那么,多核CPU实现多任务原理和单核CPU的多任务原理是什么呢。
-
单核CPU的多任务原理
操作系统轮流让各个任务交替执行。比如:QQ执行2毫秒,微信执行2毫秒....任务不断反复切换,但又因为CPU调度执行速度太快了,导致我们感觉就是所有的任务都在同时执行。 -
多核CPU实现多任务原理
真正的多任务只能在多核CPU上实现,但是由于任务数量远远多于CPU的核心数量。所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。
一般情况下任务数往往大于你的CPU核心数,所以会出现一颗CPU执行多个任务的情况。而这,又引出了并发和并行的概念
-
并发和并行
因为CPU的核心与任务数的不同。所以又出现了并发和并行的概念。并发:指的是任务数多余cpu核心数(你总有一颗CPU核心不止运行一个任务),所以只能通过操作系统的各种任务调度算法,实现用多个任务“一起”执行(因为切换任务的速度相当快,看上去一起执行而已。可以借助单核CPU的多任务原理来进行理解)
并行:指的是任务数小于等于cpu核心数,即任务真的是一起执行的。效率最高。
-
实现多任务的几种方式:
1、多进程模式
2、多线程模式
3、协程模式
4、多进程+多线程模式
这里我们主要了解一下多进程和多线程的理论,后续将用代码介绍Python3的多进程和多线程
二、进程和线程
-
进程
对于操作系统而言,一个任务就是一个进程。进程是系统中程序执行和资源分配的基本单位,每个资源都有自己的数据段、代码段和堆栈段 -
线程
线程是操作系统能够进行运算调度的最小单位(程序执行流的最小单元)。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 -
进程和线程的区别
(1)线程共享内存空间;进程的内存是独立的
(2)同一个进程的线程之间可以直接交流;两个进程想通信,必须通过一个中间代理来实现
(3)创建新线程很简单; 创建新进程需要对其父进程进行一次克隆
(4)一个线程可以控制和操作同一进程里的其他线程;但是进程只能操作子进程
(5)改变主线程(如优先权),可能会影响其它线程;改变父进程,不影响子进程
-
多进程和多线程的优缺点
多进程
缺点:密集CPU任务,需要充分使用多核CPU资源(服务器,大量的并行计算)的时候,用多进程。缺陷:多个进程之间通信成本高,切换开销大。
多线程
优点:密集I/O任务(网络I/O,磁盘I/O,数据库I/O)使用多线程合适。缺陷:同一个时间切片只能运行一个线程,不能做到高并行,但是可以做到高并发。
-
多线程在Python里是鸡肋的说法
首先来介绍一下GIL:GIL的全称是Global Interpreter Lock(全局解释器锁),来源是python设计之初的考虑,为了数据安全所做的决定。一个线程想要执行,必须先拿到GIL。在Python多线程下,每个线程的执行方式:
- 获取GIL
- 执行代码直到sleep或者是python虚拟机将其挂起。
- 释放GIL
在Python2.x里,GIL的释放逻辑是当前线程遇见IO操作或者ticks计数达到100(ticks可以看作是Python自身的一个计数器,专门做用于GIL,每次释放后归零,这个计数可以通过 sys.setcheckinterval 来调整),进行释放。
而每次释放GIL锁,线程进行锁竞争、切换线程,会消耗资源。并且由于GIL锁存在,python里一个进程永远只能同时执行一个线程(拿到GIL的线程才能执行),这就是为什么在多核CPU上,python的多线程效率并不高。
在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。所以,每个CPU在同一时间只能执行一个线程。而每个进程有各自独立的GIL,所以多进程的执行效率优于多线程(仅仅针对多核CPU而言)。
-
存在即合理,Python的多线程还是要进行分类讨论
CPU密集型代码(各种循环处理、计数等等),在这种情况下,由于计算工作多,ticks计数很快就会达到阈值,然后触发GIL的释放与再竞争(多个线程来回切换当然是需要消耗资源的),所以python下的多线程对CPU密集型代码并不友好。IO密集型代码(文件处理、网络爬虫等),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率)。所以python的多线程对IO密集型代码比较友好。
多核多线程比单核多线程更差,原因是单核下的多线程,每次释放GIL,唤醒的那个线程都能获取到GIL锁,所以能够无缝执行,但多核下,CPU0释放GIL后,其他CPU上的线程都会进行竞争,但GIL可能会马上又被CPU0拿到,导致其他几个CPU上被唤醒后的线程会醒着等待到切换时间后又进入待调度状态,这样会造成线程颠簸(thrashing),导致效率更低。
网友评论