我的python学习之路-进程/线程/协程
本节内容:
一、进程
二、线程
三、线程队列、线程池、进程池、回调函数
四、 协程
一、进程
1、进程语法与特性
(1)进程基本语法
def func(): print("1.当前进程子进程id:{} 2.父进程id:{}".format(os.getpid(),os.getppid()) , "<子11>") if __name__ == "__main__": # 1. 创建子进程,返回进程对象 p = Process(target=func) # 2. 调用子进程 p.start() # 在主进程中,打印进程id号 print("1.当前进程子进程id:{} 2.父进程id:{}".format(os.getpid(),os.getppid()),"<主22>")
(2)创建带有参数的进程
def func(n): for i in range(1,n+1): print( i , "1.当前进程子进程id:{} 2.父进程id:{}".format(os.getpid(),os.getppid()) , "<子11>") # 为了兼容linux + windows 下面这句话必须加上; if __name__ == "__main__": n = 10 # target = 指定执行的任务 args = 参数元组 p = Process(target=func, args=(n,)) p.start() for i in range(1,n+1): print("*" * i )
(3)进程之间的数据彼此隔离
num = 10 def func(): global num num += 1 print(num,"子进程") if __name__ == "__main__": p = Process(target=func) p.start() time.sleep(3) print(num,"主进程")
(4)进程的异步性
1.多个进程在执行时,是异步并发程序,因为cpu的调度策略问题,不一定先执行谁后执行谁
整体的方向,哪个进程更快的分配好资源就先执行;阻塞态就立刻切换
cpu在执行任务时,如果遇到了阻塞态,会立刻切换到就绪态的任务进行执行
让整体代码的执行效率最大化
2.主进程默认会等待所有的子进程执行完毕之后,在关闭程序,释放资源
孤儿进程:主进程执行结束了,子进程还在运行,占用空间资源,称为孤儿进程
僵尸进程:子进程执行结束了,主进程没有获取子进程的退出状态,进程释放占用的内存空间
还持续的在内存中保留,称为僵尸进程
def func(n): print(n , "1.当前进程子进程id:{} 2.父进程id:{}".format(os.getpid(),os.getppid()) , "<子11>") if __name__ == "__main__": for i in range(10): p = Process(target=func, args=(i,)) p.start() print("主进程执行结束 ... " , os.getpid() )
2、join使用
语法: 进程.join()
意义:必须等待当前这个子进程执行结束之后,再去执行下面的代码
作用:用来同步子父进程;
(1)join的基本使用
def func(): print("发送第一个短信 : honey 你在么~ ") if __name__ == "__main__": p = Process(target=func) p.start() # time.sleep(0.01) p.join() print("发送第二个短信 : 给我点钱,我要买ps5~ ")
(2)多进程场景
1 def func(n): 2 print("发送第一个短信 : honey 你在么~ , 数量{}".format(n)) 3 4 if __name__ == "__main__": 5 lst = [] 6 for i in range(1,5): 7 p = Process(target=func,args=(i,)) 8 p.start() 9 # 写法一 : 这种写法会直接导致程序由异步并发,变成同步程序; 10 # p.join() 11 # 写法二 : 先异步创建多个进程对象 [异步程序] 12 lst.append(p) 13 14 # 把所有的进程放到列表中,通过调用join进行管理 15 # 同步程序 16 for i in lst: 17 i.join() 18 19 print("发送最后一个短信 : 球球了,给我买个ps5把")
3、使用自定义进程类,创建进程
(1) 基本写法
import os class MyProcess(Process): def run(self): print("1.当前进程子进程id:{} 2.父进程id:{}".format(os.getpid(),os.getppid()) , "<子11>") if __name__ == "__main__": p = MyProcess() p.start() print("主进程 id{}".format(os.getpid()))
(2) 带有参数的自定义进程类
class MyProcess(Process): def __init__(self,name): # 手动调用父类的构造方法,完成成员初始化 super().__init__() self.name = name def run(self): print("1.当前进程子进程id:{} 2.父进程id:{}".format(os.getpid(),os.getppid()) , "<子11>") print(self.name) if __name__ == "__main__": p = MyProcess("我是个参数 ... ") p.start() print("主进程 id{}".format(os.getpid()))
4、守护进程
守护进程守护的是主进程
当主进程的所有代码执行结束之后,会立刻杀死守护进程
(1) 基本语法
from multiprocessing import Process import time def func(): print("当前任务 启动执行 .... ") print("当前任务 结束执行 .... ") if __name__ == "__main__": p = Process(target=func) # 设置守护进程,在start开始之前. p.daemon = True p.start() print("主进程代码执行结束 ... ")
(2) 多个子进程的场景
1 def func1(): 2 print("当前任务 启动执行 .... ") 3 print("当前任务 结束执行 .... ") 4 5 def func2(): 6 count = 1 7 while True: 8 print("*" * count) 9 time.sleep(0.1) 10 count += 1 11 12 if __name__ == "__main__": 13 p1 = Process(target=func1) 14 p2 = Process(target=func2) 15 16 # 把p2变成守护进程 17 p2.daemon = True 18 19 p1.start() 20 p2.start() 21 22 # time.sleep(1) 23 print("主进程代码执行结束 ... ")
(3) 守护进程用途 : 监控报活
1 def alive(): 2 while True: 3 print("10号服务器向总监控服务器报活") 4 # 间隔一段时间,报活一次 5 time.sleep(1) 6 7 def func(): 8 while True: 9 try: 10 print("10号服务器负责抗住1万用户量的并发访问 ... ") 11 12 # 三秒之后崩溃 13 time.sleep(3) 14 15 # 主动抛出执行错误的异常 16 raise RuntimeError 17 except: 18 print("10号服务器挂了... 进来维护") 19 break 20 21 if __name__ == "__main__": 22 # 当前进程负责报活 23 p1 = Process(target=alive) 24 # 当前进程抗主用户并发 25 p2 = Process(target=func) 26 27 p1.daemon = True 28 p1.start() 29 p2.start() 30 31 # 必须等待2号任务执行完毕之后,在放行 32 p2.join() 33 34 print("主进程执行结束 ... ")
5、锁 lock 互斥锁
上锁和解锁是一对
不能只上锁不解锁,会发生死锁现象(阻塞)
只有在解锁状态下,下一个进程才有机会上锁
(1)基本用法
from multiprocessing import Lock,Process import json,random,time # 创建一把锁 lock = Lock() # 上锁 lock.acquire() # ... ... .... # 解锁 lock.release()
(2)12306抢票
1 # 1.读写数据的票数 2 def wr_info(sign,dic=None): 3 if sign == "r": 4 # 读取票数 5 with open("data.txt",mode="r",encoding="utf-8") as fp: 6 dic = json.load(fp) 7 return dic 8 elif sign == "w": 9 # 写入票数 10 with open("data.txt",mode="w",encoding="utf-8") as fp: 11 json.dump(dic,fp) 12 13 14 # res = wr_info("r") 15 # print(res , type(res)) 16 # dic = {"count":0} 17 # res = wr_info("w",dic) 18 19 # 2.执行抢票方法 20 def get_ticket(person): 21 # 查看数据库票数 22 dic = wr_info("r") 23 24 # 模拟网络延迟 25 time.sleep(random.uniform(0.1,0.8)) 26 27 # 判断票数是否能抢 28 if dic["count"] > 0 : 29 print("恭喜你~{}抢票成功~".format(person)) 30 # 抢到票数之后,数量上减1 31 dic["count"] -= 1 32 # 更新数据中的票数 33 wr_info("w",dic) 34 else: 35 print("抱歉~{}没有抢到票~".format(person)) 36 37 38 def main(person,lock): 39 40 # 看一看有几张票 41 dic = wr_info("r") 42 print("{}查看票数剩余数量为:{}".format(person,dic["count"])) 43 44 # 上锁(保证同一时间,只有一个进程修改数据) 45 lock.acquire() 46 # 真正开始下单 47 get_ticket(person) 48 # 解锁(解锁之后,别的进程才有机会进来改数据) 49 lock.release() 50 51 if __name__ == "__main__": 52 # 创建一个锁对象 53 lock = Lock() 54 55 lst = ["尤佳","黄常见","张银","孙翔宇","家营和","耿择时","薛宇健","朱培峰","李琦","于盛林"] 56 for i in lst: 57 p = Process(target=main,args=(i,lock)) 58 p.start()
6、信号量 Semaphore
本质上是锁,同一时间,多个进程上多把锁,可以控制上锁的数量
(1)基本用法
from multiprocessing import Semaphore , Process import time,random """ # 同一时间允许多个进程上4把锁 sem = Semaphore(4) # 上锁 sem.acquire() # .... # 解锁 sem.release()
(2)小练习
1 def singsong_gefang(person,sem): 2 # 上锁 3 sem.acquire() 4 # 唱歌 5 print("{}进入歌房唱歌 ... ".format(person)) 6 # 唱一段时间 7 time.sleep(random.randrange(3,9)) 8 # 离开歌房 9 print("{}离开歌房不唱了 ... ".format(person)) 10 # 解锁 11 sem.release() 12 13 if __name__ == "__main__": 14 15 sem = Semaphore(4) 16 lst = ["王生福","敬旭阳","小黄人","梁瑞卿","郝建康","杨特","白金鸽","王钊","付家庆","何子豪","菲菲"] 17 for i in lst: 18 p = Process(target=singsong_gefang ,args=(i,sem)) 19 p.start()
7、事件 (Event)
阻塞事件 :
e = Event()生成事件对象e
e.wait()动态给程序加阻塞 , 程序当中是否加阻塞完全取决于该对象中的is_set() [默认返回值是False]
如果是True 不加阻塞
如果是False 加阻塞
控制这个属性的值
set()方法 将这个属性的值改成True
clear()方法 将这个属性的值改成False
is_set()方法 判断当前的属性是否为True (默认上来是False)
(1)基本语法
1 # 生成事件对象e 2 e = Event() 3 # is_set方法获取的值为False(默认值) 4 """False => 阻塞 True => 非阻塞""" 5 print(e.is_set()) 6 e.wait() 7 print("代码执行了 .... ")
1 from multiprocessing import Process , Event 2 import time,random 3 # 生成事件对象e 4 e = Event() 5 # 将这个属性的值改成True 6 e.set() 7 e.wait() 8 print("代码执行了1 .... ") 9 10 e.clear() 11 e.wait() 12 print("代码执行了2 .... ")
1 # 生成事件对象e 2 e = Event() 3 # 代码最多阻塞3秒在放行... 4 e.wait(3) 5 print("代码执行了3 .... ")
(2)模拟红绿灯
1 from multiprocessing import Process , Event 2 import time,random 3 4 def traffic_light(e): 5 print("红灯亮") 6 while True: 7 if e.is_set(): # True 8 # 绿灯状态 9 time.sleep(1) 10 # 亮1秒 , 绿灯 -> 红灯 11 print("红灯亮") 12 e.clear() 13 else: 14 # 红灯状态 15 time.sleep(1) 16 # 亮1秒 , 红灯 -> 绿灯 17 print("绿灯亮") 18 e.set() 19 # e = Event() 20 # traffic_light(e) 21 22 # 模拟小车 23 """ 24 e.is_set() => True 绿灯 25 e.is_set() => False 红灯 26 """ 27 def car(e,i): 28 # 当红灯时,小车阻塞 29 if e.is_set() == False : # e.is_set => False => not False 30 print("当前小车car{}在等待 ... ".format(i)) 31 e.wait() 32 33 # 当绿灯时,小车放行 34 print("当前小车car{} 放行了 ... ".format(i)) 35 36 37 # 全国交通灯 38 """ 39 if __name__ == "__main__": 40 e = Event() 41 # 创建交通灯 42 p1 = Process(target=traffic_light , args=(e,)) 43 p1.start() 44 45 # 创建小车进程 46 for i in range(1,21): 47 time.sleep(random.randrange(3)) 48 p2 = Process(target=car,args=(e,i)) 49 p2.start() 50 """ 51 # 包头交通灯 (在车全部跑完的时,把交通灯炸掉,省点电) 52 if __name__ == "__main__": 53 lst = [] 54 e = Event() 55 # 创建交通灯 56 p1 = Process(target=traffic_light , args=(e,)) 57 58 # 把交通灯这个进程变成守护进程 59 p1.daemon = True 60 p1.start() 61 62 # 创建小车进程 63 for i in range(1,21): 64 time.sleep(random.randrange(3)) 65 p2 = Process(target=car,args=(e,i)) 66 lst.append(p2) 67 p2.start() 68 69 70 71 # 必须让所有的小车跑完,在杀死交通灯进程 72 for i in lst: 73 i.join() 74 75 print("关闭交通灯")
8、进程队列
特点:前进先出 , 后进后出
(1)基本语法
1 q = Queue() 2 print(q) 3 # (1)put() 存放 4 q.put(100) 5 q.put(200) 6 q.put(300) 7 8 # (2)get() 获取 9 """ 10 res = q.get() 11 res = q.get() 12 res = q.get() 13 # 在获取不到任何数据时候,直接阻塞 14 res = q.get() 15 print(res) 16 """ 17 18 # (3)get_nowait() 拿不到报异常 19 """get_nowait 目前版本存在兼容性问题,时好时坏,[linux目前不支持]""" 20 """ 21 res = q.get_nowait() 22 res = q.get_nowait() 23 res = q.get_nowait() 24 # res = q.get_nowait() error 25 print(res) 26 """ 27 28 # (4)put_nowait() 非阻塞版本的put 29 '''设定队列的长度为3 , 最多放3个数据''' 30 q2 = Queue(3) 31 # 如果队列已经满了,在存储数据直接报错 32 # q2.put_nowait(1000) 33 # q2.put_nowait(2000) 34 # q2.put_nowait(3000) 35 # q2.put_nowait(4000) 36 37 try: 38 q2.put_nowait(1000) 39 q2.put_nowait(2000) 40 q2.put_nowait(3000) 41 q2.put_nowait(4000) 42 except: 43 pass 44 45 # 如果队列已经满了,在存储数据直接阻塞 46 # q2.put(1) 47 # q2.put(2) 48 # q2.put(3) 49 # q2.put(4)
(2)IPC通信
1 def func(q): 2 # 2.子进程获取主进程存放的数据 3 res = q.get() 4 print(res , "<子进程>") 5 # 3.子进程存储数据 6 q.put("王娟娟") 7 8 if __name__ == "__main__": 9 q3 = Queue() 10 p = Process(target=func,args=(q3,)) 11 p.start() 12 13 # 1.主进程存储数据 14 q3.put("荷叶") 15 16 # 为了让主进程获取到数据,必须等待子进程执行完毕之后,在向下执行; 17 p.join() 18 19 # 4.让主进程获取数据 20 res = q3.get() 21 print(res,"<主进程>")
9、生产者消费者模型
爬虫
1号进程负责向队列中存储数据,整体存储响应的一篇篇的小说,文章...
2号进程负责从队列中获取数据,匹配文章标题,时间,作者 ...
1号进程可以理解成生产者
2号进程可以理解成消费者
从程序上看
生产者负责存储数据 (put)
消费者负责获取数据 (get)
理想的生产者消费者模型:
1.生产多少,消费多少
2.生产数据的速度与消费数据的速度相对一致
(1)基础版-生产者消费者模型
1 from multiprocessing import Queue,Process 2 import time,random 3 # 生产者 4 def producer(q,name,food): 5 for i in range(1,6): 6 time.sleep(random.uniform(0.1,1)) 7 res = "第{}碗{}".format(i,food) 8 print( "{}生产了{}".format(name,res) ) 9 # 存储白米粥到队列中 10 q.put(res) 11 12 # 消费者 13 def consumer(q,name): 14 while True: 15 time.sleep(random.uniform(0.1,1)) 16 # 获取队列中的白米粥 17 food = q.get() 18 print("{}吃了{}".format(name,food)) 19 20 21 if __name__ == "__main__": 22 q = Queue() 23 # 生产者 24 p1 = Process( target = producer , args = (q , "张银" , "白米粥")) 25 # 消费者 26 p2 = Process( target = consumer , args = (q , "郝建康" )) 27 28 p1.start() 29 p2.start()
(2)升级版 (消费者是死循环,要给与终止)
1 from multiprocessing import Queue,Process 2 import time,random 3 # 生产者 4 def producer(q,name,food): 5 for i in range(1,6): 6 time.sleep(random.uniform(0.1,1)) 7 res = "第{}碗{}".format(i,food) 8 print( "{}生产了{}".format(name,res) ) 9 # 存储白米粥到队列中 10 q.put(res) 11 12 # 消费者 13 def consumer(q,name): 14 while True: 15 # 获取队列中的白米粥 16 food = q.get() 17 if food is None: 18 break 19 time.sleep(random.uniform(0.1,1)) 20 print("{}吃了{}".format(name,food)) 21 22 if __name__ == "__main__": 23 q = Queue() 24 # 生产者 25 p1 = Process( target = producer , args = (q , "张银" , "白米粥")) 26 # 消费者 27 p2 = Process( target = consumer , args = (q , "郝建康" )) 28 29 p1.start() 30 p2.start() 31 32 # 生产结束的最后放入None 关键字 代表生产结束 33 p1.join() 34 q.put(None)
1 # 消费者 2 def consumer(q,name): 3 while True: 4 # 获取队列中的白米粥 5 food = q.get() 6 if food is None: 7 break 8 time.sleep(random.uniform(0.1,1)) 9 print("{}吃了{}".format(name,food)) 10 11 if __name__ == "__main__": 12 q = Queue() 13 # 生产者 14 p1 = Process( target = producer , args = (q , "张银" , "白米粥")) 15 p1_1 = Process( target = producer , args = (q , "王永捐" , "银耳粥")) 16 17 # 消费者 18 p2 = Process( target = consumer , args = (q , "郝建康" )) 19 p2_2 = Process( target = consumer , args = (q , "李琦" )) 20 21 p1.start() 22 p1_1.start() 23 p2.start() 24 p2_2.start() 25 26 # 1号生产者 , 生产结束的最后放入None 关键字 代表生产结束 27 p1.join() 28 # 2号生产者 , 生产结束的最后放入None 关键字 代表生产结束 29 p1_1.join() 30 31 32 33 # 1号生产者 放入None 34 q.put(None) 35 # 2号生产者 放入None 36 q.put(None)
10、JoinableQueue 队列
put 存放 计数器属性值+1
get 获取
task_done 计数器属性值-1
join 配合task_done来使用(负责加阻塞)
put 一次数据, 队列的内置计数器+1
task_done 一次数据 队列的内置计数器-1
当内置计数器值为0时 , join就会放行;
计数器值=0
队列.join() 放行
计数器值非0
队列.join() 阻塞
(1)、基本用法
1 from multiprocessing import JoinableQueue 2 jq = JoinableQueue() 3 # 队列计数器+1 4 jq.put("朱培峰") 5 # 队列计数器+1 6 jq.put("薛宇健") 7 8 print(jq.get()) 9 # 队列计数器-1 10 jq.task_done() 11 12 print(jq.get()) 13 # 队列计数器-1 14 jq.task_done() 15 16 jq.join() 17 print("代码执行了 .... ")
(2)对生产者消费者模型进行改造
1 from multiprocessing import JoinableQueue,Process 2 import time,random 3 # 生产者 4 def producer(q,name,food): 5 for i in range(1,6): 6 time.sleep(random.uniform(0.1,1)) 7 res = "第{}碗{}".format(i,food) 8 print( "{}生产了{}".format(name,res) ) 9 # 存储白米粥到队列中 10 # 内置计数器 +1 => 5 11 q.put(res) 12 13 # 消费者 14 def consumer(q,name): 15 while True: 16 time.sleep(random.uniform(0.1,1)) 17 # 获取队列中的白米粥 18 food = q.get() 19 print("{}吃了{}".format(name,food)) 20 # 内置计数器 -1 => 0 21 q.task_done() 22 23 24 if __name__ == "__main__": 25 jq = JoinableQueue() 26 # 生产者 27 p1 = Process( target = producer , args = (jq , "张银" , "白米粥")) 28 # 消费者 29 p2 = Process( target = consumer , args = (jq , "郝建康" )) 30 p2.daemon = True 31 32 p1.start() 33 p2.start() 34 35 # 必须等到生产者全部生产完毕之后,在放行(带来的问题,消费者消费不完全) 36 p1.join() 37 # 必须让消费者全部吃完 38 jq.join() 39 print( " 主进程执行结束 .." )
11、 Manager (list 列表 dict 字典)
1 from multiprocessing import Process, Manager , Lock 2 3 def work(data,lock): 4 """ 5 # 共享字典 6 lock.acquire() 7 data["count"] +=1 8 lock.release() 9 """ 10 # 共享列表 11 """ 12 lock.acquire() + lock.release() => with 自动完成 13 """ 14 with lock: 15 data[0] -= 1 16 17 if __name__ == "__main__": 18 lst = [] 19 lock = Lock() 20 m = Manager() 21 # 创建一个多进程之间共享数据的字典 22 # data = m.dict( {"count":0} ) 23 # 创建一个多进程之间共享数据的列表 24 data = m.list( [100,200,300] ) 25 # {'count': 1000} <class 'multiprocessing.managers.DictProxy'> 26 print(data , type(data)) 27 28 29 """ 进程数超过1000 , 电脑蓝屏 , 谨慎使用 """ 30 for i in range(10): 31 p = Process(target=work,args=(data,lock)) 32 p.start() 33 lst.append(p) 34 35 # 必须等待所有子进程执行完毕之后,在放行,打印最后数据(否则直接报错) 36 for i in lst: 37 i.join() 38 39 print(data)
二 、线程
1、线程的基本使用及特性
进程是系统资源分配的最小单位
线程是计算机中调度的最小单位
一个进程里至少一个主线程;
(1)一个进程里面包含多个线程,线程之间是异步并发操作;
from threading import Thread import time , random , os def func(i): time.sleep(random.uniform(0.1,0.9)) print(i, "当前进程号:{}".format(os.getpid()) ) if __name__ == "__main__": for i in range(10): t = Thread(target=func,args=(i,)) t.start() print(os.getpid())
(2) 多进程和多线程谁的速度快? 多线程
1 def func(i): 2 print("当前进程号:{} , 参数{}".format(os.getpid(),i)) 3 4 if __name__ == "__main__": 5 """ 6 lst = [] 7 starttime = time.time() 8 for i in range(500): 9 t = Thread(target=func,args=(i,)) 10 t.start() 11 lst.append(t) 12 13 for i in lst: 14 i.join() 15 16 endtime = time.time() 17 print("运行时间{}".format(endtime - starttime )) # 0.09600567817687988 18 """ 19 20 lst = [] 21 starttime = time.time() 22 for i in range(500): 23 p = Process(target=func,args=(i,)) 24 p.start() 25 lst.append(p) 26 27 for i in lst: 28 i.join() 29 30 endtime = time.time() 31 print("运行时间{}".format(endtime - starttime )) # 运行时间17.507001399993896
(3) 多线程之间的数据彼此共享
1 num = 1000 2 lst = [] 3 def func(): 4 global num 5 num -= 1 6 7 for i in range(1000): 8 t = Thread(target=func) 9 t.start() 10 lst.append(t) 11 12 for i in lst: 13 i.join() 14 15 print(num)
2、用类自定义线程
1 from threading import Thread 2 import os , time 3 class MyThread(Thread): 4 5 def __init__(self,name): 6 # 必须手动调用一下父类的构造芳芳 7 super().__init__() 8 # 添加参数到成员属性name 当中 9 self.name = name 10 11 def run(self): 12 print("当前进程号:{} , 参数{}".format(os.getpid(),self.name)) 13 14 if __name__ == "__main__": 15 t = MyThread("我是石磊") 16 t.start() 17 18 # 用来同步主和子线程的 19 # print(t) 20 t.join() 21 print("主线程执行结束 ... ")
3.线程相关属性
线程.is_alive() 检测线程是否仍然存在
线程.setName() 设置线程名字
线程.getName() 获取线程名字
1.currentThread().ident 查看线程id号
2.enumerate() 返回目前正在运行的线程列表
3.activeCount() 返回目前正在运行的线程数量
1 def func(): 2 time.sleep(3) 3 4 """ 5 if __name__ == "__main__": 6 t = Thread(target=func) 7 t.start() 8 # 检测线程是否仍然存在 9 res = t.is_alive() 10 print(t) 11 print(res) 12 # 获取线程名字 13 print(t.getName()) 14 # 设置线程名字 15 t.setName("下载线程") 16 print(t.getName()) 17 """ 18 19 from threading import currentThread 20 from threading import enumerate 21 from threading import activeCount 22 23 def func(): 24 time.sleep(3) 25 print("当前子线程的线程号是{},进程号是{}".format( currentThread().ident , os.getpid() ) ) 26 # print(t) 27 28 if __name__ == "__main__": 29 ''' 30 t = Thread(target=func) 31 t.start() 32 print("当前主线程的线程号是{},进程号是{}".format( currentThread().ident , os.getpid() ) ) 33 # print(t) 34 ''' 35 for i in range(10): 36 t = Thread(target=func) 37 t.start() 38 39 # 2.返回目前正在运行的线程列表 40 lst = enumerate() 41 # MainThread, Thread-1 Thread-2 .... 42 print(lst , len(lst)) 43 # 3.activeCount() 返回目前正在运行的线程数量 (了解) 44 # print(activeCount()) 45
4.线程的缺陷
GIL:
全局解释器锁
效果:
同一时间,一个进程下的多个线程只能被一个cpu执行,不能实现线程的并行操作
原因:
1.历史原因
2.python是解释型语言
改善方法:
1.用多进程间接实现线程的并行
2.换一个Pypy,Jpython解释器
目前来看,还不能根本解决;
对于io密集型任务,python 绰绰有余.
5、守护线程
等待所有线程全部执行完毕之后,自己在终止程序;守护所有线程
1 from threading import Thread 2 import time 3 def func1(): 4 while True: 5 time.sleep(1) 6 print("我是 执行任务1 ... ") 7 8 def func2(): 9 print("我是 执行任务2 ... start ") 10 time.sleep(5) 11 print("我是 执行任务2 ... end ") 12 13 def func3(): 14 print("我是 执行任务3 ... start ") 15 time.sleep(8) 16 print("我是 执行任务3 ... end ") 17 18 if __name__ == "__main__": 19 20 t1 = Thread(target=func1) 21 t2 = Thread(target=func2) 22 t3 = Thread(target=func3) 23 24 # 给t1这个线程设置为守护线程 25 t1.setDaemon(True) 26 27 28 t1.start() 29 t2.start() 30 t3.start() 31 32 print("主线程执行结束 ... ")
6、线程的数据安全
1 from threading import Thread , Lock 2 import time 3 total = 0 4 # 尽量降低换锁的频率,提高代码整体的执行效率 5 def func1(lock): 6 # 加锁方法1 7 global total 8 lock.acquire() 9 for i in range(100000): 10 total += 1 11 lock.release() 12 13 def func2(lock): 14 # 加锁方法2 15 global total 16 with lock: 17 for i in range(100000): 18 total -= 1 19 20 21 22 if __name__ == "__main__": 23 lst = [] 24 lock = Lock() 25 startime = time.time() 26 for i in range(10): 27 # 创建10个子线程 +1 28 t1 = Thread(target=func1,args=(lock,)) 29 t1.start() 30 31 # 创建10个子线程 -1 32 t2 = Thread(target=func2,args=(lock,)) 33 t2.start() 34 35 lst.append(t1) 36 lst.append(t2) 37 38 for i in lst: 39 i.join() 40 41 endtime = time.time() 42 43 44 print("主线程执行结束了 , total的结果是{}".format(total)) 45 # 所用的时间是3.6152114868164062 46 print("所用的时间是{}".format(endtime - startime))
7、Semaphore (线程信号量)
1 from threading import Semaphore , Thread 2 import time,random 3 4 def func(i,sem): 5 time.sleep(random.uniform(0.1,0.9)) 6 """ 7 with 语法 自动上锁,自动解锁; 8 """ 9 with sem: 10 print("{}号病人,你在治疗痔疮".format(i)) 11 time.sleep(random.uniform(3,9)) 12 print("{}号病人,一瘸一拐的走出了房间".format(i)) 13 14 15 if __name__ == "__main__": 16 sem = Semaphore(4) 17 for i in range(8): 18 Thread(target=func,args=(i,sem)).start() 19 20 print(" 主线程执行结束 ... ")
8、互斥锁 _死锁 _递归锁
(1) 语法上的死锁
只上锁,不解锁是死锁 , 程序阻塞
1 from threading import Thread , Lock , RLock 2 import time 3 # 场景一 4 """ 5 lock = Lock() 6 lock.acquire() 7 # lock.release() 8 lock.acquire() 9 print("代码执行了...") 10 # lock.release() 11 """ 12 # 场景二 注意两把不同的锁同时上锁,可能造成逻辑死锁. 13 """ 14 lock1 = Lock() 15 lock2 = Lock() 16 lock1.acquire() 17 lock2.acquire() 18 print("代码执行了 ... ") 19 lock2.release() 20 lock1.release() 21 """
(2) 逻辑上的死锁
1 from threading import Thread , Lock , RLock 2 import time 3 noodle_lock = Lock() 4 kuaizi_lock = Lock() 5 6 def eat1(name): 7 noodle_lock.acquire() 8 print("{}抢到了面条...".format(name)) 9 kuaizi_lock.acquire() 10 print("{}抢到了筷子...".format(name)) 11 12 print("开始吃面条~ 老北京杂酱面 ") 13 time.sleep(0.5) 14 15 kuaizi_lock.release() 16 print("{}放下了筷子...".format(name)) 17 noodle_lock.release() 18 print("{}放下了面条...".format(name)) 19 20 # eat1("郝建康") 21 22 def eat2(name): 23 24 kuaizi_lock.acquire() 25 print("{}抢到了筷子...".format(name)) 26 noodle_lock.acquire() 27 print("{}抢到了面条...".format(name)) 28 29 print("开始吃面条~ 老北京杂酱面 ") 30 time.sleep(0.5) 31 32 noodle_lock.release() 33 print("{}放下了面条...".format(name)) 34 kuaizi_lock.release() 35 print("{}放下了筷子...".format(name)) 36 37 # eat2("李琦") 38 if __name__ == "__main__": 39 lst1 = ["李琦","朱培峰"] 40 lst2 = ["郝建康","菲菲"] 41 42 for name in lst1: 43 Thread(target=eat1,args=(name,)).start() 44 45 for name in lst2: 46 Thread(target=eat2,args=(name,)).start()
(3) 使用递归锁
递归锁的提出专门用来解决死锁现象
用于快速解决线上死锁现象,连续上锁形同虚设,可以快速解开.
1 from threading import Thread , Lock , RLock 2 import time 3 noodle_lock = kuaizi_lock = RLock() 4 def eat1(name): 5 noodle_lock.acquire() 6 print("{}抢到了面条...".format(name)) 7 kuaizi_lock.acquire() 8 print("{}抢到了筷子...".format(name)) 9 10 print("开始吃面条~ 老北京杂酱面 ") 11 time.sleep(0.5) 12 13 kuaizi_lock.release() 14 print("{}放下了筷子...".format(name)) 15 noodle_lock.release() 16 print("{}放下了面条...".format(name)) 17 18 # eat1("郝建康") 19 20 def eat2(name): 21 22 kuaizi_lock.acquire() 23 print("{}抢到了筷子...".format(name)) 24 noodle_lock.acquire() 25 print("{}抢到了面条...".format(name)) 26 27 print("开始吃面条~ 老北京杂酱面 ") 28 time.sleep(0.5) 29 30 noodle_lock.release() 31 print("{}放下了面条...".format(name)) 32 kuaizi_lock.release() 33 print("{}放下了筷子...".format(name)) 34 35 # eat2("李琦") 36 if __name__ == "__main__": 37 lst1 = ["李琦","朱培峰"] 38 lst2 = ["郝建康","菲菲"] 39 40 for name in lst1: 41 Thread(target=eat1,args=(name,)).start() 42 43 for name in lst2: 44 Thread(target=eat2,args=(name,)).start()
(4) 尽量使用一把锁解决问题,(不要使用锁嵌套,容易逻辑死锁.)
1 from threading import Thread , Lock , RLock 2 import time 3 lock = Lock() 4 def eat1(name): 5 lock.acquire() 6 print("{}抢到了面条...".format(name)) 7 print("{}抢到了筷子...".format(name)) 8 9 print("开始吃面条~ 老北京杂酱面 ") 10 time.sleep(0.5) 11 12 print("{}放下了筷子...".format(name)) 13 print("{}放下了面条...".format(name)) 14 lock.release() 15 16 def eat2(name): 17 lock.acquire() 18 print("{}抢到了筷子...".format(name)) 19 print("{}抢到了面条...".format(name)) 20 21 print("开始吃面条~ 老北京杂酱面 ") 22 time.sleep(0.5) 23 24 print("{}放下了面条...".format(name)) 25 print("{}放下了筷子...".format(name)) 26 lock.release() 27 28 if __name__ == "__main__": 29 lst1 = ["李琦","朱培峰"] 30 lst2 = ["郝建康","菲菲"] 31 32 for name in lst1: 33 Thread(target=eat1,args=(name,)).start() 34 35 for name in lst2: 36 Thread(target=eat2,args=(name,)).start()
9、事件 Event
wait : 动态加阻塞 (True => 放行 False => 阻塞)
is_set : 获取内部成员属性值 (True , False 默认为False)
clear : 把成员属性值 => False
set : 把成员属性值 => True
1 from threading import Thread , Event 2 import time , random 3 4 def check(e): 5 # 检测中 6 print("检测中 .... ") 7 time.sleep(1) 8 print("检测账号中 ... ") 9 time.sleep(1) 10 print("检测密码中 .... ") 11 time.sleep(random.randrange(1,4)) # 3 4 5 12 # 检测ok , 直接放行 13 e.set() 14 15 # 连接线程 16 def connect(e): 17 # 默认没有连接成功 18 sign = False 19 for i in range(1,4): 20 # 最多阻塞1秒 21 e.wait(1) 22 if e.is_set(): 23 print("连接远程数据库 ok .... ") 24 # sign = True 代表成功 25 sign = True 26 break 27 else: 28 print("尝试连接数据库第{}次失败".format(i)) 29 30 31 # 如果没成功,抛出超时异常 32 if sign == False: 33 raise TimeoutError 34 35 if __name__ == "__main__": 36 e = Event() 37 # 启动检测线程 38 t = Thread(target=check,args=(e,)) 39 t.start() 40 41 # 启动连接线程 42 t = Thread(target=connect,args=(e,)) 43 t.start() 44
三、线程队列、线程池、进程池
1、线程队列
相关函数:
put 存放 超出队列长度阻塞
get 获取 超出队列长度阻塞
put_nowait 存放 超出队列长度报错
get_nowait 获取 超出队列长度报错
(1)Queue
特征:先进先出,后进后出
场景一
from queue import Queue q = Queue() q.put(111) q.put(222) q.put(333) print(q.get()) print(q.get()) print(q.get()) # print(q.get()) # error # print(q.get_nowait())
场景二
# 队列中最多存放3个元素 q = Queue(3) q.put(1) q.put(2) q.put(3) # 超出队列长度阻塞 # q.put(4) # 超出队列长度报错 #q.put_nowait(444)
(2) LifoQueue
特征:先进后出 , 后进先出
from queue import LifoQueue lq = LifoQueue() lq.put(999) lq.put(998) lq.put(997) print(lq.get()) print(lq.get()) print(lq.get()) """ 997 998 999 """
(3) PriorityQueue
特征:按照优先级顺序进行排序存放数据(默认从小到大)
1).对数字进行排序 (默认从小到大)
from queue import PriorityQueue pq = PriorityQueue() pq.put(11) pq.put(-100) pq.put(100) pq.put(-34) print(pq.get()) ##-100 print(pq.get()) #-34 print(pq.get()) #11 print(pq.get()) # 100
2.)对字母进行排序
1 from queue import PriorityQueue 2 pq = PriorityQueue() 3 pq.put("wangwen") 4 pq.put("liyaqi") 5 pq.put("liqi") 6 pq.put("wangyuhan") 7 pq.put("王文") 8 pq.put("王雨涵") 9 pq.put("李琦") 10 print( pq.get() ) 11 print( pq.get() ) 12 print( pq.get() ) 13 print( pq.get() ) 14 print( pq.get() ) 15 print( pq.get() ) 16 print( pq.get() )
3).对容器进行排序
1 pq.put( (18,"wangwen") ) 2 pq.put( (19,"wangyuhan") ) 3 pq.put( (17,'wuhongchang') ) 4 pq.put( (90,"sunxiangyu") ) 5 pq.put( (90,"zhangyin") ) 6 7 print(pq.get()) 8 print(pq.get()) 9 print(pq.get()) 10 print(pq.get()) 11 print(pq.get())
4).注意点
队列里面只能存放同一类型的数据,不能混杂其他类型数据
2、进程池
获取处理器的逻辑核心数: os.cpu_count()
多条进程提前在进程池当中开辟了,可以触发并发,也可以触发并行效果
1 from concurrent.futures import ProcessPoolExecutor 2 import os,time,random 3 def func(i): 4 time.sleep(random.uniform(0.1,0.9)) 5 print(os.getpid()) 6 print("此时任务在执行 .... ") 7 print("任务结束了 ... ") 8 print(i) 9 # time.sleep(10) 10 return i 11 12 if __name__ == "__main__": 13 lst = [] 14 15 # (1) 创建进程池对象 16 """ProcessPoolExecutor( 默认参数是cpu的最大逻辑核心数 )""" 17 p = ProcessPoolExecutor() 18 # print(p) 19 20 # (2) 异步提交任务 21 """语法:submit(任务,参数1,参数2,参数3 .... )""" 22 """含义:进程池里面有4个进程提前开辟好了,执行10个任务""" 23 """ 24 注意点:默认如果一个进程短时间内可完成更多任务, 25 进程池就不会用更多进程辅助完成,可以节省系统资源; 26 """ 27 for i in range(10): 28 obj = p.submit( func, i) 29 # print(obj , "<===1===>") 30 # result 不要加这里 31 # print(obj.result() , "<==2==>") 32 lst.append(obj) 33 34 # (3) 获取当前任务的返回值 (加了result之后,会变成同步程序) 35 # for i in lst: 36 # print(i.result() ,"<==3==>") 37 38 39 # (4) shutdown 等待进程池所有任务执行完毕之后,在放行;作用:同步进程池和主进程; 40 p.shutdown() 41 42 print("进程池结束 ... ")
3、线程池
1 from concurrent.futures import ThreadPoolExecutor 2 import os,time,random 3 from threading import currentThread as ct 4 5 def func(i): 6 print("线程任务执行中.... ") 7 time.sleep(1) 8 print("线程任务结束了 .... ") 9 # 获取线程号 10 return ct().ident 11 12 if __name__ == "__main__": 13 lst = [] 14 setvar = set() 15 # (1)创建线程池对象 16 t = ThreadPoolExecutor() # os.cpu_count() * 5 进程:线程 是 1:5配比 17 # print(t) 18 19 20 # (2) 异步提交任务 21 """语法:submit(任务,参数1,参数2,参数3 .... )""" 22 """含义:进程池里面有4个进程提前开辟好了,执行4*5=20个线程""" 23 """ 24 注意点:默认如果一个线程短时间内可完成更多任务, 25 线程池就不会用更多进程辅助完成,可以节省系统资源; 26 """ 27 for i in range(100): 28 obj = t.submit(func,i) 29 # print(obj) 30 # obj.result() (加了result之后,会变成同步程序) 31 lst.append(obj) 32 33 # (3) 获取当前任务的返回值 34 for i in lst: 35 setvar.add(i.result()) 36 37 # (4) shutdown 等待线程池所有线程执行完毕之后,在放行 38 t.shutdown() 39 40 print(setvar , len(setvar))
4、线程池 map
1 from concurrent.futures import ThreadPoolExecutor 2 from threading import currentThread as ct 3 from collections import Iterator,Iterable 4 def func(i): 5 time.sleep(random.uniform(0.1,0.9)) 6 print("当前线程号是{}".format(ct().ident)) 7 return "*" * i 8 9 10 if __name__ == "__main__": 11 t = ThreadPoolExecutor() # 20个线程 12 13 # 通过线程池的map处理数据,返回迭代器 14 it = t.map(func,range(100)) 15 print(isinstance(it , Iterator)) 16 17 # 协调子主线程的同步性,等所有线程池任务结束之后,在放行 18 t.shutdown() 19 20 for i in it: 21 print(i) 22 print("主线程执行结束 ... ")
5、总结(进程池、线程池)
总结:无论是进程池还是线程池,都是由固定的进程数或者线程数完成的
系统不会额外创建更多的进程或者线程完成任务;
6、进程池 与 线程池 的 回调函数
回调函数 : 回头调用一下函数
微信支付付款成功: 通过回调函数获取金额
微信支付退款状态: 通过回调函数获取金额状态
add_done_callback:完成回调函数的调用
add_done_callback作用:
可以在获取result返回值时,变成异步并发程序;
最后调用一下自定义的回调函数
1)进程任务
结论:进程池的回调函数由主进程完成
1 from concurrent.futures import ProcessPoolExecutor , 2 import os,time,random 3 4 def func1(i): 5 time.sleep(random.uniform(0.1,0.9)) 6 print(i) 7 print("进程任务执行中 .... 进程号{}".format(os.getpid())) 8 print("进程任务结束了 .... 进程号{}".format(os.getpid())) 9 return i 10 11 def call_back1(obj): 12 # 获取当前进程号 13 print("<=====回调函数的进程号{}======>".format(os.getpid())) 14 # 获取返回值 15 print(obj.result()) 16 if __name__ == "__main__": 17 # ### 进程池 18 """***结论:进程池的回调函数由主进程完成""" 19 p = ProcessPoolExecutor() 20 for i in range(1,11): 21 obj = p.submit(func1,i) 22 # 1.异步获取当前进程的返回值 23 obj.add_done_callback(call_back1) 24 # 2.同步获取当前进程的返回值p 25 # print(obj.result()) 26 p.shutdown() 27 print("主线程执行结束 ... 线程号{}".format(os.getpid()os.getpid())) 28 29 30 31 32 33 34
2)线程任务
结论:线程池的回调函数由对应线程池中的子线程完成
1 from concurrent.futures import ThreadPoolExecutor 2 from threading import currentThread as ct 3 import os,time,random 4 # ### 线程任务 5 def func2(i): 6 time.sleep(random.uniform(0.1,0.9)) 7 print(i) 8 print("线程任务执行中 .... 线程号{}".format(ct().ident)) 9 print("线程任务结束了 .... 线程号{}".format(ct().ident)) 10 return i 11 12 def call_back2(obj): 13 # 获取当前线程号 14 print("<=====回调函数的线程号{}======>".format(ct().ident)) 15 # 获取返回值 16 print(obj.result()) 17 if __name__ == "__main__": 18 # ### 线程池 19 """***结论:线程池的回调函数由对应线程池中的子线程完成""" 20 t = ThreadPoolExecutor() 21 for i in range(1,11): 22 obj = t.submit(func2,i) 23 # 1.异步获取当前线程的返回值 24 obj.add_done_callback(call_back2) 25 # 2.同步获取当前线程的返回值 26 # print(obj.result()) 27 28 t.shutdown() 29 print("主线程执行结束 ... 线程号{}".format(ct().ident)) 30
3)模拟一下 add_done_callback 内部实现
1 class Ceshi(): 2 def add_done_callback(self,fn): 3 print("执行操作1") 4 print("执行操作2") 5 fn(self) 6 7 def result(self): 8 return "这是我的返回值" 9 10 def call_back1(obj): 11 print(obj.result()) 12 13 14 obj = Ceshi() 15 obj.add_done_callback(call_back1)
四、协程
总结: 协程是基于线程的一种异步并发程序;
进程是资源分配的最小单位
线程是程序调度的最小单位
协程是线程实现的具体过程
总结:
在进程一定的情况下,开辟多个线程
在线程一定的情况下,创建多个协程
提高更大的并发,充分利用cpu
1、 利用协程改写生产者消费者模型
def producer(): for i in range(2000): yield i def consumer(gen): for i in range(10): print( next(gen) ) # 初始化生成器函数 => 生成器对象(简称生成器) gen = producer() consumer(gen) consumer(gen) consumer(gen
2、协程的终极形态 (猴子补丁)
from gevent import monkey ; monkey.patch_all() # 猴子补丁可以把接下来引入的所有模块中的阻塞,自动识别出来;自动遇到阻塞就切换任务; import time import gevent def eat(): print("吃吃吃1 ...") time.sleep(3) print("喝喝喝2 .... ") def play(): print("玩玩玩1 ...") time.sleep(3) print("乐乐乐2 .... ") g1 = gevent.spawn(eat) g2 = gevent.spawn(play) g1.join() g2.join() print("主线程执行结束")
3、协程方法
(1) spawn(函数,参数1,参数2,参数3 .. ) 创建协程
(2) join 阻塞,直到某个协程任务执行完毕之后,放行;
(3) value 获取协程任务中的返回值 g1.value g2.value
(4) joinall 等待所有协程任务执行完毕之后,放行;
g1.join()
g2.join()
=> gevent.joinall( [g1,g2 .... ] )
1 from gevent import monkey ; monkey.patch_all() 2 import time 3 import gevent 4 5 def eat(): 6 print("吃吃吃1 ...") 7 time.sleep(3) 8 print("喝喝喝2 .... ") 9 return "吃完了" 10 11 def play(): 12 print("玩玩玩1 ...") 13 time.sleep(3) 14 print("乐乐乐2 .... ") 15 return "玩完了" 16 17 g1 = gevent.spawn(eat) 18 g2 = gevent.spawn(play) 19 20 # 等待所有协程任务执行完毕之后,放行; 21 gevent.joinall( [g1,g2] ) 22 print("主线程执行结束") 23 24 # 获取协程任务中的返回值 25 print(g1.value) 26 print(g2.value)
4、协程案例
http 状态码:
200 ok
400 bad request
404 not found
1 # 基本语法 2 import requests 3 # 返回响应对象(get获取时,必须要加http协议) 4 response = requests.get("http://www.baidu.com/") 5 print(response) 6 # 获取状态码 7 print(response.status_code) 8 # 获取网页中字符编码 9 res = response.apparent_encoding 10 print(res) 11 # 设置编码集,防止乱码 12 response.encoding = res 13 # 获取页面内容 14 res = response.text 15 print(res)
(1) 正常爬取
1 from gevent import monkey ; monkey.patch_all() 2 import time 3 import gevent 4 import requests 5 url_lst = [ 6 "http://www.baidu.com", 7 "http://www.taobao.com/", 8 "http://www.jd.com/", 9 "http://www.4399.com/", 10 "http://www.7k7k.com/", 11 "http://www.baidu.com", 12 "http://www.taobao.com/"] 13 def get_url(url): 14 response = requests.get(url) 15 if response.status_code == 200: 16 # print(response.text) 17 pass 18 startime = time.time() 19 for i in url_lst: 20 get_url(i) 21 endtime = time.time() 22 print("所用时间是{}".format(endtime - startime))
(2) 异步多协程的方式抓取页面
1 from gevent import monkey ; monkey.patch_all() 2 import time 3 import gevent 4 import requests 5 url_lst = [ 6 "http://www.baidu.com", 7 "http://www.taobao.com/", 8 "http://www.jd.com/", 9 "http://www.4399.com/", 10 "http://www.7k7k.com/", 11 "http://www.baidu.com", 12 "http://www.taobao.com/", 13 "http://www.jd.com/"] 14 def get_url(url): 15 response = requests.get(url) 16 if response.status_code == 200: 17 # print(response.text) 18 pass 19 20 lst = [] 21 startime = time.time() 22 for i in url_lst: 23 g = gevent.spawn(get_url , i) 24 lst.append(g) 25 26 # 必须等待所有协程任务执行完毕之后,计算最后的时间; 27 gevent.joinall(lst) 28 endtime = time.time() 29 30 print("所用时间是{}".format(endtime - startime))