目录
(见右侧目录栏导航)
- 1. 前言
- 2. multiprocess模块
- 2.1 multiprocess.Process模块
- 2.2 使用Process模块创建进程
- 2.3 守护进程
- 2.4 socket聊天并发实例
- 3. 进程锁 - multiprocess.Lock
- 4. 信号量(multiprocessing.Semaphore)
- 5. 事件
- 6. 进程间通信
- 6.1 队列
- 6.2 管道(了解)
- 6.3 进程之间数据共享
- 7. 进程池
- 7.1 为什么要有进程池
- 7.2 Pool模块
1. 前言
运行中的程序就是一个进程。所有的进程都是通过它的父进程来创建的。因此,运行起来的python程序也是一个进程,那么我们也可以在程序中再创建进程。多个进程可以实现并发的效果,也就是说,当我们的程序中存在多个进程的时候,在某些时候,就会让程序的执行速度变快。以我们之前所学的知识,并不能实现创建进程这个功能,所以我们就需要借助python中强大的模块。
2. multiprocess模块
mulitiprocess 是python的一个内建模块,包含了和进程有关的所有子模块。由于提供的子模块非常多,为了方便大家归类记忆,我将这部分大致分为四个部分:创建进程部分,进程同步部分,进程池部分,进程之间数据共享。
2.1 multiprocess.Process模块
Process模块介绍
process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建。
Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)
强调:
1. 需要使用关键字的方式来指定参数
2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号
参数介绍:
group参数未使用,值始终为None
target表示调用对象,即子进程要执行的任务
args表示调用对象的位置参数元组,args=(1,2,'egon',)
kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}
name为子进程的名称
方法介绍:
p.start():启动进程,并调用该子进程中的p.run()
p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
p.is_alive():如果p仍然运行,返回True
p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程
属性介绍:
p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
p.name:进程的名称
p.pid:进程的pid
p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
注意:
在Windows操作系统中由于没有fork(linux操作系统中创建进程的机制),在创建子进程的时候会自动 import 启动它的这个文件,而在 import 的时候又执行了整个文件。因此如果将process()直接写在文件中就会无限递归创建子进程报错。所以必须把创建子进程的部分使用if __name__ ==‘__main__’ 判断保护起来,import 的时候 ,就不会递归运行了。
2.2 使用Process模块创建进程
在一个python进程中开启子进程,start方法和并发效果。
from multiprocessing import Process
import time
def f(name):
print('hello,', name)
print('我是子进程.')
if __name__ == '__main__':
p = Process(target=f, args=('hkey',))
p.start()
time.sleep(1)
print('执行主进程的内容了。')
from multiprocessing import Process
import time
def f(name):
print('hello,', name)
time.sleep(1)
print('我是子进程.')
if __name__ == '__main__':
p = Process(target=f, args=('hkey',))
p.start()
p.join() # 等待子进程执行完毕然后在继续执行主进程
print('执行主进程的内容了。')
from multiprocessing import Process
import time
import os
def f(name):
print('hello,', name)
time.sleep(1)
print('子进程id:', os.getpid()) # 子进程id
print('父进程id:', os.getppid()) # 子进程的父进程id
print('我是子进程.')
if __name__ == '__main__':
p = Process(target=f, args=('hkey',))
p.start()
p.join() # 等待子进程执行完毕然后在继续执行主进程
print('执行主进程的内容了。')
print('主进程的id:', os.getpid()) # 主进程的id
进阶,多个进程同时运行(注意:子进程的执行顺序不是根据启动顺序决定的)
from multiprocessing import Process
import time
def f(name):
print('hello,', name)
time.sleep(1)
if __name__ == '__main__':
for i in range(5): # 产生 5 个子进程同时运行
p = Process(target=f, args=('hkey',))
p.start()
from multiprocessing import Process
import time
def f(name):
print('hello,', name)
time.sleep(1)
if __name__ == '__main__':
p_lst = []
for i in range(5): # 产生 5 个子进程同时运行
p = Process(target=f, args=('hkey',))
p.start()
p_lst.append(p)
[p.join() for p in p_lst] # 等待所有子进程执行结束在继续执行后续的代码
print('hello world.')
除了上面这些开启进程的方法,还有一种以继承Process类的形式开启进程的方式
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self, name):
super(MyProcess, self).__init__() # 需要继承父类中的初始化方法
self.name = name
def run(self): # 必须重写的方法
print('hello,', self.name)
time.sleep(1)
if __name__ == '__main__':
p_lst = []
for i in range(5):
p = MyProcess(str(i))
p.start()
p_lst.append(p)
[p.join() for p in p_lst]
print('hello hkey')
进程之间数据是相互隔离的。
这里可以想象电脑上通过开启QQ和wrod文档,它们是相互独立的两个进程,不存在任何数据共享。
2.3 守护进程
守护进程:会随着主进程的结束而结束的进程。
主进程创建守护进程:
(1) 守护进程会在主进程代码执行结束后就终止
(2) 守护进程内无法再开启子进程,否则抛出异常
注意:进程之间是相互独立的,主进程代码运行结束,守护进程随即终止
from multiprocessing import Process
import time
import os
class MyProcess(Process):
def __init__(self, name):
super(MyProcess, self).__init__()
self.name = name
self.daemon = True # True为开启守护进程
def run(self):
print(os.getpid(), self.name)
print('%s正在吃饭.' % self.name)
if __name__ == '__main__':
p = MyProcess('小王')
p.start()
time.sleep(4)
print('主进程.')
from multiprocessing import Process
import time
def foo():
print('123')
time.sleep(3)
print('end123')
if __name__ == '__main__':
p1 = Process(target=foo)
p1.daemon = True
p1.start()
time.sleep(0.1)
print('main-end')
2.4 socket聊天并发实例
from multiprocessing import Process
import socket
class MyProcess(Process):
def __init__(self, conn, addr):
super(MyProcess, self).__init__()
self.conn = conn
self.addr = addr
def run(self):
try:
while True:
res = self.conn.recv(1024)
if not res: break
print(self.addr, res)
self.conn.send(res.upper())
except ConnectionResetError as e:
print('Error', e)
if __name__ == '__main__':
sk_server = socket.socket()
sk_server.bind(('localhost', 8080))
sk_server.listen(5)
while True:
conn, addr = sk_server.accept()
conn.send('welcome QQ'.encode())
p = MyProcess(conn, addr)
p.start()
import socket
sk_client = socket.socket()
sk_client.connect(('localhost', 8080))
res = sk_client.recv(1024).decode()
print(res)
while True:
cmd = input('>>>').strip()
if not cmd: continue
sk_client.send(cmd.encode())
res = sk_client.recv(1024)
print(res)
3. 进程锁 - multiprocess.Lock
通过上面的学习,实现了并发的效果,让多个任务可以同时在几个进程中并发处理,他们之间的运行没有顺序,一旦开启也不受我们控制。尽管并发编程让我们能更加充分的利用IO资源,但也给我们带来了新的问题。
当多个进程使用同一份数据资源的时候,就会引发数据安全或顺序混乱问题。
import os
import time
import random
from multiprocessing import Process, Lock
class Work(Process):
def __init__(self, n):
super(Work, self).__init__()
self.n = n
def run(self):
print('%s: %s is running.' % (self.n, os.getppid()))
time.sleep(random.random())
print('%s:%s is done' % (self.n, os.getpid()))
if __name__ == '__main__':
for i in range(5):
p = Work(i)
p.start()
import os
import time
import random
from multiprocessing import Process, Lock
class Work(Process):
def __init__(self, n, lock):
super(Work, self).__init__()
self.n = n
self.lock = lock
def run(self):
self.lock.acquire()
print('%s: %s is running.' % (self.n, os.getppid()))
time.sleep(random.random())
print('%s:%s is done' % (self.n, os.getpid()))
self.lock.release()
if __name__ == '__main__':
lock = Lock()
for i in range(5):
p = Work(i, lock)
p.start()
上面这种情况虽然使用加锁的形式实现了顺序的执行,但是程序又重新变成串行了,这样确实会浪费了时间,却保证了数据的安全。
接下来,我们以模拟抢票为例,来看看数据安全的重要性。
# 文件 ticket.json 的内容为:{"count":1}
# 注意一定要用双引号,不然json无法识别
# 并发运行,效率高,但竞争写同一文件,数据写入错乱
from multiprocessing import Process, Lock
import time, json
def search():
with open('ticket.json', 'r') as f:
dic = json.load(f)
print('