python并发编程
# 进程 """ 程序运行的过程,是操作系统资源分配的最小单位.资源分配的是cpu和内存的物理资源. 进程号(PID):进程的号码,不同进程有不同的号码.进程之间,彼此隔离,通过socket通信 """ # 进程三状态: 就绪-----运行-----阻塞 (在阻塞状态下,等时间片到了时,转为运行状态) # 并发: 一个cpu同一时间执行多个任务 # 并行: 多个cpu同一时间执行多个任务""" # cpu进程的调度方法:1, 先来的先执行 2, 短作业优先 3, 时间片轮转 4, 多级反馈 # 同步: 一条主线,调用一个方法,要等这个方法执行结束,再开始下一个 # 异步: 多条主线,不在一条主线上的方法,不关心,齐头并进
1, 进程的基本使用
# 获取进程号 import os.getpid() # 获取当前进程号(子进程) os.getppid() # 获取父进程 """父子是相对的 pycharm 和 py 那么pycharm是父进程,py文件是子进程 py 和 func 那么py文件是父进程, func 是子进程 """ # linux 命令 ps -aux # 获取所有进程列表 ps -aux | grep 进程号码 # 获取单独进程号进程具体信息 kill -9 进程号码 # 杀死进程号码进程
1.1.1,在进程里边创建子进程
import time,os from multiprocessing import Process # 曾经的代码 def func(): print(os.getpid(),"start") # 29840 start time.sleep(1) print(os.getpid(),"end") # 29840 end if __name__ == "__main__": func() print("主进程",os.getpid()) # 主进程 29840 # 创建子进程 def func(): print(os.getpid(),"start") # 29855 start time.sleep(1) print(os.getpid(),"end") # 29855 end if __name__ == "__main__": p = Process(target = func) # 创建一个即将要执行func函数的子进程对象 p.start() # 异步开启子进程 print("主进程",os.getpid()) # 主进程 29854 """ 总结: 曾经我们的代码时在一个主进程中,代码从上至下一次执行;今天,通过Process能够在 主进程中再创建子进程,start()异步开启子进程,不关心这个进程结束没有,主进程继续执行 而且,我们看到主进程一般情况要比子进程快一点,子进程的顺序却是不同的!(抢占cpu,谁快谁先执行) """
1.1.2 带参数的子进程
import time,os from multiprocessing import Process def func(n): time.sleep(0.5) print(n, "-----", os.getpid()) if __name__ == "__main__": for i in range(10): p = Process(target=func, args=(i,)) # args接收参数,为元组! p.start() print("主进程", os.getpid())
1.1.3 进程之间数据隔离
# 主进程负责开启子进程,也负责回收子进程.主进程代码执行完毕,不代表主进程结束!如果不回收,则子进程称之为僵尸进程.几乎不存在,底层封装好了! count = 10 def func(): global count count += 1 print("我是子进程count={}".format(count)) if __name__ == "__main__": p=Process(target=func) p.start() # 我是子进程count=11 time.sleep(1) print(count) # 10
1.1.4 多进程异步并发
def func(n): print("数字{}<=>1.子进程id>>{},2父进程id>>{}".format(n,os.getpid(),os.getppid())) if __name__ == "__main__": for i in range(1,11): Process(target=func,args=(i,)).start() print("主进程执行结束了....",os.getpid())
1.1.5 join 同步子父进程
# 只有一个子进程时 def func(): time.sleep(0.5) print("子进程", os.getpid()) if __name__ == "__main__": p = Process(target=func) p.start() # 异步,非阻塞 p.join() # 同步阻塞 print("主进程", os.getpid()) """ 如果没有p.join(),先打印"主进程",再打印"子进程" p.join()后,会等子进程结束后在执行主进程 """ # 多个子进程怎么执行呢? def func(n): time.sleep(0.5) print(n, "-----", os.getpid()) if __name__ == "__main__": lst = [] for i in range(10): p = Process(target=func, args=(i,)) p.start() lst.append(lst) for j in lst: p.join() # 已结束的进程相当于pass,没有执行的进行阻塞 print("主进程", os.getpid())
1.1.6 守护进程
""" 守护进程守护的是主进程,如果主进程中的所有代码执行完毕了, 当前这个守护进程会被立刻杀死,立刻终止. 语法: 进程.daemon = True 设置当前这个进程为守护进程 必须写在start()调用进程之前进行设置 默认:主进程会默认等待所有子进程执行结束之后,在关闭程序,释放资源 """ def func1(): count = 1 while True: print("*" * count) time.sleep(0.5) count += 1 def func2(): print("start func2 当前子进程任务") time.sleep(3) print("end func2 当前子进程任务") if __name__ == "__main__": p1 = Process(target=func1) p2 = Process(target=func2) # 设置p1这个进程对象为守护进程 p1.daemon = True p1.start() p2.start() time.sleep(1) print("主进程执行结束 ... ")
1.1.7 面向对象创建子进程
class MyProcess(Process): def __init__(self,arg): # 手动调用一下父类的构造方法(最终实现进程的创建) super().__init__() self.arg = arg def run(self): print("1.子进程id>>{},2父进程id>>{}".format(os.getpid(),os.getppid())) print(self.arg) if __name__ == "__main__": p = MyProcess("我是传进来的参数") p.start() print("3.子进程id>>{},4父进程id>>{}".format(os.getpid(),os.getppid()))
1.2 Lock & Semaphore 进程锁
""" 如果不上锁,执行代码,我们会发现,只有一张票的情况下,3个人却都买到了票: 异步并发,会造成数据混乱! """ import json,time from multiprocessing import Process def w_r_info(mod,dic=None): if mod == "r": with open("12306",mode=mod,encoding='utf-8') as f: dic = json.load(f) return dic else: with open("12306",mode=mod,encoding='utf-8') as f: json.dump(dic,f) def func_12306(name): info = w_r_info("r") print("%s查看了余票,还剩%s张"%(name,info["count"])) if info["count"] > 0 : info["count"] -= 1 time.sleep(1) w_r_info("w",info) print("%s买到了票" % (name)) else:print("%s没有买到票" % (name)) if __name__ == '__main__': lst = ["赵一","钱二","sun3"] for i in lst: p = Process(target=func_12306,args=(i,)) p.start()
1.2.1 Lock
# 一把锁 Lock 改造(模拟12306) import json,time from multiprocessing import Process,Lock def w_r_info(mod,dic=None): if mod == "r": with open("12306",mode=mod,encoding='utf-8') as f: dic = json.load(f) return dic else: with open("12306",mode=mod,encoding='utf-8') as f: json.dump(dic,f) def func_12306(name,lock): info = w_r_info("r") # 读余票 print("%s查看了余票,还剩%s张"%(name,info["count"])) time.sleep(0.5) lock.acquire() # 上锁,后面人进来就是修改过的值 info = w_r_info("r") # 读余票 if info["count"] > 0 : info["count"] -= 1 w_r_info("w",info) print("%s买到了票" % (name)) # lock.release() else:print("%s没有买到票" % (name)) lock.release() # 解锁 if __name__ == '__main__': lock = Lock() # 创建一把锁 lst = ["赵一","钱二","sun3"] for i in lst: p = Process(target=func_12306,args=(i,lock)) p.start()
1.2.2 Semaphore
# 多把锁 import time from multiprocessing import Process,Semaphore def func(n,sem): with sem: time.sleep(1) print("%s在拉屎"%(n)) if __name__ == '__main__': lst = ["赵一","钱二","孙三","李四","周五","吴六","郑七","王八"] sem = Semaphore(1) # 相当于指定多少个人同时拉屎 for i in lst: p = Process(target=func,args=(i,sem)) p.start()
1.3 Event 进程事件
""" # 阻塞事件 : e = Event()生成事件对象e e.wait()动态给程序加阻塞 , 程序当中是否加阻塞完全取决于该对象中的is_set() [默认返回值是False] # 如果是True 不加阻塞 # 如果是False 加阻塞 # 控制这个属性的值 # set()方法 将这个属性的值改成True # clear()方法 将这个属性的值改成False # is_set()方法 判断当前的属性是否为True (默认上来是False) """ # 红绿灯解释进程事件 import time,random from multiprocessing import Process,Event def light(e): print("红灯!") while True: if e.is_set(): # 1,is_set() 默认值是 False 阻塞 time.sleep(2) # 4,等待绿灯2秒,变为红灯 print("红灯!") e.clear() # 5,设置is_set()为 False 阻塞 else: time.sleep(2) # 2,等待红灯2秒 print("绿灯!") e.set() # 3,设置is_set()为True ,放行 def car(e,i): # 格式一定,因为当红灯时,小车进来等待红灯,wait阻塞,当绿灯时继续执行代码,如果反过来,红灯等待,阻塞,当绿灯时,这个程序已经结束了,同行不了 if not e.is_set(): time.sleep(random.uniform(0.3,0.8)) print(i,"等待红灯中") e.wait() print(i,"通行了") if __name__ == '__main__': e = Event() lst= [] p1 = Process(target=light, args=(e,)) p1.daemon = True p1.start() for i in range(10): p2 = Process(target=car,args=(e,i)) time.sleep(1) p2.start() lst.append(p2) for i in lst: p2.join() print("主程序结束")
1.4 Queue 进程队列
# 进程之间的通信 IPC (inter process communication) from multiprocessing import Queue q = Queue() # 创建一个队列对象,可以设置队列长度 q.put(值) # 往队列对象里放值 q.get() # 从队列对象里取值 """ 队列特点: 先进先出,后进后出 1,如果超过了队列的指定长度,在继续存值会出现阻塞 2,队列中如果已经没有数据了,在调用get会发生阻塞 """ q.put_nowait() # 非阻塞版本的put,超出长度后,直接报错 q.get_nowait() # 如果没有值会报错,windows好使,可以使用try抑制报错
1.4.1 进程之间数据共享
from multiprocessing import Process,Queue def func(p): p.put("我爱你") # 子进程放值 if __name__ == '__main__': p = Queue() pro = Process(target=func,args=(p,)) pro.start() pro.join() print(p.get()) # 主进程取值
1.4.1.1 Manager 字典,列表共享数据
from multiprocessing import Manager,Process def func(dic): print(dic) if __name__ == '__main__': m = Manager() dic = m.dict({"name":"bajie"}) Process(target=func,args=(dic,)).start() time.sleep(1) # 共享数据必须慢一点 print(dic,'--------')
1.4.2 生产者与消费者模型
# 消费者模型 def consumer(q,name): while True: food = q.get() if food is None: break time.sleep(random.uniform(0.1,1)) print("%s 吃了一个%s" % (name,food)) # 生产者模型 def producer(q,name,food): for i in range(5): time.sleep(random.uniform(0.1,1)) # 打印生产的数据 print("%s 生产了 %s%s" % (name,food,i)) # 存储生产的数据 q.put(food + str(i)) if __name__ == "__main__": q = Queue() p1 = Process(target=consumer,args=(q,"宋云杰")) p2 = Process(target=producer,args=(q,"马生平","黄瓜")) p1.start() p2.start() # 在生产者生产完所有数据之后,在队列的末尾添加一个None p2.join() q.put(None)
1.4.3 生产者与消费者模型升级版 (JoinableQueue)
from multiprocessing import Process, JoinableQueue import time,random """ put 存储 get 获取 task_done join task_done 和 join 配合使用的 队列中 1 2 3 4 5 put 一次 内部的队列计数器加1 get 一次 通过task_done让队列计数器减1 join函数,会根据队列计数器来判断是阻塞还是放行 队列计数器 = 0 , 意味着放行 队列计数器 != 0 , 意味着阻塞 """ # 1.基本语法 """ jq =JoinableQueue() jq.put("a") print(jq.get()) # 通过task_done让队列计数器减1 jq.task_done() jq.join() print("finish") """ # 2.改造生产者和消费者模型 def consumer(q,name): while True: food = q.get() time.sleep(random.uniform(0.1,1)) print("%s 吃了一个%s" % (name,food)) # 当队列计数器减到0的时,意味着进程队列中的数据消费完毕 q.task_done() # 生产者模型 def producer(q,name,food): for i in range(5): time.sleep(random.uniform(0.1,1)) # 打印生产的数据 print("%s 生产了 %s%s" % (name,food,i)) # 存储生产的数据 q.put(food + str(i)) if __name__ == "__main__": q =JoinableQueue() # 消费者 p1 = Process(target=consumer,args=(q,"宋云杰")) # 生产者 p2 = Process(target=producer,args=(q,"马生平","黄瓜")) # 设置p1消费者为守护进程 p1.daemon = True p1.start() p2.start() # 把所有生产者生产的数据存放到进程队列中 p2.join() # 为了保证消费者能够消费完所有数据,加上队列.join # 当队列计数器减到0的时,放行,不在阻塞,程序彻底结束. q.join() print("程序结束 ... ")
2, 线程的基本使用
""" cpu执行程序的最小单位. 进程与线程的关系: 1,进程是资源,线程是工人 2,一个进程至少有一个线程 """
2.1 创建多线程
from threading import Thread,currentThread currentThread().ident # 获取线程号 def func(): print("当前线程ID:%s"%(currentThread().ident)) if __name__ == '__main__': t = Thread(target=func).start() print("当前线程ID:%s" % (currentThread().ident))
2.1.1 进程与线程谁的速度快?
# 测试线程速度 1000个大概0.5秒 import time from threading import Thread,currentThread def func(): print("当前线程ID:%s"%(currentThread().ident)) if __name__ == '__main__': lst = [] a = time.time() for i in range(1000): t = Thread(target=func) t.start() lst.append(t) for i in lst: i.join() b = time.time() print("用了{}秒".format(b-a)) # 测试进程速度 1000个大概5秒 from multiprocessing import Process def func(): print("当前线程ID:%s"%(currentThread().ident)) if __name__ == '__main__': lst = [] a = time.time() for i in range(1000): t = Process(target=func) t.start() lst.append(t) for i in lst: i.join() b = time.time() print("用了{}秒".format(b-a))
2.1.2 线程共享进程资源
from threading import Thread num = 520 def func(): global num num -= 1 if __name__ == '__main__': t = Thread(target=func) t.start() t.join() print(num) # 519
2.1.3 线程的相关函数
import time from threading import Thread,currentThread,enumerate from multiprocessing import Process def func(): time.sleep(1) if __name__ == '__main__': t = Thread(target=func) t.start() print(t.is_alive()) # 获取线程存活状态 t.setName("爬树据") # 设置线程名字 print(t.getName()) # 获取线程名字 print(currentThread().ident) # 获取线程ID print(enumerate()) # 获取存活线程的列表
2.2 守护线程
# ### 守护线程 : 等待所有线程全部执行完毕之后,自己在终止,守护所有线程 from threading import Thread import time def func1(): while True: time.sleep(0.5) print("我是func1") def func2(): print("我是func2 start ... ") time.sleep(3) print("我是func2 end ... ") def func3(): print("我是func3 start ... ") time.sleep(5) print("我是func3 end ... ") if __name__ == "__main__": t1 = Thread(target=func1) t2 = Thread(target=func2) t3 = Thread(target=func3) # 在start调用之前,设置线程为守护线程 t1.setDaemon(True) t1.start() t2.start() t3.start() print("主线程执行结束 .... ")
2.3 线程中的数据安全
2.3.1 Lock锁
# 一把锁 from threading import Thread,Lock n = 0 def add(lock): global n with lock: for i in range(1000000): n += 1 def rem(lock): global n with lock: for i in range(1000000): n -= 1 if __name__ == "__main__": lst = [] lock = Lock() t1 = Thread(target = add,args=(lock,)) t2 = Thread(target = rem,args=(lock,)) t1.start() t2.start() lst.append(t1) lst.append(t2) for i in lst: i.join() print(n)
2.3.2 Semaphore 信号量
# 信号量 Semaphore """ 再创建线程的时候是异步创建 在执行任务时,遇到Semaphore进行上锁,会变成同步程序 """ from threading import Semaphore , Thread import time def func(i,sm): with sm: print(i) time.sleep(3) if __name__ == "__main__": # 支持同一时间,5个线程上锁 sm = Semaphore(5) for i in range(20): Thread(target=func,args=(i,sm)).start()
2.3.3 递归锁 Rlock
# 语法死锁 from threading import Lock lock = Lock() lock.acquire() lock.acquire() lock.acquire() lock.release() # 逻辑死锁 多把锁, # 递归锁 # (3) 递归锁的使用 """ 递归锁专门用来解决这种死锁现象,临时用于快速解决线上项目发生阻塞死锁问题的 """ rlock = RLock() rlock.acquire() rlock.acquire() rlock.acquire() rlock.acquire() print(112233) rlock.release() rlock.release() rlock.release() rlock.release() print("程序结束 ... ") # 依然可以执行
2.4 Event 事件
from threading import Thread # 模拟链接远程数据库 def check(e): # 用一些延迟来模拟检测的过程 time.sleep(random.randrange(1,6)) # 1 2 3 4 5 # time.sleep(1) print("开始检测链接用户的合法性") e.set() def connect(e): sign = False for i in range(1,4): # 1 2 3 e.wait(1) if e.is_set(): print("数据库链接成功 ... ") sign = True break else: print("尝试链接数据库第%s次失败 ... " % (i)) if sign == False: raise TimeoutError e = Event() Thread(target=connect,args=(e,)).start() Thread(target=check,args=(e,)).start()
2.5 线程队列 PriorityQueue
from queue import Queue # Queue (先进先出,后进后出) from queue import LifoQueue # LifoQueue(先进后出,后进先出) 方法与进程队列相同 put() get() put_nowait() get_nowait() from queue import PriorithQueue # 优先级队列 """ 1,按照ascii码排序输出, 2,不可以存放不同的数据类型 """
3, 池 Pool
3.1 进程池 & 线程池
# 引入模块 from concurrent.futures import ProcessPoolExecutor # 进程池 from concurrent.futures import ThreadPoolExecutor # 线程池 from threading import current_thread print(current_thread().ident) # 线程号 import os print(os.getpid()) # 进程号 print(os.cpu_count()) # cpu逻辑数 # 进程池的基本使用 def func(i): print(i,"start...进程号:",os.getpid()) time.sleep(0.1) print(i,"end.....进程号:",os.getpid()) return os.getpid() if __name__ == '__main__': setvar = set() lst = [] # 创建进程池,默认为cpu逻辑数, p = ProcessPoolExecutor(30) # 异步提交任务 ----> p.submit(func,i) for i in range(30): # 可以拿到返回值,是一个对象 res = p.submit(func,i) lst.append(res) for i in lst: # result() 获取返回值,同步阻塞 setvar.add(i.result()) print(setvar,len(setvar)) # 线程池的基本使用 def func(i): print(i,"start...线程号:",current_thread().ident) time.sleep(1) print(i,"end.....进程号:",os.getpid()) # time.sleep(1) print(i, "end.....进程号:%s,线程号:%s"%(os.getpid(),current_thread().ident) ) return current_thread().ident if __name__ == '__main__': setvar = set() lst = [] # 创建进程池,默认为cpu逻辑数, p = ThreadPoolExecutor(60) # 异步提交任务 ----> p.submit(func,i) for i in range(60): # 可以拿到返回值,是一个对象 res = p.submit(func,i) lst.append(res) for i in lst: # result() 获取返回值,同步阻塞 setvar.add(i.result()) print(setvar,len(setvar))
3.1.1 线程池 map 的使用
def func(i): time.sleep(5) print(i, "end.....进程号:%s,线程号:%s"%(os.getpid(),current_thread().ident) ) return current_thread().ident if __name__ == '__main__': setvar = set() lst = [] # 创建线程池,(最大允许并发5个线程) p = ThreadPoolExecutor(5) it = p.map(func,range(20)) p.shutdown() for i in lst: print(i)
3.1.2 回调函数
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor from threading import current_thread as cthread import os,time # 返回值对象.add_done_callback(回调函数) def func1(i): print("process start ... " , os.getpid()) time.sleep(1) print("process end ... ", i) return "*" * i def func2(i): print("thread start ... " , cthread().ident) time.sleep(1) print("thread end ... ", i) return "*" * i def call_back1(obj): print("<===回调函数callback进程号===>" , os.getpid()) print(obj.result()) def call_back2(obj): print("<===回调函数callback线程号===>" ,cthread().ident) print(obj.result()) # (1) 进程池的回调函数: 由主进程执行调用完成的 if __name__ == "__main__": p = ProcessPoolExecutor() for i in range(1,11): res = p.submit(func1,i) # print(res.result()) res.add_done_callback(call_back1) # self.func(func2) p.shutdown() print("主进程执行结束 ... " , os.getpid()) # (2) 线程池的回调函数 : 由当前子线程调用完成的 if __name__ == "__main__": tp = ThreadPoolExecutor(5) for i in range(1,11): res = tp.submit(func2,i) res.add_done_callback(call_back2) tp.shutdown() print("主线程执行结束 ... " , cthread().ident)
4, 协程
# 能够在一个线程中多个任务(函数)之间来回切换,那么每个任务都是协程 # 线程与协程的区别: 线程:操作系统切换,开销大,操作系统不可控,让操作系统的压力的大,操作系统对IO(细微的阻塞)操作更加敏感 协程:prthon代码切换,开销小,用户可控,不会增加操作系统的压力。在用户层面,对细微的阻塞的感知相对较低(open,print)
4.1 协程基本操作
import time,gevent def func(): print("1") time.sleep(2) # 操作系统阻塞,协程不识别,不会切换任务 print("2") g = gevent.spawn(func) # 创建协程任务 gevent.sleep(3) # 协程阻塞 print(3) # 识别所有阻塞,底层覆盖 from gevent import monkey monkey.patch_all() # 阻塞所有任务 g.joinall(list(协程对象)) # 查看g协程任务返回值 g.value