并发编程
在明白并发编程之前,我们需要了解一些名次,例如 程序
, 进程
, 线程
。
程序:
程序是一推代码,用某种语言编写的一组命令的集合。进程:
正在进行的一个过程或者说一个任务
,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,进程是资源分配的最小单位
线程:
进程的子集,比进程更小的执行单位,线程在进程中执行,线程是cpu调度的最小单位
1. 进程
同一个程序执行多次,是开启多个进程,进程中的两大概念,并行
和 并发
, 并行是指 同时运行处理多个任务
,需要多个cpu才能实现,并发
是指 处理多个任务,看起来是同时运行
,是一种伪并行
1.1 进程的状态
程序运行过程中,由于被操作系统的调度算法控制,程序会进入几个状态:就绪
,运行
,阻塞
就绪:
进程已分配到除 CPU 以外的所有必要的资源
,只要获得处理机便可立即执行,这时的进程状态称为就绪状态运行:
当进程已获得处理机
,其程序正在处理机上执行
,此时的进程状态称为执行的状态阻塞:
在执行的进程,由于等待某个时间发生而无法执行时
,便放弃处理机而处于阻塞状态
,引起进程阻塞的时间可能有多种,例如,等待I/O完成
,申请缓冲区不能满足
,等待信件(信号)等。
1.2 开启多进程的两种方式
python中开启多进程的模块在 multiprocessing
模块 Process
类
class Process:
def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
*, daemon=None):
"""
group: 参数未使用,值始终为None
target: 表示调用对象,即子进程要执行的任务
args: 表示调用对象的位置参数元组,args=(1,2,'egon',)
kwargs: 表示调用对象的字典,kwargs={'name':'egon','age':18}
name为: 子进程的名称
daemon: 是否开启守护模式
"""
进程属性和方法
方法 | 描述 |
---|---|
p.start() |
启动进程,并调用该子进程中的 p.run()
|
p.join() |
主进程等待子进程终止,p.join只能join主start开启的进程,不能join主 run开启的进程 |
p.is_alive() |
验证进程是否还存活着 |
p.terminate() |
强制终止进程 |
p.daemon |
守护进程,如果设为True ,代表为守护进程,当父进程结束,子进程也随之结束,子进程不能在创建自己的子进程,必须要在开启之前设置 |
p.name |
进程的名称 |
p.pid |
进程的 pid |
方式一
import time
from multiprocessing import Process
def task(name):
print(f'{name} is running')
time.sleep(3)
print(f'{name} is done')
if __name__ == '__main__':
p = Process(target=task, args=('进程',)) # 创建线程
p.start() # 发了一个信号,申请内存空间,需要花一定时间
print('主进程') # 主进程
# 结果如下:
"""
主进程
进程 is running
进程 is done
"""
方式二
import time
from multiprocessing import Process
class OwnProcess(Process):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print(f'{self.name} is running')
time.sleep(3)
print(f'{self.name} is done')
if __name__ == '__main__':
p = OwnProcess('进程') # 创建进程
p.start() # 触发 run 方法
print('主进程')
# 结果如下:
"""
主进程
进程 is running
进程 is done
"""
p.start()
是开启进程的方式,
1.3 进程间数据是隔离的
from multiprocessing import Process
import time
n = 100
def task():
global n
n = 0
print(n)
if __name__ == '__main__':
p = Process(target=task)
p.start()
print(p.is_alive()) # 验证进程是否还存活着的
p.join() # 等子进程运行完毕再执行下面代码
print(p.is_alive())
print('主进程', n)
# 结果:
"""
True
0
False
主进程 100
"""
1.4 僵尸进程与孤儿进程僵尸进程:
是指一个进程使用fork创建子进程,如果子进程退出
,而父进程并没有调用 wait 或 waitpid 获取子进程的状态信息
,那么子进程的进程描述符仍然保存在系统中
。这种进程称之为僵死进程。
孤儿进程:
顾名思义,子进程还在世的时候父进程却结束了,孤儿进程是无害的,在用户机结束时终止。它有一个功能就是收养这些孤儿
1.5 守护进程守护进程
: 当子进程执行的任务 在父进程代码运行代码完毕之后就没有存在的必要了
,那么该子进程就应该设施为守护进程
import time
from multiprocessing import Process
def task(name):
print(f'{name} is running')
time.sleep(5)
print(f'{name} is done')
if __name__ == '__main__':
p = Process(target=task, args=('action',))
p.daemon = True # 创建守护进程
p.start()
time.sleep(2)
print('主进程')
# 结果:
"""
action is running
主进程
"""
1.6 进程互斥锁
当有 两个或以上线程在同一时刻访问同一资源
,会出现一系列问题,例如进程之间数据不共享,但是共享同一套文件系统
,所以访问同一个文件,或同一个打印终端,是没有问题的,而共享带来的是竞争
,竞争带来的结果就是错乱,而加锁就能解决这个问题
import json
from multiprocessing import Process,Lock
import time
import random
import os
def search():
"""查票"""
time.sleep(random.randint(1, 3))
dic = json.load(open('db.txt', 'r', encoding='utf-8'))
print(f'{os.getpid()} 查看到剩余票数{dic["count"]}')
def get():
"""购票"""
dic = json.load(open('db.txt', 'r', encoding='utf-8'))
if dic['count'] > 0:
dic['count'] -= 1
time.sleep(random.randint(1, 3))
json.dump(dic, open('db.txt', 'w', encoding='utf-8'))
print(f'{os.getpid()} 购票成功')
def task(mutex):
search()
mutex.acquire() # 加锁
get()
mutex.release() # 释放
if __name__ == '__main__':
mutex = Lock()
for i in range(5):
p = Process(target=task, args=(mutex,))
p.start()
print('主进程')
# 结果:
"""
主进程
7533 查看到剩余票数1
7535 查看到剩余票数1
7531 查看到剩余票数1
7532 查看到剩余票数1
7534 查看到剩余票数1
7533 购票成功
"""
1.7 进程间通信
进程之间数据是相互隔离
的,要想实现进程间的通信,就必须借助于一些技术才可以,比如multiprocess
模块中的:队列
和管道
,这两种方式都是可以实现进程间数据传输的,由于队列是管道+锁的方式实现的,
创建共享进程队列,Queue是多进程安全的队列
,可以使用 Queue实现多进程之间的数据传递
from multiprocessing import Queue
q = Queue(3)
q.put('first')
q.put(2)
q.put(['count', 3])
# q.put('fourth') # 默认阻塞
# q.put_nowait('fourth') # 不阻塞
# q.put('fourth', block=False) # 不阻塞 ,报错
# q.put('fourth', block=True, timeout=5) # 设置超时时间
print(q.get())
print(q.get())
print(q.get())
# print(q.get()) # 默认阻塞
# print(q.get_nowait()) # 默认不阻塞
# print(q.get(block=False)) # 不阻塞
# print(q.get(block=True, timeout=5)) # 设置超时时间
生产者模式消费者模式
生产者消费者模式是 通过一个容器来解决生产者和消费者强耦合的问题
,生产者和消费者 彼此之间不直接通信
,而是 通过阻塞队列来进行通讯
,所以 生产者生产完数据之后不用等待消费者来处理
,直接扔给阻塞队列,消费者不找生产者要数据
,而是直接从阻塞队列中取。
import time
import random
from multiprocessing import Queue, Process
def producer(name, food, q):
for i in range(3):
res = f'{food}{i}'
time.sleep(random.randint(1, 3))
print(f'厨师{name}生产了{res}')
# consumer('action', res)
q.put(res)
def consumer(name, q):
while True:
res = q.get()
if res is None:
break
time.sleep(random.randint(1, 3))
print(f'吃货{name} 吃了{res}')
if __name__ == '__main__':
# 队列
q = Queue()
# 生产者
p1 = Process(target=producer, args=('wu_chang1', '火锅', q))
p2 = Process(target=producer, args=('wu_chang2', '火锅', q))
# 消费者
c1 = Process(target=consumer, args=('action1', q))
c2 = Process(target=consumer, args=('action2', q))
c3 = Process(target=consumer, args=('action3', q))
# 开启线程
p1.start()
p2.start()
c1.start()
c2.start()
c3.start()
p1.join()
p2.join()
q.put(None) # 发送结束信号
q.put(None) # 发送结束信号
q.put(None) # 发送结束信号
c1.join()
c2.join()
c3.join()
print('主进程')
这样又一个不好的地方,就是每一个进程都要发送一个结束信号,对开发这非常不友好
import time
import random
from multiprocessing import Process, JoinableQueue
def producer(name, food, q):
for i in range(3):
res = f'{food}{i}'
time.sleep(random.randint(1, 3))
print(f'厨师{name}生产了{res}')
# consumer('action', res)
q.put(res)
def consumer(name, q):
while True:
res = q.get()
time.sleep(random.randint(1, 3))
print(f'吃货{name} 吃了{res}')
q.task_done()
if __name__ == '__main__':
# 队列
q = JoinableQueue()
# 生产者
p1 = Process(target=producer, args=('wu_chang1', '火锅', q))
p2 = Process(target=producer, args=('wu_chang2', '火锅', q))
# 消费者
c1 = Process(target=consumer, args=('action1', q))
c2 = Process(target=consumer, args=('action2', q))
c3 = Process(target=consumer, args=('action3', q))
c1.daemon = True
c2.daemon = True
c3.daemon = True
# 开启线程
p1.start()
p2.start()
c1.start()
c2.start()
c3.start()
p1.join()
p2.join()
q.join()
print('主进程')
将消费者设置为守护线程,当 JoinableQueue
队列中没有东西过后,就随着主进程一起结束
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。