一 multiprocessing模块介绍
python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。Python提供了multiprocessing。
multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。
二 Process类的介绍与使用
通过创建一个 Process 对象然后调用它的 start() 方法来生成进程。 Process 和 threading.Thread API 相同。
Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)
#参数说明
1 group参数未使用,值始终为None
2 target表示调用对象,即子进程要执行的任务
3 args表示调用对象的位置参数元组,args=(1,2,'anne',)是一个元组形式,x结尾必须有逗号
4 kwargs表示调用对象的字典,kwargs={'name':'anne','age':18}
5 name为子进程的名称
三Process类的方法
run()
表示进程活动的方法。
你可以在子类中重载此方法。标准 run() 方法调用传递给对象构造函数的可调用对象作为目标参数(如果有),分别从 args 和 kwargs 参数中获取顺序和关键字参数。
start()
启动进程活动。
这个方法每个进程对象最多只能调用一次。它会将对象的 run() 方法安排在一个单独的进程中调用。
join([timeout])
如果可选参数 timeout 是 None (默认值),则该方法将阻塞,直到调用 join() 方法的进程终止。如果 timeout 是一个正数,它最多会阻塞 timeout 秒。请注意,如果进程终止或方法超时,则该方法返回 None 。检查进程的 exitcode 以确定它是否终止。
p1.join() #表示执行完进程P1再执行主进程
主进程等待子进程,等待的是主进程,所以等待的总时间是子进程中耗费时间最长的那个进程运行的时间
一个进程可以被 join 多次。
进程无法join自身,因为这会导致死锁。尝试在启动进程之前join进程是错误的。
daemon()方法:守护进和,
p2.daemon=True #守护进程,表示主进程结束p2就结束
进程间的通信 Queue(对列)Pipe(通道)
进程之间数据不共享, 虽然可以用文件共享数据实现进程间通信,但问题是:1)效率低(共享数据基于文件,而文件是硬盘上的数据) 2)需要自己加锁处理
mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道
创建队列的类(底层就是以管道和锁定的方式实现):
Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。
相当于创建立了一个堆栈,通过put方法存入数据,get 方法取出数据 ,empty 方法判断队列是否为空
创建管道的类:
Pipe([duplex]):在进程之间创建一条管道,并返回元组(conn1,conn2),其中conn1,conn2表示管道两端的连接对象,强调一点:必须在产生Process对象之前产生管道
参数介绍:
dumplex:默认管道是全双工的,如果将duplex射成False,conn1只能用于接收,conn2只能用于发送。
# 管道可以用于双向通信,利用通常在客户端/服务器中使用的请求/响应模型或远程过程调用,就可以使用管道编写与进程交互的程序
from multiprocessing import Process,Pipe
import time,os
def adder(p,name):
server,client=p
client.close()
while True:
try:
x,y=server.recv()
except EOFError:
server.close()
break
res=x+y
server.send(res)
print('server done')
if __name__ == '__main__':
server,client=Pipe()
c1=Process(target=adder,args=((server,client),'c1'))
c1.start()
server.close()
client.send((10,20))
print(client.recv())
client.close()
c1.join()
print('主进程')
#注意:send()和recv()方法使用pickle模块对对象进行序列化。
Pool 进程池类
可以创建一个进程池
class multiprocessing.pool.Pool([processes[, initializer[, initargs[, maxtasksperchild[, context]]]]])
参数
1 numprocess:要创建的进程数,如果省略,将默认使用cpu_count()的值
2 initializer:是每个工作进程启动时要执行的可调用对象,默认为None
3 initargs:是要传给initializer的参数组
一个进程池对象,它控制可以提交作业的工作进程池。它支持带有超时和回调的异步结果,以及一个并行的 map 实现。创建进程池的类:如果指定numprocess为3,则进程池会从无到有创建三个进程,然后自始至终使用这三个进程去执行所有任务,不会开启其他进程
Pool内的进程数默认是cpu核数,假设为4(查看方法os.cpu_count())
开启6个客户端,会发现2个客户端处于等待状态
在每个进程内查看pid,会发现pid使用为4个,即多个客户端公用4个进程
主要方法:
1 p.apply(func [, args [, kwargs]])
在一个池工作进程中执行func(*args,**kwargs),然后返回结果。
需要强调的是:此操作并不会在所有池工作进程中并执行func函数。如果要通过不同参数并发地执行func函数,必须从不同线程调用p.apply()函数或者使用p.apply_async()
2 p.apply_async(func [, args [, kwargs]]):
在一个池工作进程中执行func(*args,**kwargs),然后返回结果。
此方法的结果是AsyncResult类的实例,callback是可调用对象,接收输入参数。当func的结果变为可用时,
将理解传递给callback。callback禁止执行任何阻塞操作,否则将接收其他异步操作中的结果。
3 p.close():关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
4 P.jion():等待所有工作进程退出。此方法只能在close()或teminate()之后调用
from multiprocessing import Pool
import requests
import json
import os
def get_page(url):
print('<进程%s> get %s' %(os.getpid(),url))
respone=requests.get(url)
if respone.status_code == 200:
return {'url':url,'text':respone.text}
def pasrse_page(res):
print('<进程%s> parse %s' %(os.getpid(),res['url']))
parse_res='url:<%s> size:[%s]\n' %(res['url'],len(res['text']))
with open('db.txt','a') as f:
f.write(parse_res)
if __name__ == '__main__':
urls=[
'https://www.baidu.com',
'https://www.python.org',
'https://www.openstack.org',
'https://help.github.com/',
'http://www.sina.com.cn/'
]
p=Pool(3)
res_l=[]
for url in urls:
res=p.apply_async(get_page,args=(url,),callback=pasrse_page)
res_l.append(res)
p.close()
p.join()
print([res.get() for res in res_l]) #拿到的是get_page的结果,其实完全没必要拿该结果,该结果已经传给回调函数处理了
实例
做的阿里千牛的自动回复程序
import RobotCL,time,pythoncom
import sys
from ctypes import *
from ctypes.wintypes import MSG
from ctypes.wintypes import DWORD
from KeyboardHook import *
from multiprocessing import Process,Queue
q = Queue()
#定义钩子处理函数
#参数nCode是hook类型 ,wParam和lParam参数的值依赖于Hook代码包含了关于发送或者接收消息的信息。
#keyLogger
def hookProc(nCode, wParam, lParam):
print ('nCode=',nCode, 'wParam=',wParam, 'lParam=',lParam)
print(user32.GetKeyState(0x45))
if wParam is not WM_KEYDOWN: #如果不是按键消息就放过
user32.CallNextHookEx(keyLogger.hooked, nCode, wParam, lParam)
return False
if(CTRL_CODE == int(0xFFFFFFFF&lParam[0])):#如果按下CTRL就结束
print("Ctrl pressed, call uninstallHook()")
keyLogger.uninstallHookProc()
user32.CallNextHookEx(keyLogger.hooked, nCode, wParam, lParam)
# q.put(True)
keyLogger.endKeyLog() #结束消息循环
return True
#sys.exit(-1)
hookedKey = chr(0xFFFFFFFF&lParam[0]) #chr函数返回指定的字符代码的对应字符
print("按键",hookedKey)
#q.put(False)
#print("hookProcisempty=", q.empty())
user32.CallNextHookEx(keyLogger.hooked, nCode, wParam, lParam)
return False
#实例化一个hook类
keyLogger = KeyLogger()
hp=hookProc
#取得回调函数指针
pointer = keyLogger.getFPTR(hp)
def hook(q):
if keyLogger.installHookProc(pointer):
print("installed keyLogger")
print('keyLogger=',keyLogger)
pythoncom.PumpMessages(10000)
#keyLogger.startKeyLog()
q.put(True)
print("hookisempty=", q.empty())
def Robot(q):
while True:
print('开始')
RobotA=RobotCL.RobotCL() #定义机器人
RobotA.Activate_window() #激活窗口
if RobotA.Select_friends(): #有新信息
RobotA.Activate_window() # 激活窗口
RobotA.Input_text() #输入并发送并关闭对话
time.sleep(10)
if not q.empty():
isctrl=q.get()
print("isctrl=",isctrl)
if isctrl:break
print('重新开始')
def test(q):
while True:
print("testisempty=", q.empty())
if not q.empty():
isctrl = q.get()
print("isctrl=", isctrl)
if isctrl:
break
time.sleep(5)
print('重新开始')
if __name__ == '__main__':
p1=Process(target=hook,args=(q,)) #必须加,号
#p2=Process(target=test,args=(q,))
p2=Process(target=Robot,args=(q,))
p2.daemon=True #守护进程,主进程结束p2就结束
p1.start()
p2.start()
p1.join()
#Ap2.join()
#q.put(True)
test(q)
while True:
if not q.empty():
print("q的内容", q.get())
if q.empty():
print('q打空')
break
print('主线程')