前言
python调用多进程还是非常方便快捷的,他有一个专门的包(multiprocessing)。借助它,可以轻松完成从单进程到并发执行的转换。multiprocessing支持子进程、通信和共享数据、执行不同形式的同步处理,它提供了一些主要的用法,比如Pool,Queue,Lock等,我们一起去看下他的一些基本用法吧。
常用用法
multiprocessing这个包的基本用法还是非常简单的,让我们看下他的一个小例子
import multiprocessing
def process(num):
print('Process:',num)
if __name__ == '__main__':
for i in range(5):
## args表示被调用对象的位置参数元组,比如target是函数a,他有两个参数m,n,那么args就传入(m, n)即可
p = multiprocessing.Process(target=process, args=(i,)) # target为调用的函数,args中传入输入的值
p.start() # 使用start() 去掉用进程
看下其输出:
Process: 3
Process: 1
Process: 0
Process: 4
Process: 2
这个就是多进程一个非常简单的用法了。我们在平常书写代码时,为了代码的整洁和工整性,往往会使用类。没关系,他也可以继承Process类,自定义个进程类。话不多说,用例子感受一下:
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self, loop):
# 因为Process类本身也有__init__方法,这个子类相当于重写了这个方法,
# 但这样就会带来一个问题,我们并没有完全的初始化一个Process类,所以就不能使用从这个类继承的一些方法和属性,
# 最好的方法就是将继承类本身传递给Process.__init__方法,完成这些初始化操作
Process.__init__(self) #重写父类
self.loop = loop
def run(self):
for count in range(self.loop):
time.sleep(1)
print('Pid: ' + str(self.pid) + ' LoopCount: ' + str(count))
if __name__ == '__main__':
for i in range(2, 5):
p = MyProcess(i) # 子进程
### 当父进程结束后,子进程会自动被终止。
# p.daemon = True
p.start()
# p.join() # 加入join()以后,会执行到结束再关闭所有子进程
##在这一步骤的时候,执行完print语句后,主进程结束,所以下面的子进程也会自动关闭,可以防止无控制生成子进程
print("结束了QAQ") # 父进程
可以看到,上面有个属性叫daemon,他还是非常好用的,给他传入的是一个bool值,当我们设置他为True的时候,当父进程结束后,子进程会自动被终止。我们看下输出:
结束了QAQ
Process finished with exit code 0
我们发现,他只会打印一个字符串,就和他定义了一样,父进程结束后,所有的子进程也都关闭。但是这个就带来一个问题,我们正常的需求就是在父进程执行完后,他的子进程也希望执行完再关闭。别担心,它还有一个属性叫做join()用法,他的存在完全会实现我们之前的需求。加入join()方法以后,我们看下其输出:
Pid: 6308 LoopCount: 0
Pid: 6308 LoopCount: 1
Pid: 1964 LoopCount: 0
Pid: 1964 LoopCount: 1
Pid: 1964 LoopCount: 2
Pid: 5384 LoopCount: 0
Pid: 5384 LoopCount: 1
Pid: 5384 LoopCount: 2
Pid: 5384 LoopCount: 3
结束了QAQ
Process finished with exit code 0
是不是和我们的预期期望是一样的。上面就是多进程继承类的一些基本用法。
当我们仔细观察上面输出结果,我们就会发现他的一些进程发生了错位运行。这是为什么呢?其实不难理解。这是由于并行导致的,两个进程同时进行了输出,结果第一个进程的换行没有来得及输出,第二个进程就输出了结果。
那么有了问题,就想把它给解决,如何让进程之间有序的运行呢,这边就又有了一个概念叫做进程锁-Lock。
我们可以通过 Lock 来实现,在一个进程输出时,加锁,其他进程等待。等此进程执行结束后,释放锁,其他进程可以进行输出。让我们来看下他的用法吧:
from multiprocessing import Process
from multiprocessing import Lock
import time
## 加入进程锁这个概念,防止进程在运行过程中发生错位
##在我们并没有打开进程锁这个过程中,我们发现有的进程已经发生了错位,其实可以看出,让每一个进程有序的执行是很有必要的
class MyProcess(Process):
def __init__(self, loop,lock):
Process.__init__(self)
self.loop = loop
self.lock = lock
def run(self):
## 进程锁需要在此进程执行之前加锁
for count in range(self.loop):
time.sleep(0.1)
self.lock.acquire() # 加锁
print('Pid: ' + str(self.pid) + ' LoopCount: ' + str(count))
self.lock.release() # 释放锁
if __name__ == '__main__':
lock = Lock()
for i in range(10, 15):
p = MyProcess(i,lock)
p.start()
在上面的代码中,我们给print输出加入了锁机制,调用了acquire()以及release()两个方法,这样可以实现进程之间的有序进行。看下输出:
Pid: 8524 LoopCount: 0
Pid: 8460 LoopCount: 0
Pid: 8300 LoopCount: 0
Pid: 8444 LoopCount: 0
Pid: 8504 LoopCount: 0
Pid: 8460 LoopCount: 1
Pid: 8524 LoopCount: 1
Pid: 8300 LoopCount: 1
Pid: 8444 LoopCount: 1
Pid: 8504 LoopCount: 1
Pid: 8460 LoopCount: 2
Pid: 8524 LoopCount: 2
Pid: 8300 LoopCount: 2
Pid: 8444 LoopCount: 2
Pid: 8504 LoopCount: 2
Pid: 8460 LoopCount: 3
Pid: 8524 LoopCount: 3
Pid: 8300 LoopCount: 3
Pid: 8444 LoopCount: 3
Pid: 8504 LoopCount: 3
Pid: 8524 LoopCount: 4
Pid: 8460 LoopCount: 4
Pid: 8300 LoopCount: 4
Pid: 8444 LoopCount: 4
Pid: 8504 LoopCount: 4
Pid: 8524 LoopCount: 5
Pid: 8460 LoopCount: 5
Pid: 8300 LoopCount: 5
Pid: 8444 LoopCount: 5
Pid: 8504 LoopCount: 5
Pid: 8524 LoopCount: 6
Pid: 8460 LoopCount: 6
Pid: 8300 LoopCount: 6
Pid: 8444 LoopCount: 6
Pid: 8504 LoopCount: 6
Pid: 8524 LoopCount: 7
Pid: 8460 LoopCount: 7
Pid: 8300 LoopCount: 7
Pid: 8444 LoopCount: 7
Pid: 8504 LoopCount: 7
Pid: 8460 LoopCount: 8
Pid: 8524 LoopCount: 8
Pid: 8300 LoopCount: 8
Pid: 8444 LoopCount: 8
Pid: 8504 LoopCount: 8
Pid: 8460 LoopCount: 9
Pid: 8524 LoopCount: 9
Pid: 8300 LoopCount: 9
Pid: 8444 LoopCount: 9
Pid: 8504 LoopCount: 9
Pid: 8524 LoopCount: 10
Pid: 8460 LoopCount: 10
Pid: 8444 LoopCount: 10
Pid: 8504 LoopCount: 10
Pid: 8460 LoopCount: 11
Pid: 8444 LoopCount: 11
Pid: 8504 LoopCount: 11
Pid: 8460 LoopCount: 12
Pid: 8444 LoopCount: 12
Pid: 8444 LoopCount: 13
Process finished with exit code 0
可以看到,上面的进程之间有序运行了。
多进程在一定程度上能够提升爬虫的运行的效率,但是一旦网页网址请求数量过多的话,进程无限开启,这样会占用大量地资源,导致机器卡顿。这边要引入一个进程池的概念,顾名思义,就是我们把大概需要用的进程设置一个临界值,小于这个的往内输入进程,一旦等于他,就等进程池内的进程释放,再往内输入。这样子就形成了一个循环。
from multiprocessing import Pool
import requests
from requests.exceptions import ConnectionError
def scrape(url):
try:
print(requests.get(url))
except ConnectionError:
print('Error Occured ', url)
finally:
print('URL ', url, ' Scraped')
if __name__ == '__main__':
pool = Pool(processes=2) # 设置了进程池内最大容量为两个
urls = [
'http://xxxyxxx.net',
'http://www.meituan.com/',
'http://blog.csdn.net/',
'http://douban.com'
] # 要测试的网址
pool.map(scrape, urls) # 可以通过map函数调用进程,异步请求
我们来看下输出:
<Response [200]>
URL http://www.meituan.com/ Scraped
<Response [200]>
URL http://blog.csdn.net/ Scraped
<Response [200]>
URL http://douban.com Scraped
Error Occured http://xxxyxxx.net
URL http://xxxyxxx.net Scraped
Process finished with exit code 0
我们看到已经实现了代码块的请求,实现了异步请求。效率还是非常高的。
以上呢就是多进程在python内实现的一些基本用法。欢迎大家讨论,也希望本文能够对大家有所帮助,下面一篇文章内我将会使用多进程去实战爬取一个网站,给大家一个更加直观的感受。同时欢迎访问个人博客主页… …
本文参考:崔大大的多进程博客