并发编程相关概念
# 进程
"""
程序运行的过程,是操作系统资源分配的最小单位.资源分配的是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