一. 进程(进程间数据是相互隔离的)
1. 开启子进程的两种方式(整个py文件为主进程)
p = Process(target='函数名',grgs=(函数的参数,)
p.start() 主进程向操作系统发送提交开启子进程的信号
p.join() 主进程等待子进程执行完毕(阻塞态)
# 开启子进程的方式一: 常用 导入模块 实例一个对象 ''' from multiprocessing import Process import time def task(name): print('%s is running' %name) time.sleep(3) print('%s is done' %name) # 在windows系统上,开启子进程的操作必须放到if __name__ == '__main__'的子代码中 if __name__ == '__main__': p=Process(target=task,args=('egon',)) #Process(target=task,kwargs={'name':'egon'}) p.start() # 只是向操作系统发送了一个开启子进程的信号 print('主') ''' # 开启子进程的方式二: from multiprocessing import Process import time class Myprocess(Process): def __init__(self,name): super().__init__() self.name=name def run(self): print('%s is running' %self.name) time.sleep(3) print('%s is done' %self.name) # 在windows系统上,开启子进程的操作必须放到if __name__ == '__main__'的子代码中 if __name__ == '__main__': p=Myprocess('egon') p.start() # 只是向操作系统发送了一个开启子进程的信号 print('主')
2.进程间的内存空间是相互隔离的
from multiprocessing import Process n=100 def task(): global n n=0 if __name__ == '__main__': p=Process(target=task) # p.start() #主进程向操作系统发送开启子进程的信号 p.join() # 主进程等待子进程运行完毕 print(n) #100
3.join的用法 (主进程等待子进程运行完毕)
from multiprocessing import Process import time def task(name,n): print('%s is running' %name) time.sleep(n) print('%s is done' %name) if __name__ == '__main__': start=time.time() p_l=[] for i in range(1,4): p = Process(target=task, args=('子%s' %i, i)) p_l.append(p) p.start() #并发的时候不按顺序 for p in p_l: p.join() print('主', (time.time() - start))
4.进程对象其它相关属性或方法
os.getpid() 获取当前进程的进程号
p.is_alive() 判断进程是否还活着
p.terminate() 杀死进程
#2. 进程对象其他相关的属性或方法 from multiprocessing import Process,current_process import time,os def task(): print('%s is running 爹是:%s' %(os.getpid(),os.getppid())) time.sleep(30) print('%s is done 爹是:%s' %(os.getpid(),os.getppid())) if __name__ == '__main__': p=Process(target=task,name='子进程1') p.start() # print(p.name) p.terminate() #杀死进程 # time.sleep(0.1) print(p.is_alive()) print('主:%s 主他爹:%s' %(os.getpid(),os.getppid()))
5.子进程回收的两种方式:
1.通过主进程p.jion() 回收
2.父进程结束后,操作系统会将子进程一并回收
6.僵尸进程与 孤儿进程
僵尸进程:主进程没死(死循环了),子进程运行完成之后,资源没有被主进程回收,占用资源, 解决方法:杀掉主进程,资源交给系统统一回收
孤儿进程:子进程还没运行完成,资源没有被父进程回收,这时候父进程挂掉了,子进程就成为孤儿进程
僵尸进程有害,孤儿进程无害
7.守护进程:
就是子进程去守护父进程,主进程结束,子进程也跟着结束
方式 :在子程序启动之前,即p1.start()之前,加上p1.daemon = True
from multiprocessing import Process import time import os def demo(name): print(f'start...{name}') time.sleep(100) print(f'end...{name}') print('子进程结束了') print('子进程编号:',os.getpid()) if __name__ == '__main__': p1 = Process(target=demo,args=('子进程1',)) #将子进程设置为守护进程 p1.daemon = True p1.start() #p1.join() time.sleep(3) print('主进程结束') print('主进程编号:', os.getpid())
8. 互斥锁
主要目的 是为了把并发变成串行进程锁
方式:1. 导入LOCK模块
2. 在创建进程对象时,设置args的一个参数为lock ==》 LOCK() 并锁定数据修改部分 " lock.acquire() +函数修改数据部分 + lock.release() "
from multiprocessing import Process from multiprocessing import Lock import json import time import random ''' 进程锁的主要目的 是为了把并发变成串行 进程锁 在创建进程对象时,设置args的一个参数为lock ==》 LOCK() 然后用lock.acquire() 函数 lock.release() 锁定或释放函数 ''' #1.查看余票 def search(name): with open('data.json','r',encoding='utf-8') as f: data_dict = json.load(f) print(f'用户[{name}]查看余票,余票还剩:{data_dict.get("number")}!') #2.若有余票,购买成功,票数会减少 def buy(name): with open('data.json','r',encoding='utf-8') as f1: data_dict = json.load(f1) if data_dict.get('number') > 0: data_dict['number'] -=1 #模拟网络延迟 time.sleep(random.randint(1,3)) with open('data.json','w',encoding = 'utf-8') as f2: json.dump(data_dict,f2) print(f'用户[{name}],抢票成功!!!') else: print(f'用户[{name}],抢票失败') def run(name,lock): search(name) # 如果不加锁,所有的人都能抢票成功 lock.acquire() buy(name) lock.release() if __name__ == '__main__': lock = Lock() #开启多进程实现并发 for line in range(10): p1 = Process(target= run,args =(f'jason{line}',lock)) # p1 = Process(target=run, args=(f'jason{line}', lock)) p1.start()
9.队列 相当与共享内存 可以存取数据
进程间通信(IPC机制)
原则:先进先出 管道+锁 主要用来实现进程间的通信
常用方法: put 只要队列满了 就会进入阻塞态 put _nowait 队列满了会报错
get:只要队列中有数据,就能获取数据,若没有数据则会进入阻塞态 get_nowait() 若无数据直接报错
from multiprocessing import Queue from multiprocessing import JoinableQueue import queue #第一种 q_obj = Queue(2) q_obj.put('json') q_obj.put('tank') #q_obj.put('www') # put 只要队列满了 就会进入阻塞态 #q_obj.put_nowait('www') # 只要队列满了 就会报错 print(q_obj.get()) #只要队列中有数据,就能获取数据,若没有数据则会进入阻塞态 print(q_obj.get()) print(q_obj.get()) #print(q_obj.get_nowait()) #第二种 q_obj1 = JoinableQueue(5) #第三种用法相同 q_obj2 = queue.Queqe(5)
进程间通信(IPC机制)
from multiprocessing import Process from multiprocessing import JoinableQueue import time def task1(q): x = 100 q.put(x) print('添加数据') time.sleep(3) print(q.get()) def task2(q): # 想要在task2中获取task1的x res = q.get() print(f'获取的数据是{res}') q.put(9527) if __name__ == '__main__': # 产生队列 q = JoinableQueue(10) # 产生两个不同的子进程 p1 = Process(target=task1, args=(q, )) p2 = Process(target=task2, args=(q, )) p1.start() p2.start()
10.生产者与消费者模型
生产者: 生产数据 ---》 队列 ---》消费者:使用数据
from multiprocessing import JoinableQueue from multiprocessing import Process import time # 生产者: 生产数据 ---》 队列 def producer(name, food, q): msg = f'{name} 生产了 {food} 食物' # 生产一个食物,添加到队列中 q.put(food) print(msg) # 消费者: 使用数据 《--- 列队 def customer(name, q): while True: try: time.sleep(0.5) # 若报错,则跳出循环 food = q.get_nowait() msg = f'{name} 吃了 {food} 食物!' print(msg) except Exception: break if __name__ == '__main__': q = JoinableQueue() # 创建两个生产者 for line in range(10): p1 = Process(target=producer, args=('tank1', f'Pig饲料{line}', q)) p1.start() # 创建两个消费者 c1 = Process(target=customer, args=('jason', q)) c2 = Process(target=customer, args=('sean', q)) c1.start() c2.start()
二. 线程 (线程之间数据是共享的)
进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。每个进程都有一个主线程
线程: 线程中没有主线程和子线程,所有的线程都是一样的
1 什么是线程 进程其实一个资源单位,而进程内的线程才是cpu上的执行单位 线程其实指的就是代码的执行过程 2 为何要用线程 线程vs进程 1. 同一进程下的多个线程共享该进程内的资源 2. 创建线程的开销要远远小于进程
1. 创建线程的两种方式: from threading import Thread
from threading import Thread import time # # number = 1000 # # # 启动线程的方式一: # 任务1: # def task(): # global number # number = 100 # print('start...') # time.sleep(1) # print('end...') # # # if __name__ == '__main__': # # 开启一个子线程 # t = Thread(target=task) # t.start() # # t.join() # print('主进程(主线程)...') # print(number) # 启动线程的方式二: # class MyThread(Thread): # def run(self): # print('start...') # time.sleep(1) # print('end...') # # # if __name__ == '__main__': # # 开启一个子线程 # t = MyThread() # t.start() # # t.join() # print('主进程(主线程)...')
2.守护线程
# from threading import Thread # import time # # def task(name): # print('%s is running' %name) # time.sleep(2) # print('%s is done' %name) # # if __name__ == '__main__': # t=Thread(target=task,args=('线程1',)) # t.daemon=True # t.start() # print('主')
3.线程锁
from threading import Thread,Lock import time mutex=Lock() n=100 def task(): global n mutex.acquire() temp=n time.sleep(0.1) n=temp-1 mutex.release() if __name__ == '__main__': t_l=[] for i in range(100): t=Thread(target=task) t_l.append(t) t.start() for t in t_l: t.join() print(n)
4.GIL(全局解释器锁)
GIL全局解释器锁: 是解释器锁,python的开发者的解释锁机制 ,因为python的内存管理机制不是内存安全的,在多线程工作时,GIL锁为了保证数据安全
python 会用解释器锁,把并发变成串行,导致无法使用多核优势
结论:在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势。
优化方法:多核情况下:计算机密集型多进程,I/O密集型多线程
#分析: 我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是: 方案一:开启四个进程 方案二:一个进程下,开启四个线程 #单核情况下,分析结果: 如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜 如果四个任务是I/O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜 #多核情况下,分析结果: 如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜 如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜 #结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。
实例比较
# 计算密集型任务 def task1(): # 计算1000000次 += 1 i = 10 for line in range(10000000): i += 1 # IO密集型任务 def task2(): time.sleep(3) if __name__ == '__main__': # 1、测试多进程: # 测试计算密集型 start_time = time.time() list1 = [] for line in range(6): p = Process(target=task1) p.start() list1.append(p) for p in list1: p.join() end_time = time.time() # 消耗时间: 0.44082188606262207 print(f'多进程计算密集型消耗时间: {end_time - start_time}') # 测试IO密集型 start_time = time.time() list1 = [] for line in range(6): p = Process(target=task2) p.start() list1.append(p) for p in list1: p.join() end_time = time.time() # 消耗时间: 0.44082188606262207 print(f'多进程IO密集型消耗时间: {end_time - start_time}') # 2、测试多线程: # 测试计算密集型 start_time = time.time() list1 = [] for line in range(6): p = Thread(target=task1) p.start() list1.append(p) for p in list1: p.join() end_time = time.time() # 消耗时间: 0.44082188606262207 print(f'多线程计算密集型消耗时间: {end_time - start_time}') # 测试IO密集型 start_time = time.time() list1 = [] for line in range(6): p = Thread(target=task2) p.start() list1.append(p) for p in list1: p.join() end_time = time.time() # 消耗时间: 0.44082188606262207 print(f'多线程IO密集型消耗时间: {end_time - start_time}') 多进程计算密集型消耗时间: 1.0132722854614258 多进程IO密集型消耗时间: 3.190805196762085 多线程计算密集型消耗时间: 3.1894755363464355 多线程IO密集型消耗时间: 3.0030970573425293
5.死锁与递归锁
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,
若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁
from threading import Thread,Lock import time mutexA=Lock() mutexB=Lock() class MyThread(Thread): def run(self): self.func1() self.func2() def func1(self): mutexA.acquire() print('