进程与线程概念
进程:一个运行的程序。给定的时间内只做一件事情。
每个进程都有独立的系统状态(内存,已打开文件列表,程序计数器,保存函数局部变量的调用栈)
线程:和进程类似,但所有线程都运行在一个进程里,共享运行环境
进程间通信:基于消息传递,通过管道或套接字
竞争条件:多个任务同时更新一个数据结构,会导致数据不一致
全局解释锁(GIL):Python虚拟机被GIL控制来保证同时刻只有一个线程在运行,即使有多处理器。Python虚拟机执行顺序:1.设置GIL 2.切换一个线程 3.运行 4.设置睡眠状态 5.解锁 6.重复上述
I/O密集型程序(调用内置的操作系统C代码的程序),GIL会在调用它之前释放,允许其他线程在等待I/O操作时候运行。所以多线程一般用于I/O密集型程序。
计算密集型程序用线程则会在等待中等浪费时间。
同时,如果有需要要大量线程来工作,会有系统资源,上下文切换,锁定等问题。
一般计算密集型程序,使用C扩展或multiprocessing模块。
C扩展能够释放锁定,不与解释器交互。
multiprocessing模块可将工作分派给不受锁定限制的单独子进程。
异步事件:中心时间循环监控所有I/O,并将异步事件分发给大量I/O处理器
multiprocessing模块
进程 创建启动函数:
# -*- coding: UTF-8 -*-
import multiprocessing
import time
def clock(interval)
while True:
print("The time is %s" % time.ctime())
time.sleep(self.interval)
p = multiprocessing.Process(target=clock,args(5,))
p.start()
定义为继承Process的类:
import multiprocessing
import time
class ClockProcess(multiprocessing.Process):
def __init__(self,interval):
#初始化Process
multiprocessing.Process.__init__(self)
self.interval = interval
#重新实现run()函数
def run(self):
while True:
print("The time is %s" % time.ctime())
time.sleep(self.interval)
if __name__ == '__main__':
p = ClockProcess(5)
p.start()
进程间通信
Queue
示例代码:
# -*- coding: UTF-8 -*-
import multiprocessing
import time
def consumer(input_q):
while True:
item = input_q.get()
if item == None:
break
#处理项目
print(item)
print("The time is %s" % time.ctime())
#发出信号任务完成
input_q.task_done()
def producer(sequence,output_q):
for item in sequence:
#将项目放入队列
output_q.put(item)
#建立进程
if __name__ == '__main__':
q = multiprocessing.JoinableQueue()
#运行消费者进程
cons_p1 = multiprocessing.Process(target=consumer,args=(q,))
cons_p1.daemon=True
cons_p1.start()
sequence = [1,2,3,4]
cons_p2 = multiprocessing.Process(target=consumer,args=(q,))
cons_p2.daemon=True
cons_p2.start()
sequence.append(5)
#sequence = [1,2,3,4]
producer(sequence,q)
#也可发送信号 当做标志
q.put(None)
#producer(sequence,q)
#等待所有被处理完
q.join()
q.put(item[,block[,timeout]])
将item放入队列,如果队列已满,将阻塞直到有空间为止。
block控制阻塞行为,默认为True。如果为False,
将引发Queue,Empty异常。
timeout指定在阻塞模式中等待可用空间的时长。超时后引发Queue.Full异常。
一般把每个项目序列化,通过管道或者套接字发送给进程。
规则:数量少大对象
Pipe
Pipe([duplex])在进程间创建管道,返回管道两端的Connection对象。
管道实例:
# -*- coding: UTF-8 -*-
import multiprocessing
import time
'''
'''
def consumer(pipe):
output_p,input_p = pipe
input_p.close()#关闭输入端
while True:
try:
item = output_p.recv()
#recv()接受send()返回的对象,如果另一端关闭,没有数据
#则引发EOFError异常,使用异常处理可以避免recv()挂起
except EOFError:
break
#处理项目
#也可以加上诸如:
#output_p.send()来发送消息
#变成双向通信
print(item)
print("The time is %s" % time.ctime())
def producer(sequence,input_p):
for item in sequence:
#将项目放入管道
input_p.send(item)
#也可以加上诸如:
#input_p.recv()来接受output_p的消息
#变成双向通信
#建立进程
if __name__ == '__main__':
(output_p,input_p) = multiprocessing.Pipe()
#运行消费者进程
cons_p = multiprocessing.Process(target=consumer,args=((output_p,input_p),))
cons_p.start()
#不用输出管道-关闭输出管道
output_p.close()
sequence = [1,2,3,4]
producer(sequence,input_p)
#关闭输入管道表示完成
input_p.close()
#等待所有被处理完
cons_p.join()
注意:管道端口没有使用应该关闭它,只有两端关闭才会引发EOFError异常,避免recv()挂起。
pool
进程池可以把数据处理任务放到Pool。有点类似于列表解析。如映射-规约。
pool提供numprocess 的进程数,如果达到最大值就会等待,直到某个进程结束才创建新的进程。
可以利用可迭代对象分发重复任务成进程,丢给Pool自动处理多个进程,然后再收集任务结果返回给生成器,这样相比单个进程更高效。
特别是对于多核系统,当然这样也增加了额外的通讯开销。
Pool([numprocess [,initializer[, initargs]]])
#numprocess 创建的进程数,
#initializer进程启动要执行的可调用对象,
#initargs要传递给initializer的参数元组。
'''
python
'''
# -*- coding: UTF-8 -*-
import os
import multiprocessing
import hashlib
'''
p.imap(func,iterable [,chunksize])
map()函数版本,返回可迭代对象
p.imap_unordered(func,iterable [,chunksize])
进程接受结束时,返回结果次序任意
'''
BUFSIZE = 8192
POOLSIZE = 2
def compute_digest(filename):
try:
f = open(filename,"rb")
except IOError:
return None
digest = hashlib.sha512()
while True:
chunk = f.read(BUFSIZE)
if not chunk:
break
digest.update(chunk)
f.close()
return filename,digest.digest()
def build_digest_map(topdir):
digest_pool = multiprocessing.Pool(POOLSIZE)
allfiles = (os.path.join(path,name)
for path,dirs,files in os.walk(topdir)
for name in files)
digest_map = dict(digest_pool.imap_unordered(compute_digest,allfiles,20))
digest_pool.close()
return digest_map
if __name__ == '__main__':
digest_map = build_digest_map("/home/hb/PycharmProjects/pachong")
for (k,v) in digest_map.items():
print('key=',k,'value=',v)
print(len(digest_map))
多进程总结
- 确保进程之间传递的所有数据都能够序列化
- 尽可能使用消息队列
- 单进程的函数内部,不要使用全局变量,而应该显示的传递参数
- 显示地关闭进程,多注意异常处理,防止一些奇怪的事情让整个程序崩溃
- 尽量让事情变得简单