python多进程和多线程使用案例_多进程和多线程

本文详细介绍了Python中的多进程和多线程概念,包括进程与线程的区别、并发与并行、GIL锁的影响。通过实例展示了Python中如何使用多进程和多线程,特别提到了计算密集型任务适合多进程,IO密集型任务适合多线程。最后,文章还探讨了Unix/Linux的fork机制以及Python的multiprocessing模块。
摘要由CSDN通过智能技术生成

进程的概念

进程:一个正在执行的程序

计算机程序是存储在磁盘上的可执行二进制(或其他类型)文件,只有把它们加载到内存中,并被操作系统调用,它们才会拥有其自己的生命周期。

进程是表示的一个正在执行的程序。

每个进程都拥有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据。

操作系统负责其上所有进程的执行,并为这些进程合理地分配执行时间。

进程之间是独立的,不能共享彼此的数据。

并行与并发

并行:同时进行多个任务

并发:短时间内,运行多个任务

并发:单CPU,多进程并发

无论是并行还是并发,在用户看来都是'同时'运行的,不管是进程还是线程,都只是一个任务而已,真实干活的是cpu,cpu来做这些任务,而一个cpu同一时刻只能执行一个任务

一 并发:是伪并行,即看起来是同时运行。单个cpu+多道技术就可以实现并发,(并行也属于并发)

并行:多CPU(同时运行,只有具有多个cpu才能实现并行)

并行满足的条件是总进程数量不多于 CPU核心数量!因此,现在PC运行的程序大部分都是轮询调度产生的并行假象

举例:比如PC有四个核,六个任务,这样同一时间有四个任务被执行,假设分别被分配给了cpu1,cpu2,cpu3,cpu4,

一旦任务2遇到I/O就被迫中断执行,此时任务5就拿到cpu1的时间片去执行,这就是单核下的多道技术

而一旦任务2的I/O结束了,操作系统会重新调用它(需知进程的调度、分配给哪个cpu运行,由操作系统说了算),可能被分配给四个cpu中的任意一个去执行

所以,现代计算机经常会在同一时间做很多件事,一个用户的PC(无论是单cpu还是多cpu),都可以同时运行多个任务(一个任务可以理解为一个进程)。

多道技术:内存中同时存入多道(多个)程序,cpu从一个进程快速切换到另外一个,使每个进程各自运行几十或几百毫秒,这样,虽然在某一个瞬间,一个cpu只能执行一个任务,但在1秒内,cpu却可以运行多个进程,这就给人产生了并行的错觉,即伪并发,以此来区分多处理器操作系统的真正硬件并行(多个cpu共享同一个物理内存)

例子:在python中执行耗时操作

结果:

用进程来分担耗时任务后:

python进程使用流程

线程的概念

线程被称作轻量级进程。

与进程类似,不过它们是在同一个进程下执行的。

并且它们会共享相同的上下文。

当其他线程运行时,它可以被抢占(中断)和临时挂起(也称为睡眠)

线程的轮训调度机制类似于进程的轮询调度,只不过这个调度不是由操作系统来负责,而是由Python解释器来负责。

GIL锁

Python在设计的时候,还没有多核处理器的概念。因此,为了设计方便与线程安全,直接设计了一个锁。这个锁要求,任何进程中,一次只能有一个线程在执行。

因此,并不能为多个线程分配多个CPU。所以Python中的线程只能实现并发,而不能实现真正的并行。但是Python3中的GIL锁有一个很棒的设计,在遇到阻塞(不是耗时)的时候,会自动切换线程。

Scrapy、Django、Flask、Web2py ...等框架 都是使用多线程来完成

GIL锁带给我们的新认知,遇到阻塞就自动切换。因此我们可以利用这种机制来有效的避开阻塞 ,充分利用CPU

例子:使用线程来避开阻塞任务

python线程使用流程

计算密集型和IO密集型

IO密集型遇到等待时,不消耗CPU,CPU会调度其他程序执行,使用多线程可以有效的进行并发(爬虫的请求是IO密集型),线程在系统中所占资源较少。

计算密集型:CPU会一直计算,此时使用多线程,反而会耗时更多,此时使用多进程(CPU越多,性能越好)。

使用多进程/多线程实现并发服务器:

importsocketfrom multiprocessing importProcessfrom threading importThreaddefread(conn,addr):whileTrue:

data= conn.recv(1024)ifdata:print('客户端{}的消息:{}'.format(addr,data.decode()))

conn.send(data)else:

conn.close()print('客户端{}断开连接'.format(addr))break

if __name__ == '__main__':

