并发编程02
子进程回收的两种方式
- join让主进程等待子进程结束后,并回收进程资源,主进程在结束并回收资源
from multiprocessing import Process
import time
def task():
print('start...')
time.sleep(3)
print('end...')
if __name__ == '__main__':
p_obj = Process(target=task)
p_obj.start()
p_obj.join()
print('主进程结束....')
- 主进程正常结束,子进程与主进程一并被回收资源
from multiprocessing import Process
import time
def task():
print('start...')
time.sleep(3)
print('end...')
if __name__ == '__main__':
p_obj = Process(target=task)
p_obj.start()
print('主进程结束....')
僵尸进程
在子进程结束后,主进程没有正常结束,子进程的PID不会被回收
缺点:
①:操作系统中的PID号都是有限的,如有子进程PID号无法正常回收,则会占用PID号
②:PID号骂好了,则无法创建新的进程
③:浪费资源
孤儿进程(没有坏处)
在子进程没有结束时,主进程没有正常结束,子进程PID不会被回收
孤儿院:操作系统优化机制
当主进程意外终止,操作系统会检测是否有正在运行的子进程,会将他们放入孤儿院中,让操作系统帮你自动回收
守护进程
当主进程结束时,子进程必须结束(陪葬机制)
from multiprocessing import Process
import time
def dome(name):
print(f'start....{name}')
time.sleep(3)
print(f'end...{name}')
print('子进程结束...')
if __name__ == '__main__':
p = Process(target=dome, args=('太监一号', ))
# 守护进程必须在p.start()调用前设置
p.daemon = True
p.start()
time.sleep(1)
print('皇帝驾崩啦啊~~~~~')
# 打印结果:
# start....太监一号
# 皇帝驾崩啦啊~~~~~
进程间数据是隔离的
from multiprocessing import Process
import time
number = 10
def func():
global number
number = 100
def func1(number):
number += 10
if __name__ == '__main__':
p_obj = Process(target=func)
p_obj1 = Process(target=func1, args=(number,))
p_obj.start()
p_obj1.start()
p_obj.join()
p_obj1.join()
time.sleep(1)
print(number) # 10 ---> 证明数据是隔离的
进程互斥锁
进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件或同一个打印终端是没有问题的,而共享带来的就是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理
# 抢票例题
from multiprocessing import Process
from multiprocessing import Lock # ---> 进程互斥锁
import random
import json
import time
# 抢票例子
# 1、查看余票
def search(name):
# 1.读取data.json文件中的数据
with open('data.json', 'r', encoding='utf-8')as f:
data_dic = json.load(f)
print(f'用户[{name}]查看余票,余票还剩[{data_dic.get("number")}]!')
# 2、若有余票,购买成功,票数会减少
def buy(name):
# 网络延时
with open('data.json', 'r', encoding='utf-8')as f:
data_dic = json.load(f)
# 进入这一步 证明最先抢到票
if data_dic.get('number') > 0:
data_dic['number'] -= 1
time.sleep(random.randint(1, 3))
with open('data.json', 'w', encoding='utf-8')as f:
json.dump(data_dic, f)
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):
p_obj = Process(target=run, args=(f'张全蛋{line}', lock))
p_obj.start()
总结:加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,速度是慢了,但牺牲了效率却保证了数据安全,这就是互斥锁的特点
队列(遵循先进先出)
队列相当于一个第三方的管道,可以存放数据,基于队列,可以实现进程间的互相通信
创建队列有三种方式
from multiprocessing import Queue
from multiprocessing import JoinableQueue
import queue
# 第一种
# Queue(5)指的是队列中只能存放5份数据
q_obj = Queue(5) # q_obj队列的对象
# 添加数据队列中
q_obj.put('张三') # put:添加数据到队列中,将队列加满会阻塞
print('添第加1个')
q_obj.put('李四')
print('添加第2个')
q_obj.put('王二麻子')
print('添加第3个')
q_obj.put('张全蛋')
print('添加第4个')
q_obj.put('李小花')
print('添加第5个')
# q_obj.put('王小明') # 到这里程序就会进入阻塞态
# print('添加第6个')
print(q_obj.get()) # 取出队列中的数据,取完再取会阻塞
print(q_obj.get())
print(q_obj.get())
print(q_obj.get())
print(q_obj.get())
# print(q_obj.get()) # 到这里程序就会进入阻塞态
# # 第二种
# JoinableQueue(5)指的是队列中只能存放5份数据
q_obj = JoinableQueue(5) # q_obj队列的对象
# 添加数据队列中
q_obj.put('张三') # put:添加数据到队列中,将队列加满会阻塞
print('添第加1个')
q_obj.put('李四')
print('添加第2个')
q_obj.put('王二麻子')
print('添加第3个')
q_obj.put('张全蛋')
print('添加第4个')
q_obj.put('李小花')
print('添加第5个')
# q_obj.put('王小明') # 到这里程序就会进入阻塞态
# print('添加第6个')
print(q_obj.get()) # 取出队列中的数据,取完再取会阻塞
print(q_obj.get())
print(q_obj.get())
print(q_obj.get())
print(q_obj.get())
# print(q_obj.get()) # 到这里程序就会进入阻塞态
# # 第三种
# queue.Queue(5)指的是队列中只能存放5份数据
q_obj = queue.Queue(5) # q_obj队列的对象
# 添加数据队列中
q_obj.put('张三') # put:添加数据到队列中,将队列加满会阻塞
print('添第加1个')
q_obj.put('李四')
print('添加第2个')
q_obj.put('王二麻子')
print('添加第3个')
q_obj.put('张全蛋')
print('添加第4个')
q_obj.put('李小花')
print('添加第5个')
# q_obj.put('王小明') # 到这里程序就会进入阻塞态
# print('添加第6个')
print(q_obj.get()) # 取出队列中的数据,取完再取会阻塞
print(q_obj.get())
print(q_obj.get())
print(q_obj.get())
print(q_obj.get())
# print(q_obj.get()) # 到这里程序就会进入阻塞态
# 还有put和get的进阶用法
q_obj.put_nowait('张三') # put_nowait:添加数据到队列中,将队列加满会报错
print('添加第1个')
q_obj.put_nowait('李四')
print('添加第2个')
q_obj.put_nowait('王二麻子')
print('添加第3个')
q_obj.put_nowait('张全蛋')
print('添加第4个')
q_obj.put_nowait('李小花')
print('添加第5个')
q_obj.put_nowait('王小明') # 到这里程序就会报错
print('添加第6个')
print(q_obj.get_nowait()) # 取出队列中的数据,取完再取会报错
print(q_obj.get_nowait())
print(q_obj.get_nowait())
print(q_obj.get_nowait())
print(q_obj.get_nowait())
print(q_obj.get_nowait()) # # 到这里程序就会报错
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()
# 打印结果:
# 添加数据
# 获取的数据是[100]
# 9527
生产者与消费者
生产者:生产数据的
消费者:使用数据的
解决供需不平衡问题
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()
# 创建生产者生产10个食物
for line in range(10):
p1 = Process(target=producer, args=('tank', f'Pig饲料{line}', q))
p1.start()
# 创建两个消费者
c1 = Process(target=customer, args=('Pig1', q))
c2 = Process(target=customer, args=('Pig2', q))
c1.start()
c2.start()
线程
什么是线程?
进程:资源单位
线程:执行单位
线程与进程都是虚拟的概念,为了更好表达某种事物
PS:开启一个进程一定会自带一个线程,线程才是真正的执行者
为什么要使用线程?
节省资源的占用
开启进程和开启线程的区别
开启进程
①:开辟一个名称空间,每开启一个进程都会占用一份内存资源
②:会自带一个主线程
开启线程
①:一个进程可以开启多个线程,从进程的内存空间中申请执行单位
②:节省内存资源
打个比方:
开启三个进程:占用三份内存空间
开启三个线程,从一个内存资源中,申请三个小的执行单位
PS:进程与进程之间数据都是隔离的,线程与线程之间的数据都是共享的
创建线程的两种方式
from multiprocessing import Process
from threading import Thread
import time
# 方式一
def task():
print("线程start...")
time.sleep(3)
print("线程end...")
if __name__ == '__main__':
p_obj = Process(target=task)
p_obj.start()
p_obj.join()
print("进程...")
# # 方式二
class MyThread(Thread):
def __init__(self, name):
super().__init__()
self.name = name
def run(self) -> None:
print("线程start...")
time.sleep(3)
print("线程end...")
if __name__ == '__main__':
p = MyThread("线程")
p.start()
p.join()
print("主线程")
线程互斥锁(和进程互斥锁一样使用)
from threading import Lock
from threading import Thread
import time
lock = Lock()
# 开启10个线程,对一个数据进行修改
number = 100
def task():
global number
lock.acquire()
number2 = number
# time.sleep(1)
number = number2 - 1
lock.release()
if __name__ == '__main__':
list1 = []
for line in range(10):
t = Thread(target=task)
t.start()
list1.append(t)
for t in list1:
t.join()
print(number) # 90
线程池
他是基于concurrent.futures模块,用来限制创建的线程数量的
# 线程池限制线程数量
from concurrent.futures import ThreadPoolExecutor
pool = ThreadPoolExecutor(50) # 限制线程数量为50
def task():
import time
time.sleep(1)
while True:
pool.submit(task) # 相当于Thread().start()
GIL全局解释器锁(纯理论)
GIL全局解释器锁,本质上就是一把互斥锁,保证数据安全
在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势
GIL全局解释器锁的优缺点
优点:
保证数据的安全
缺点:
单个进程下,开启多个线程,牺牲执行效率,无法实现并行
注意:
IO密集型下使用多线程
计算密集型下使用多进程
协程
进程:资源单位
线程:执行单位
协程:单线程下实现并发
在IO密集型情况下,使用协程能将效率提到最高
PS:协程不是任何单位,只是程序员YY出来的东西
PS:将效率提到极致的方法,多进程--->多线程--->让每个线程都实现协程
协程的目的:
手动实现“遇到IO切换 + 保存状态”去欺骗操作系统,让操作系统误以为没有IO操作,将CPU的执行权限给你
from gevent import monkey # 猴子补丁
monkey.patch_all() # 监听所有的任务是否有IO操作
from gevent import spawn # spawn(任务)
from gevent import joinall
import time
def task1():
print('start from task1...')
time.sleep(1)
print('end from task1...')
def task2():
print('start from task2...')
time.sleep(3)
print('end from task2...')
def task3():
print('start from task3...')
time.sleep(5)
print('end from task3...')
if __name__ == '__main__':
start_time = time.time()
sp1 = spawn(task1)
sp2 = spawn(task2)
sp3 = spawn(task3)
joinall([sp1, sp2, sp3])
end_time = time.time()
print(f'消耗时间: {end_time - start_time}')
# 打印结果:
# start from task1...
# start from task2...
# start from task3...
# end from task1...
# end from task2...
# end from task3...
# 消耗时间: 5.02597451210022