Python学习第12天---进程与线程

进程是cpu资源分配的最小单位,线程是cpu调度的最小单位。

我们现代的操作系统,都是支持“多任务”的操作系统。很多朋友对“多任务”可能比较陌生。

我们举个例子:小明是一个大学生。有一天他去网吧打游戏,玩了没多久突然想起来有一个论文还没写今天老师就要收了。可是游戏才玩了没多久。算了游戏先挂机,赶论文先……打开word写了一会之后,觉得写论文好枯燥啊!放首歌一边写一边听吧。又写了一会之后,小明发现自己有个专业术语不太懂,打开Chrome查询一下吧。

上面这个例子就是一个多任务的例子。游戏、word、音乐播放器、Chrome就是计算机运行的任务。当然,计算机在后台还运行了很多任务,只是桌面上没有显示。

对于操作系统来说,一个任务就是一个进程(process)。比如打开一个浏览器就是启动一个浏览器进程。

有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。

01 进程和线程

如果我们将计算器的核心CPU比喻为一座工厂,那么进程就像工厂里的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。

看到这大家可能会有一些疑问了,其他进程处于非运行状态?可是我用浏览器访问网页的时候,音乐播放器明明也在运行啊。

实际上是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

线程就好比车间里的工人。一个进程可以包括多个线程。车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。

线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。

Python既支持多进程又支持多线程。因此使用Python实现并发编程主要有3种方式:

1. 多进程

2. 多线程

3. 多进程+多线程。

02 Python中的多进程

在UNIX/LINUX操作系统中,可以使用fork()函数来创建。fork函数比其他普通函数有一点特殊之处,就是普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。

子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。

fork()函数被封装在os模块中。接下来,我们举例说明使用多进程和不使用多进程的区别:

from random import randint
from time import time, sleep
def download_task(filename):
	print('开始下载%s...' % filename)
	time_to_download = randint(5, 10)
	sleep(time_to_download)
	print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))
def main():
	start = time()
	download_task('MySQL从删库到跑路.pdf')
	download_task('万万没想到.mp4')
	end = time()
	print('总共耗费了%.2f秒.' % (end - start))

if __name__ == '__main__':
	main()

执行结果:

开始下载MySQL从删库到跑路.pdf...

MySQL从删库到跑路.pdf下载完成! 耗费了9秒

开始下载万万没想到.mp4...

万万没想到.mp4下载完成! 耗费了9秒

总共耗费了18.00秒.

从上面的例子可以看出,如果程序中的代码只能按顺序一点点的往下执行,那么即使执行两个毫不相关的下载任务,也需要先等待一个文件下载完成后才能开始下一个下载任务,很显然这并不合理也没有效率。接下来我们使用多进程的方式将两个下载任务放到不同的进程中,代码如下所示:

from multiprocessing import Process
from os import getpid
from random import randint
from time import time, sleep

def download_task(filename):
	print('启动下载进程,进程号[%d].' % getpid())
	print('开始下载%s...' % filename)
	time_to_download = randint(5, 10)
	sleep(time_to_download)
	print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))
	
def main():
	start = time()
	p1 = Process(target=download_task, args=('MySQL从删库到跑路.pdf', ))
	p1.start()
	p2 = Process(target=download_task, args=('万万没想到.mp4', ))
	p2.start()
	p1.join()
	p2.join()
	end = time()
	print('总共耗费了%.2f秒.' % (end - start))


if __name__ == '__main__':
	main()

执行结果:

启动下载进程,进程号[568408].
开始下载万万没想到.mp4...
万万没想到.mp4下载完成! 耗费了6秒
启动下载进程,进程号[565896].
开始下载MySQL从删库到跑路.pdf...
MySQL从删库到跑路.pdf下载完成! 耗费了10秒
总共耗费了10.09秒.

运行上面的代码可以明显发现两个下载任务“同时”启动了,而且程序的执行时间将大大缩短,不再是两个任务的时间总和。

03 Python中的多线程

多任务可以由多进程完成,也可以由一个进程内的多线程完成。

Python的标准库提供了两个模块:_threadthreading_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。

启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行:

from random import randint
from threading import Thread
from time import time, sleep

def download(filename):
	print('开始下载%s...' % filename)
	time_to_download = randint(5, 10)
	sleep(time_to_download)
	print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))
	
def main():
	start = time()
	t1 = Thread(target=download, args=('MySQL从删库到跑路.pdf',))
	t1.start()
	t2 = Thread(target=download, args=('万万没想到.mp4',))
	t2.start()
	t1.join()
	t2.join()
	end = time()
	print('总共耗费了%.3f秒' % (end - start))

if __name__ == '__main__':
	main()

04 多进程还是多线程

无论是多进程还是多线程,只要数量一多,效率肯定上不去,为什么呢?

我们打个比方,假设你不幸正在准备中考,每天晚上需要做语文、数学、英语、物理、化学这5科的作业,每项作业耗时1小时。如果你先花1小时做语文作业,做完了,再花1小时做数学作业,这样,依次全部做完,一共花5小时。这种方式称为单任务模型。

如果你打算切换到多任务模型,可以先做1分钟语文,再切换到数学作业,做1分钟,再切换到英语,以此类推,只要切换速度足够快,这种方式就和单核CPU执行多任务是一样的了,以旁观者的角度来看,你就正在同时写5科作业。

但是,切换作业是有代价的,比如从语文切到数学,要先收拾桌子上的语文书本、钢笔(这叫保存现场),然后,打开数学课本、找出圆规直尺(这叫准备新环境),才能开始做数学作业。

操作系统在切换进程或者线程时也是一样的,它需要先保存当前执行的现场环境(CPU寄存器状态、内存页等),然后,把新任务的执行环境准备好(恢复上次的寄存器状态,切换内存页等),才能开始执行。这个切换过程虽然很快,但是也需要耗费时间。

如果有几千个任务同时进行,操作系统可能就主要忙着切换任务,根本没有多少时间去执行任务了,这种情况最常见的就是硬盘狂响,点窗口无反应,系统处于假死状态。所以,多任务一旦多到一个限度,反而会使得系统性能急剧下降,最终导致所有任务都做不好。

转载于:https://my.oschina.net/u/3938912/blog/3093268

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值