python中进程、线程、协程详细介绍_python进程、线程、协程的介绍及使用...

python中进程、线程、协程详细介绍_python进程、线程、协程的介绍及使用...

2023年7月26日发(作者:)

python中进程、线程、协程详细介绍_python进程、线程、协程的介绍及使⽤⼀、必备的理论基础操作系统理论:操作系统是⼀个协调管理控制计算机硬件资源与应⽤软件资源的控制程序操作系统的两⼤功能:将复杂的硬件操作封装成简单的接⼝给应⽤程序或者⽤户去使⽤将多个进程对硬件的竞争变得有序⼆、进程理论2.1什么是进程?进程指的是⼀个正在进⾏/运⾏的程序,进程是⽤来描述程序执⾏过程的虚拟概念。进程的概念起源于操作系统,进程是操作系统最核⼼的概念,操作系统其它所有的概念都是围绕进程的。进程vs程序:进程:程序执⾏的过程程序:⼀堆代码2.2 多道技术单道:⼀条道⾛道⿊ ---->串⾏多道技术:多道技术中的多道指的是多个程序,多道技术是为了解决多个程序竞争或者说共享同⼀个资源(⽐如cpu)的有序调度问题,解决⽅式即多路复⽤,多路复⽤分为时间上的复⽤和空间上的复⽤。1. 空间上的复⽤:内存中同时有多道(个)程序2. 时间上的复⽤:多道程序复⽤(共⽤)cpu的时间,切换+保存状态当执⾏程序遇到IO时,操作系统会将CPU的执⾏权限剥夺。优点: CPU的执⾏效率提⾼当执⾏程序执⾏时间过长时,操作系统会将CPU的执⾏权限剥夺。缺点: 程序的执⾏效率低2.3 并发与并⾏:串⾏: ⼀个任务完完整整地运⾏完成,才能运⾏下⼀个任务并发: 在单核(⼀个cpu)情况下,当执⾏两个A,B程序时,A先执⾏,当A遇到IO时,B开始争抢cpu的执⾏权限,再让B执⾏,他们看起像同时运⾏。并⾏:在多核(多个cpu)的情况下,当执⾏两个A,B程序时,A与B同时执⾏。他们是真正意义上的同时运⾏2.4 进程调度:要想多个进程交替运⾏,操作系统必须对这些进程进⾏调度,这个调度也不是随即进⾏的,⽽是需要遵循⼀定的法则,由此就有了进程的调度算法。先来先服务调度算法(了解)- ⽐如程序 a,b,若a先来,则让a先服务,待a服务完毕后,b再服务。- 缺点:执⾏效率低。短作业优先调度算法(了解)- 执⾏时间越短,则先调度。- 缺点:导致执⾏时间长的程序,需要等待所有时间短的程序执⾏完毕后,才能执⾏。现代操作系统的进程调度算法: 时间⽚轮转法 + 多级反馈队列时间⽚轮转法- 将CPU的执⾏时间,等分成N个时间⽚。⽐如同时有10个程序需要执⾏,操作系统会给你10秒,然后时间⽚轮转法会将10秒分成10等分。多级反馈队列1级队列: 优先级最⾼2级队列: 优先级次⾼3级队列: 优先级当前最低2.5 同步与异步同步与异步 指的是 “提交任务的⽅式”同步(串⾏): 若有两个任务需要提交,在提交第⼀个任务时,必须等待该任务运⾏完毕拿到结果后,才能继续提交并执⾏第⼆个任务,会导致任务是串⾏执⾏的。如两个a,b程序都要提交并执⾏,假如a先提交执⾏,b必须等a执⾏完毕后,才能提交任务。异步(并发): 若有两个任务需要提交,在提交第⼀个任务时,不需要原地等待,⽴即可以提交并执⾏第⼆个任务,会导致任务是并发执⾏的。如两个a,b程序都要提交并执⾏,假如a先提交并执⾏,b⽆需等a执⾏完毕,就可以直接提交任务。2.6 阻塞与⾮阻塞阻塞(等待): 凡是遇到 IO 都会阻塞。IO:input()print()(3)⽂件的读写数据的传输⾮阻塞 (不等待) : 除了IO都是⾮阻塞 (⽐如: 从1+1开始计算到100万)3.7 进程的三种状态就绪态:所有任务提交完毕后,就会进⼊就绪态运⾏态:通过进程调度⼀个任务开始执⾏,该任务进⼊运⾏态- 程序的执⾏时间过长 ----> 将程序返回给就绪态,等待下次调度。- ⾮阻塞阻塞态:凡是遇到IO操作的任务都会进⼊阻塞态,待IO操作结束,则阻塞态结束,进⼊就绪态,等待下次调度。问题:阻塞与同步是⼀样的吗?⾮阻塞与异步是⼀样的吗?同步与异步: 提交任务的⽅式阻塞与⾮阻塞: 进程的状态。异步⾮阻塞: ----> CPU的利⽤率最⼤化(通过并发对程序进程操作)2.8 创建进程的两种⽅式from multiprocessing import Process #Process 是⼀个类importtime#开启⼦进程⽅式⼀:直接调⽤Processdeftask(name):print(f'{name} is running')(3)print(f'{name} is done')#在windows系统上,开启⼦进程的操作必须要到if __name__ == '__main__':的⼦代码下⾯去if __name__ == '__main__':#target=任务(函数地址) ---> 创建⼀个⼦进程obj = Process(target=task,args=('egon',)) #args必须传⼀个元组的形式() #只是向操作系统发送了⼀个开启⼦进程的信号print('主') #代表主进程#开启⼦进程⽅式⼆:classMyprocess(Process):def __init__(self,name):super().__init__()=namedef run(self): #函数名必须叫runprint(f'{} is running')(3)print(f'{} is done')if __name__ == '__main__':obj= Myprocess('egon')()# ⾃动调⽤run⽅法print('主') #代表主进程join⽅法:让主进程在原地等待,等待⼦进程运⾏完毕后再运⾏,不会影响⼦进程的运⾏#join:from multiprocessing import Process #Process 是⼀个类importtime#开启⼦进程⽅式⼀:直接调⽤Processdeftask(name,n):print(f'{name} is running')(n)print(f'{name} is done')#在windows系统上,开启⼦进程的操作必须要到if __name__ == '__main__':的⼦代码下⾯去if __name__ == '__main__':#target=任务(函数地址) ---> 创建⼀个⼦进程#异步提交3个任务obj1 = Process(target=task,args=('⼦1',1)) #args必须传⼀个元组的形式obj2 = Process(target=task,args=('⼦2',2)) #args必须传⼀个元组的形式obj3 = Process(target=task,args=('⼦3',3)) #args必须传⼀个元组的形式()#只是向操作系统发送了⼀个开启⼦进程的信号() #只是向操作系统发送了⼀个开启⼦进程的信号() #只是向操作系统发送了⼀个开启⼦进程的信号()#主进程等obj1结束() #主进程等obj2结束() #主进程等obj3结束 #obj1与obj3谁在前谁在后⽆所谓print('主')上述代码冗余,改进如下#join:让主进程在原地等待,等待⼦进程运⾏完毕,不会影响⼦进程的运⾏from multiprocessing import Process #Process 是⼀个类importtime#开启⼦进程⽅式⼀:直接调⽤Processdeftask(name,n):print(f'{name} is running')(n)print(f'{name} is done')#在windows系统上,开启⼦进程的操作必须要到if __name__ == '__main__':的⼦代码下⾯去if __name__ == '__main__':#target=任务(函数地址) ---> 创建⼀个⼦进程#异步提交obj_1 =[]for i in range(1,4):obj= Process(target=task,args=('⼦%s' %i ,1)) #args必须传⼀个元组的形式obj_(obj)()for obj inobj_1:()print('主')#开启⼦进程⽅式⼆:classMyprocess(Process):def __init__(self,name):super().__init__()=namedef run(self): #函数名必须叫runprint(f'{} is running')(3)print(f'{} is done')if __name__ == '__main__':list1=[]for i in range(1,4):obj= Myprocess('⼦%s' %i)()# ⾃动调⽤run⽅法(obj)for obj inlist1:()print('主') #代表主进程2.9 进程对象相关的其他属性或⽅法2.9.1 进程PIDPID:每⼀个进程在操作系统内都有⼀个唯⼀的id号,称之为PIDfrom multiprocessing importProcess,current_processimporttime,os#第⼀种:current_process().piddeftask():print('⼦%s is running' %current_process().pid)(2)print('⼦%s is done' %current_process().pid)if __name__ == '__main__':p= Process(target=task,name='⼦进程1') #name给⼦进程起名()print() #'⼦进程1'print('主',current_process().pid)#第⼆种:(),d()deftask():print('⼦%s is running 爹是:%s' %((),d()))(2)print('⼦%s is done 爹是:%s' %((),d()))if __name__ == '__main__':p= Process(target=task)()print('主:%s 主他爹:%s' %((),d()))### 2.进程名字2.9.2 ⼦进程回收资源的两种⽅式:join让主进程等待⼦进程结束,并回收⼦进程资源,主进程再结束并回收资源。主进程 “正常结束” ,⼦进程与主进程⼀并被回收资源。2.10 进程间数据隔离进程隔离是为保护操作系统中进程互不⼲扰⽽设计的⼀组不同硬件和软件的技术这个技术是为了避免进程A写⼊进程B的情况发⽣。 进程的隔离实现,使⽤了虚拟地址空间。进程A的虚拟地址和进程B的虚拟地址不同,这样就防⽌进程A将数据信息写⼊进程B进程隔离的安全性通过禁⽌进程间内存的访问可以⽅便实现from multiprocessing importProcessimporttimenumber= 10deffunc():globalnumbernumber= 20print(number)if __name__ == '__main__':p_obj= Process(target=func)p_()p_()#让⼦进程先运⾏print(number)执⾏结果:20102.11 僵⼫进程与孤⼉进程(了解)僵⼫进程 (有坏处):在⼦进程结束后,主进程没有结束, ⼦进程PID不会被回收。缺点:操作系统中的PID号是有限的,如有⼦进程PID号⽆法正常回收,则会占⽤PID号。资源浪费。若PID号满了,则⽆法创建新的进程。孤⼉进程(没有坏处):在⼦进程没有结束时,主进程结束, ⼦进程PID不会被回收。操作系统优化机制(孤⼉院):当主进程意外终⽌,操作系统会检测是否有正在运⾏的⼦进程,会他们放⼊孤⼉院中,让操作系统帮你⾃动回收。2.12 守护进程、守护进程1.1、什么是守护进程?1、守护进程会在主进程代码运⾏结束的情况下,⽴即挂掉。2、守护进程本⾝就是⼀个⼦进程。3、主进程在其代码结束后就已经算运⾏完毕了(守护进程在此时就被回收),然后主进程会⼀直等⾮守护的⼦进程都运⾏完毕后回收⼦进程的资源(否则会产⽣僵⼫进程),才会结束,1.2、为什么要⽤守护进程?1、守护进程本⾝就是⼀个⼦进程,所以在主进程需要将任务并发执⾏的时候需要开启⼦进程。2、当该⼦进程执⾏的任务⽣命周期伴随着主进程的⽣命周期时,就需要将该⼦进程做成守护进程。#主进程会等待所有的⼦进程结束,是为了回收⼦进程的资源# 守护进程会等待主进程的代码执⾏结束之后再结束,⽽不是等待整个主进程结束.# 主进程的代码什么时候结束,守护进程就什么时候结束,和其他⼦进程的执⾏进度⽆关from multiprocessing importProcessimporttimedeftask():print('is running')(3)print('is done')if __name__ == '__main__':obj= Process(target=task)#守护进程必须在()调⽤之前执⾏ = True #将⼦进程设置为守护进程()(1)print('主进程结束')2.13 进程互斥锁互斥锁是⼀把锁,让并发变成串⾏, 牺牲了执⾏效率, 保证了数据安全进程之间数据是不共享的,但是共享同⼀套⽂件系统,⽽共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理,如抢票例⼦from multiprocessing importProcessfrom multiprocessing import Lock #---进程互斥锁importjson,time,random#抢票例⼦:#查票defsearch(name):with open('','rt',encoding='utf-8')as f:dic=(f)(1)print(f'%s 查看到余票为%s' %(name,dic['count']))#购票defget(name):with open('','rt',encoding='utf-8')as f:dic=(f)if dic['count'] >0 :(t(1,3))dic['count'] -= 1with open('','wt',encoding='utf-8')as f:(dic,f)print('%s购票成功' %name )else:print('没有票了')defrun(name,lock):search(name)#并发e() #加锁get(name) #串⾏e() #释放锁if __name__ == '__main__':lock= Lock() #产⽣⼀个互斥锁对象for i in range(10):p= Process(target=run,args=('路⼈%s' %i,lock))()#() #join只能将进程的任务整体变成串⾏2.14 队列(先进先出)队列相当于⼀个第三⽅的管道,可以存放数据。先进先出指的是先存放的数据就先取出来应⽤:让进程之间的数据进⾏交互from multiprocessing import Queue #multiprocessing提供的队列from multiprocessing import JoinableQueue #基于Queue封装的队列import queue #python内置的队列,也是先进先出#第⼀种:#Queue(5)指的是队列中只能存放5个数据q_obj1 = Queue(5) #q_obj1队列对象 #第⼀种#q_obj1 = (5) #第⼆种#q_obj1 = JoinableQueue(5) #第三种#添加数据到队列中q_('jason')print('添加1个')q_('hcy')print('添加1个')q_('bh')print('添加1个')q_('tank')print('添加1个')q_('zzc')print('添加1个')#q_('第6个') #put:只要队列满了,就会进⼊阻塞#print('第6个')#q__nowait('sean')#只要数据满了就会报错#get:只要队列中有数据,就能获取数据,若没有则会进⼊阻塞print(q_())print(q_())print(q_())print(q_())print(q_())print(q_()) #到这步已经没有数据了#print(q__nowait()) #get_nowait:若队列中没有数据获取则会报错2.15 IPC机制(进程间通信)进程间数据是相互隔离的,若想实现进程间通信,可以利⽤队列from multiprocessing importProcessfrom multiprocessing importJoinableQueueimporttimedeftask1(q):(100)print('添加数据100')(3)print(())deftask2(q):#想要在task2中获取task1的100res =()print(f'获取的数据是{res}')(9527)if __name__ == '__main__':#产⽣队列q =JoinableQueue()#产⽣两个不同的⼦进程p1 = Process(target=task1,args=(q,))p2= Process(target=task2,args=(q,))()()执⾏结果:添加数据100获取的数据是10095272.16 ⽣产者消费者模型⽣产者:代指⽣产数据的消费者:代指使⽤数据的该模型的⼯作⽅式:⽣成数据传递消费者处理实现⽅式:⽣产者---->队列此模型是为了解决供需不平衡问题。from multiprocessing importJoinableQueuefrom multiprocessing importProcessimporttime#⽣产者:⽣成数据放到队列中defproducer(name,food,q):msg= f'{name}⽣产了{food}'(food)#⽣产⼀个⾷物,添加到队列中print(msg)#消费者:使⽤的数据 从队列中获取defcustomer(name,q):whileTrue:try:(1)#若报错,则跳出循环food =_nowait()msg= f'{name}吃了{food}⾷物!'print(msg)exceptException:breakif __name__ == '__main__':q=JoinableQueue()#创建10个⽣产者for line in range(10):p1= Process(target=producer,args=('tank1',f'油条{line}',q,))()#创建两个消费者c1 = Process(target=customer,args=('jason',q))c2= Process(target=customer,args=('egon',q))()()三、线程理论3.1 什么是线程?进程其实是⼀个资源单位,⽽进程内的线程才是cpu的执⾏单位。线程其实指的就是代码的执⾏过程。进程就好⽐划了块地,在这块地上开了⼀个车间,⽽线程好⽐车间⾥的⼀条或多条流⽔线,真正运⾏的也就是流⽔线。那每个车间必须⾄少有⼀条流⽔线吧。注意:开启⼀个进程,⼀定会⾃带⼀个线程,线程才是真正地执⾏者3.2 为什么要使⽤线程?可以节省内存资源。进程vs线程开启进程:会产⽣⼀个内存空间,申请⼀块资源。会⾃带⼀个主线程。开启⼦进程的速度要⽐开启⼦线程的速度慢开启线程:⼀个进程内可以开启多个线程,从进程的内存空间中申请资源。节省资源。强调:进程之间数据是隔离的,同⼀进程下多个线程共享该进程的数据,如下所⽰n = 100deftask():globalnn=0if __name__ == '__main__':t= Thread(target=task)()()print('主',n) #n为03.3 如何使⽤?开启线程的两种⽅式:from threading importThreadimporttime#开启⽅式⼀:deftask():print('') #最先打印,因为开启线程的速度要⽐进程的快(1)print('') #因为睡了1秒,最后打印if __name__ == '__main__':#开启⼀个⼦线程t = Thread(target=task)()print('主进程(主线程)...') #其次打印#开启⽅式⼆:classMythread(Thread):defrun(self):print('') #最先打印,因为开启线程的速度要⽐进程的快(1)print('') #因为睡了1秒,最后打印if __name__ == '__main__':#开启⼀个⼦线程t =Mythread()()print('主进程(主线程)...') #其次打印⽰例2:# ⾯向对象的⽅式起线程# from threading import Thread# class MyThread(Thread):# def __init__(self,a,b):# self.a = a# self.b = b# super().__init__()## def run(self):# print()## t = MyThread(1,2)# () # 开启线程 才在线程中执⾏run⽅法# print()3.4,线程相关的其他属性或⽅法from threading importThread,active_count,current_threadimporttime,osdeftask():print('%s is running'%current_thread().name) #查看⼦线程名(2)print('%s is done' %()) #查看⼦线程 PID号if __name__ == '__main__':t=Thread(target=task,)()#()#print('主',active_count()) #查看运⾏的线程数:2print('主',current_thread().name) #查看主线程的名字4.5 线程互斥锁#未加互斥锁状态:from threading importThread,Lockimporttimen= 100deftask():globalntemp= n     #每个线程的temp值都为100,temp不是与n绑定,⽽是temp与h的值绑定(0.1) #等100个线程全造完 如果没有阻塞n的输出结果为0n = temp-1 #如果这句在另⼀个线程的temp=n的后⾯执⾏,temp的值就没有得到跟新。顺序落后的原因是:cup切到另⼀个线程上去了。切掉之后上⼀线程的最终结果可能⽆法保留到下⼀个线程if __name__ == '__main__':t_1=[]for i in range(100):t=Thread(target=task)t_(t)()for t int_1:()print(n) #n-->99tip:a=1temp=aa=2print(temp) temp=1#加互斥锁状态:from threading importThread,Lockimporttimemutex=Lock()n= 100deftask():globaln        #每个线程拿到初始值n为e()temp= n #这⾥的每个线程拿到的n为100,(0.1) #等100个线程全造完n = e()if __name__ == '__main__':t_1=[]for i in range(100):t=Thread(target=task)t_(t)()for t int_1:()print(n) #n-->0补充:# from threading import Thread# n = 0# def add():# for i in range(500000):# global n #再改之前切掉了 n-1 qie n=(n-1)# n -=1# def sub():# for i in range(500000):# global n# n +=1## t_l = []# for i in range(2):# t1 = Thread(target=add)# ()# t2 = Thread(target=sub)# ()# t_(t1)# t_(t2)# for t in t_l:# ()# print(n)# from threading import Thread# import time# n = []# def append():# for i in range(500000):# (1)# def pop():# for i in range(500000):# if not n:# (0.0000001)# ()## t_l = []# for i in range(20):# t1 = Thread(target=append)# ()# t2 = Thread(target=pop)# ()# t_(t1)# t_(t2)# for t in t_l:# ()# print(n)# += -= *= /= while if 数据不安全 + 和 赋值是分开的两个操作# append pop strip数据安全 列表中的⽅法或者字典中的⽅法去操作全局变量的时候 数据安全的# 线程之间也存在数据不安全# import dis# a = 0# def func():# global a# a += 1'''56 0 LOAD_GLOBAL 0 (a)2 LOAD_CONST 1 (1)4 INPLACE_ADD# GIL锁切换了6 STORE_GLOBAL 0 (a)'''# (func)# import dis# a = []# def func():# (1)## (func)'''70 0 LOAD_GLOBAL 0 (a)2 LOAD_ATTR 1 (append)4 LOAD_CONST 1 (1)6 CALL_FUNCTION 1 #线程⾥的某个函数是原⼦性操作,函数内部不是原⼦性操作,像+=就不是原⼦性操作8 POP_TOP'''import timefrom threading import Threadn = 0def add():for i in range(10):global nn =(1)print(n)n=3def sub():for i in range(10):global nn=(1)t_l = []for i in range(2):t1 = Thread(target=add)()t2 = Thread(target=sub)()t_(t1)t_(t2)for t in t_l:()#print(n)3.6 线程池与进程池进程池是⽤来限制创建的进程数。线程池是⽤来限制创建的线程数。# 什么是池# 要在程序开始的时候,还没提交任务先创建⼏个线程或者进程# 放在⼀个池⼦⾥,这就是池# 为什么要⽤池?# 如果先开好进程/线程,那么有任务之后就可以直接使⽤这个池中的数据了# 并且开好的线程或者进程会⼀直存在在池中,可以被多个任务反复利⽤# 这样极⼤的减少了开启关闭调度线程/进程的时间开销# 池中的线程/进程个数控制了操作系统需要调度的任务个数,控制池中的单位# 有利于提⾼操作系统的效率,减轻操作系统的负担# 发展过程# threading模块 没有提供池# multiprocessing模块 仿照threading写的 Pool# s模块 线程池,进程池都能够⽤相似的⽅式开启使⽤from s importThreadPoolExecutorimporttime#让pool只能创建100个线程pool = ThreadPoolExecutor(100) #pool为线程池对象deftask(line):print(line)(3)if __name__ == '__main__':for line in range(1000):#异步提交任务(task, line)#同步提交任务#res = (task,line).result() #原地等拿到结果#print(res)from s importThreadPoolExecutorimporttimeimportrandom#让pool只能创建100个线程pool = ThreadPoolExecutor(10) #pool为线程池对象deftask(line):print(line)(t(1,2))return '⼩仓'if __name__ == '__main__':l=[]for line in range(50):#异步提交任务#(task, line)#res = (task,line).result() #res拿到task任务提交后返回的结果#print(res)future =(task,line)(future)wn(wait=True) #关闭进程池的⼊⼝,并且在原地等待进程池内所有任务运⾏完毕for future inl:print(())print('主')#使⽤字典获取if __name__ == '__main__':tp = ProcessPoolExecutor(4)futrue_l = {}for i in range(20): # 异步⾮阻塞的ret = (func,i,b=i+1)futrue_l[i] = ret#绑定#print('--',()) # Future未来对象 会有阻塞效果for key in futrue_l: # 同步阻塞的print(key,futrue_l[key].result())回调函数: add_done_callbackfrom s importThreadPoolExecutorimporttimepool= ThreadPoolExecutor(50)deftask1(n):print(f'{n}')(3)return 'baohan'defget_result(obj):result=()print(result)if __name__ == '__main__':n= 1whileTrue:#add_done_callback(参数1), 会将submit提交的task1执⾏返回的结果,传给get_result中的第⼀个参数,#第⼀个参数是⼀个对象。(task1,n).add_done_callback(get_result)n+= 1from s importThreadPoolExecutorfrom simportProcessPoolExecutorimporttime,os,requestsdefget(url):print('%s GET %s' %((),url))(3)response=(url)if _code == 200:res=se:res= '下载失败'returnresdefparse(future):(1)res=()print('%s 解析结果为%s' %((),len(res)))if __name__ == '__main__':urls=['','','','','','= ProcessPoolExecutor(7)l=[]for url inurls:future=(get,url)#parse会在任务运⾏完毕后⾃动触发,然后接收⼀个参数future对象#结果future对象会在任务运⾏完毕后⾃动传给回调函数_done_callback(parse) parse是回调函数,add_done_callback()是中间函数(future)wn(wait=True)print('主')3.7 GIL全局解释器锁什么是GIL?GIL全局解释器锁,本质上就是⼀把互斥锁,保证数据安全。在Cpython解释器中,每个进程内都会存在⼀把GIL,同⼀进程内的每个线程必须要抢到GIL后才能执⾏⾃⼰的代码。同⼀个进程下开启的多线程,同⼀时刻只能有⼀个线程执⾏,⽆法利⽤多核优势。即⽆法实现并⾏,但可以实现并发。GIL与多线程PS:cpu是⽤来计算的,不是⽤来做I/O的多cpu,意味着可以有多个核并⾏完成计算,所以多核提升的是计算性能每个cpu⼀旦遇到I/O阻塞,仍然需要等待,所以多核对I/O操作没什么⽤处#分析:我们有四个任务需要处理,若实现并发的效果,解决⽅案可以是:⽅案⼀:开启四个进程⽅案⼆:⼀个进程下,开启四个线程#单核情况下,分析结果:如果四个任务是计算密集型,没有多核来并⾏计算,⽅案⼀徒增了创建进程的开销,⽅案⼆胜如果四个任务是I/O密集型,⽅案⼀创建进程的开销⼤,且进程的切换速度远不如线程,⽅案⼆胜#多核情况下,分析结果:如果四个任务是计算密集型,多核意味着并⾏计算,在python中⼀个进程中同⼀时刻只有⼀个线程执⾏⽤不上多核,⽅案⼀胜如果四个任务是I/O密集型,再多的核也解决不了I/O问题,⽅案⼆胜结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多⼤性能上的提升,甚⾄不如串⾏(没有⼤量切换),但是,对于IO密集型的任务效率还是有显著提升的总结:计算密集型:多进程效率⾼I/O密集型:多线程效率⾼3.8 死锁与递归锁(了解)from threading importThread,Lock,RLockimporttime###死锁mutexA =Lock()mutexB=Lock()classMythread(Thread):defrun(self):self.f1()self.f2()deff1(self):e()print('%s 抢到A锁' %)e()print('%s 抢到B锁' %)e()e()passdeff2(self):e()print('%s 抢到B锁' %)(2) #⾜够其他99个线程起来了e()print('%s 抢到A锁' %)e()e()if __name__ == '__main__':for i in range(100):t=Mythread()()#递归锁mutexA =RLock()mutexB=RLock()classMythread(Thread):defrun(self):self.f1()self.f2()deff1(self):e()print('%s 抢到A锁' %)e()print('%s 抢到B锁' %)e()e()passdeff2(self):e()print('%s 抢到了B锁' %)(2) #⾜够其他99个线程起来了e()print('%s 抢到了A锁' %)e()e()if __name__ == '__main__':for i in range(100):t=Mythread()()3.9 信号量(了解)from threading importSemaphore,Threadimporttime,randomsm= Semaphore(5) #限制5个⼈同⼀时刻能拿到锁deftask(name):e()print(f'{name}正在上厕所')(t(1,3))e()if __name__ == '__main__':for i in range(20):t= Thread(target=task,args=('路⼈%s' %i,))()3.10 Event事件(了解)⼀个进程等其他进程结束后才能运⾏from threading importThread,Eventimporttime#模拟车等红绿灯event =Event()deflight():print('红灯正亮着')(3)()#绿灯亮defcar(name):print('车%s正等绿灯' %name)()#等绿灯print('车%s通⾏' %name)if __name__ == '__main__':#红绿灯t1 = Thread(target=light)()#车for i in range(10):t= Thread(target=car,args=(i,))()四、协程什么是协程?协程: 单线程下实现并发,协程可以理解为'协助'主线程(当主线程遇到io时,会切换到其他协程上去,所有任务之间都可以相互切换)- 在IO密集型的情况下,使⽤协程能提⾼最⾼效率。注意: 协程不是任何单位,只是⼀个程序员YY出来的东西,操作系统⾥⾯只有进程和线程的概念,(操作系统调度的是线程)总结: 在单线程下实现多个任务间遇到IO就切换就可以降低单线程的IO时间,从⽽最⼤限度地提⾼单线程地效率。协程的⽬的:⼿动实现 "IO切换 + 保存状态" 去欺骗操作系统,让操作系统误以为没有IO操作,将CPU的执⾏权限给你。from gevent import monkey #猴⼦补丁_all() #识别下列所有的任务是否有IO操作from gevent import spawn #spawn 任务from gevent importjoinallimporttimedeftask1():print('start ')(3)print('end ')deftask2():print('start ')(5)print('end ')if __name__ == '__main__':start_time=()sp1= spawn(task1) #spawn 提交任务sp2 =spawn(task2)#()#()joinall([sp1,sp2])end_time=()print(f'消耗时间: {end_time - start_time}')⽰例2:from gevent import _all() #这两句放到最上⾯import time # socketimport geventdef func(): # 带有io操作的内容写在函数⾥,然后提交func给geventprint('start func')(1) #不是真正的睡⼀秒,⽽是切出来等待⼀秒print('end func')#x=()g1 = (func) # 本质是⼀个线程,前5个可以认为是并发的 主现程遇到io才切到函数中 #g2 = (func)print('aaa')补充内容:#进程#线程#正常的开发语⾔ 多线程可以利⽤多核#cpython解释器下的多个线程不能利⽤多核 : 规避了所有io操作的单线程 线程之间切换#协程#是操作系统不可见的#协程本质就是⼀条线程 多个任务在⼀条线程上来回切换 ⼀条线程中切换#利⽤协程这个概念实现的内容 : 来规避IO操作,就达到了我们将⼀条线程中的io操作降到最低的⽬的#import time#def func1():#print('start')#(1)#print('end')##def func2():#print('start')#(1)#print('end')#切换 并 规避io 的两个模块#gevent = 利⽤了 greenlet 底层模块完成的切换 + ⾃动规避io的功能#asyncio = 利⽤了 yield 底层语法完成的切换 + ⾃动规避io的功能#tornado 异步的web框架#yield from - 更好的实现协程#send - 更好的实现协程#asyncio模块 基于python原⽣的协程的概念正式的被成⽴#特殊的在python中提供协程功能的关键字 : aysnc await#进程 数据隔离 数据不安全 操作系统级别 开销⾮常⼤ 能利⽤多核#线程 数据共享 数据不安全 操作系统级别 开销⼩ 不能利⽤多核 ⼀些和⽂件操作相关的io只有操作系统能感知到#协程 数据共享 数据安全 ⽤户级别 更⼩ 不能利⽤多核 协程的所有的切换都基于⽤户,只有在⽤户级别能够感知到的io才会⽤协程模块做切换来规避(socket,请求⽹页的)#⽤户级别的协程还有什么好处:#减轻了操作系统的负担#⼀条线程如果开了多个协程,那么给操作系统的印象是线程很忙,这样能多争取⼀些时间⽚时间来被CPU执⾏,程序的效率就提⾼了#少排⼏次队#a = 1#def func():#global a## 切换#a += 1 #协程不会再字节码之间切换## 切换##import dis#(func)#对于操作系统 : python代码--> 编译 --> 字节码 --> 解释 --> ⼆进制101010#⼆进制 反编译过来的 --> LOAD_GLOBAL#4cpu#进程 :5个进程#线程 :20个#协程 :500个#5*20*500 = 50000View Code

发布者:admin,转转请注明出处:http://www.yc00.com/xiaochengxu/1690380003a340750.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信