server=socket.socket()

server.bind(('', 9998)) #注意这些代码要写在if...main...判断下面,因为在Windows中,使用进程的模式类似导入,在判断外面的代码会在子进程执行

server.listen(5)whileTrue:print('等待客户端连接')

conn,addr= server.accept() #有客户端连接,就往下进行

print('客户端{}已连接'.format(addr))#p = Process(target=read,args=(conn,addr)) # 使用多进程,每来一个客户端连接,就分出一个进程去和它一对一处理

#p.start()

t = Thread(target=read,args=(conn,addr)) #使用多线程,每来一个客户端连接,就分出一个线程去和它一对一处理

t.start()

补充

fork介绍

Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。

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

importosif __name__ == '__main__':print('进程 (%s) start...' %os.getpid())

pid=os.fork()#time.sleep(10)

if pid == 0: #若fork()返回0为子进程

print("子进程{},父进程{}".format(os.getpid(), os.getppid())) #getpid()获取当前进程的pid,getppid()获取父进程的pid

else: #父进程fork返回子进程的id

print("父进程{},子进程{}".format(os.getpid(), pid))>>进程 (3130) start...

父进程3130,子进程3131

子进程3131,父进程3130

multiprocessing模块介绍

python中的多线程无法利用CPU资源,在python中大部分计算密集型任务使用多进程。如果想要充分地使用多核CPU的资源(os.cpu_count()查看)

python中提供了非常好的多进程包multiprocessing。

multiprocessing模块用来开启子进程,并在子进程中执行功能(函数),该模块与多线程模块threading的编程接口类似。

multiprocessing的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。

计算密集型任务:如金融分析,科学计算

multiprocessing.current_process() :在任何一个进程中搞清楚自己是谁,在线程中也有类似方法

Process类的介绍

1.创建进程的类

Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)

强调:

1. 需要使用关键字的方式来指定参数

2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

2.参数介绍

group参数未使用,值始终为None

target表示调用对象,即子进程要执行的任务

args表示调用对象的位置参数元组,args=(1,2,'al',)

kwargs表示调用对象的字典,kwargs={'name':'al','age':18}

name为子进程的名称

3.方法介绍

p.start():启动进程,并调用该子进程中的p.run()

p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法

p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。

如果p还保存了一个锁那么也将不会被释放,进而导致死锁,线程中无此方法

p.is_alive():如果p仍然运行,返回True

p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)再往下运行。timeout是可选的超时时间,

需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程

4.属性介绍

p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置

p.name:进程的名称

p.pid:进程的pid,线程中是t.ident 注意只有进程或线程已经启动才会分配pid或ident

p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)

p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,

这类连接只有在具有相同的身份验证键时才能成功(了解即可)

以用面向对象的形式使用进程与线程

关键点:

继承Process或Thread类

重写__init__方法

重写run方法

from multiprocessing importProcessimporttimeimportrandomimportosclass pro(Process): #继承Process类

def __init__(self,name):

super().__init__() #重写__init__方法 这里是直接调用父类的初始化方法

self.name =namedef run(self): #重写run方法

print('%s is working,父进程%s,当前进程%s'%(self.name,os.getppid(),os.getpid()))

time.sleep(1)print('%s end'%self.name)if __name__ =='__main__':

p1= pro('a')

p2= pro('b')

p3= pro('c')

p1.start()

p2.start()

p3.start()print('主进程',os.getpid())>>主进程9116bisworking,父进程9116,当前进程4316

aisworking,父进程9116,当前进程6664

cisworking,父进程9116,当前进程10132

b end

a end

c end

总结:

Windows用的导入(所以if...main...判断外面的代码,在子进程仍会执行)

方法:

p= Process(target=func,name='p1',daemon=True) #实例的时候可以指定方法、名字、是否守护进程

p.current_process() 获取当前进程对象

p.is_alive() 判断进程实例是否在运行 是返回True 否返回False

p.terminate() 结束进程 线程无此方法

p.name 获取进程名字

p.join() 等待子进程结束再往下执行

p.daemon=True 把进程设为守护进程 (父进程结束,守护进程也结束)

注意:如果设置了join() 那么terminate和daemon 就不管用啦

p.pid 获取当前进程的pid 线程是t.ident

注意:只有进程(或线程)启动之后,操作系统(或python解释器)才会分配pid(或ident)

默认在子进程当中,会关闭标准输入importsysimportos

sys.stdin= os.fdopen(0) #加上这行代码,子进程才能使用标准输入

多线程的方法与属性

t.setDaemon(True)

t.setName('p1')

t.getName()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值