Python爬虫进阶六之多进程的用法

包 multiprocessing。multiprocessing支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。

  • Process
  • Lock
  • Semaphore
  • Queue
  • Pipe
  • Pool

Process

基本使用

在multiprocessing中,每一个进程都用一个Process类来表示。

Process([group [, target [, name [, args [, kwargs]]]]])
  • target表示调用对象,你可以传入方法的名字
  • args表示被调用对象的位置参数元组,比如target是函数a,他有两个参数m,n,那么args就传入(m, n)即可
  • kwargs表示调用对象的字典
  • name是别名,相当于给这个进程取一个名字
  • group分组,实际上不使用

 

import multiprocessing
 
def process(num):
    print 'Process:', num
 
if __name__ == '__main__':
    for i in range(5):
        p = multiprocessing.Process(target=process, args=(i,))
        p.start()

 

另外还可以通过 cpu_count() 方法、 active_children() 方法获取机器 CPU 核心数量、所有运行的进程。

import multiprocessing
import time
 
def process(num):
    time.sleep(num)
    print 'Process:', num
 
if __name__ == '__main__':
    for i in range(5):
        p = multiprocessing.Process(target=process, args=(i,))
        p.start()
 
    print('CPU number:' + str(multiprocessing.cpu_count()))
    for p in multiprocessing.active_children():
        print('Child process name: ' + p.name + ' id: ' + str(p.pid))
 
    print('Process Ended')
Process: 0
CPU number:8
Child process name: Process-2 id: 9641
Child process name: Process-4 id: 9643
Child process name: Process-5 id: 9644
Child process name: Process-3 id: 9642
Process Ended
Process: 1
Process: 2
Process: 3
Process: 4

自定义类

另外可以继承Process类,自定义进程类,实现run方法即可。

from multiprocessing import Process
import time
 
 
class MyProcess(Process):
    def __init__(self, loop):
        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.start()

实现了run方法。打印出来了进程号和参数。

属性deamon

设置为True,当父进程结束后,子进程会自动被终止。

if __name__ == '__main__':
    for i in range(2, 5):
        p = MyProcess(i)
        p.daemon = True
        p.start()
 
 
    print 'Main process Ended!'

结果:

Main process Ended!

结果很简单,主进直接输出一句话结束,所以在这时也直接终止了子进程的运行。这样可以有效防止无控制地生成子进程。

让所有子进程都执行完了然后再结束呢?加入join()方法即可。

if __name__ == '__main__':
    for i in range(2, 5):
        p = MyProcess(i)
        p.daemon = True
        p.start()
        p.join()
 
 
    print 'Main process Ended!'

发现所有子进程都执行完毕之后,父进程最后打印出了结束的结果。

Lock

 

from multiprocessing import Process, 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()

 

Semaphore

信号量,可以控制临界资源的数量

from multiprocessing import Process, Semaphore, Lock, Queue
import time
 
buffer = Queue(10)
empty = Semaphore(2)
full = Semaphore(0)
lock = Lock()
 
class Consumer(Process):
 
    def run(self):
        global buffer, empty, full, lock
        while True:
            full.acquire()
            lock.acquire()
            buffer.get()
            print('Consumer pop an element')
            time.sleep(1)
            lock.release()
            empty.release()
 
 
class Producer(Process):
    def run(self):
        global buffer, empty, full, lock
        while True:
            empty.acquire()
            lock.acquire()
            buffer.put(1)
            print('Producer append an element')
            time.sleep(1)
            lock.release()
            full.release()
 
 
if __name__ == '__main__':
    p = Producer()
    c = Consumer()
    p.daemon = c.daemon = True
    p.start()
    c.start()
    p.join()
    c.join()
    print 'Ended!'

两个信号量,一个代表缓冲区空余数,一个表示缓冲区占用数。

生产者Producer使用empty.acquire()方法来占用一个缓冲区位置,然后缓冲区空闲区大小减小1,接下来进行加锁,对缓冲区进行操作。然后释放锁,然后让代表占用的缓冲区位置数量+1,消费者则相反。

Queue

在上面的例子中我们使用了Queue,作为进程通信的共享队列使用。

如果把Queue换成普通的list,是完全起不到效果的。即在一个进程中改变了这个list,在另一个进程也不能获取到它的状态。

因此进程间的通信,队列需要用Queue。当然这里的队列指的是 multiprocessing.Queue

get方法有两个参数,blocked和timeout,意思为阻塞和超时时间。默认blocked是true,即阻塞式。

当一个队列为空的时候如果再用get取则会阻塞,所以这时候就需要吧blocked设置为false,即非阻塞式,实际上它就会调用get_nowait()方法,此时还需要设置一个超时时间,在这么长的时间内还没有取到队列元素,那就抛出Queue.Empty异常。

当一个队列为满的时候如果再用put放则会阻塞,所以这时候就需要吧blocked设置为false,即非阻塞式,实际上它就会调用put_nowait()方法,此时还需要设置一个超时时间,在这么长的时间内还没有放进去元素,那就抛出Queue.Full异常。

另外队列中常用的方法

Queue.qsize() 返回队列的大小 ,不过在 Mac OS 上没法运行。

Queue.empty() 如果队列为空,返回True, 反之False

Queue.full() 如果队列满了,返回True,反之False

Queue.get([block[, timeout]]) 获取队列,timeout等待时间

Queue.get_nowait() 相当Queue.get(False)

Queue.put(item) 阻塞式写入队列,timeout等待时间

Queue.put_nowait(item) 相当Queue.put(item, False)

Pipe

管道,一端发一端收。

Pipe可以是单向(half-duplex),也可以是双向(duplex)。通过mutiprocessing.Pipe(duplex=False)创建单向管道 (默认为双向)。单向管道只允许管道一端的进程输入,而双向管道则允许从两端输入。

from multiprocessing import Process, Pipe
  
class Consumer(Process):
    def __init__(self, pipe):
        Process.__init__(self)
        self.pipe = pipe
 
    def run(self):
        self.pipe.send('Consumer Words')
        print 'Consumer Received:', self.pipe.recv()
 
 class Producer(Process):
    def __init__(self, pipe):
        Process.__init__(self)
        self.pipe = pipe
 
    def run(self):
        print 'Producer Received:', self.pipe.recv()
        self.pipe.send('Producer Words')
  
if __name__ == '__main__':
    pipe = Pipe()
    p = Producer(pipe[0])
    c = Consumer(pipe[1])
    p.daemon = c.daemon = True
    p.start()
    c.start()
    p.join()
    c.join()
    print 'Ended!'

这里声明了一个默认为双向的管道,然后将管道的两端分别传给两个进程。

Producer Received: Consumer Words
Consumer Received: Producer Words
Ended!

Pool

Process动态成生多个进程,十几个还好,但如果是上百个,上千个目标。进程池。
Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来它。

要了解阻塞和非阻塞。

阻塞即要等到回调结果出来,在有结果之前,当前进程会被挂起。

Pool的用法有阻塞和非阻塞两种方式。非阻塞即为添加进程后,不一定非要等到进程执行完就添加其他进程运行,阻塞则相反。

from multiprocessing import Lock, Pool
import time
  
def function(index):
    print 'Start process: ', index
    time.sleep(3)
    print 'End process', index
 
if __name__ == '__main__':
    pool = Pool(processes=3)
    for i in xrange(4):
        pool.apply_async(function, (i,))
 
    print "Started processes"
    pool.close()
    pool.join()
    print "Subprocess done."

apply_async方法,即非阻塞。

发现在这里添加三个进程进去后,立马就开始执行,不用非要等到某个进程结束后再添加新的进程进去。

 

from multiprocessing import Lock, Pool
import time
 
 
def function(index):
    print 'Start process: ', index
    time.sleep(3)
    print 'End process', index
 
 
if __name__ == '__main__':
    pool = Pool(processes=3)
    for i in xrange(4):
        pool.apply(function, (i,))
 
    print "Started processes"
    pool.close()
    pool.join()
    print "Subprocess done."

非组塞需要把apply_async改成apply即可。

Start process:  0
End process 0
Start process:  1
End process 1
Start process:  2
End process 2
Start process:  3
End process 3
Started processes
Subprocess done.

下面对函数进行解释:

apply_async(func[, args[, kwds[, callback]]]) 它是非阻塞,apply(func[, args[, kwds]])是阻塞的。

close() 关闭pool,使其不在接受新的任务。

terminate() 结束工作进程,不在处理未完成的任务。

join() 主进程阻塞,等待子进程的退出, join方法要在close或terminate之后使用。

每个进程可以返回一个结果。apply或apply_async可以拿到这个结果。

from multiprocessing import Lock, Pool
import time

def function(index):
    print 'Start process: ', index
    time.sleep(3)
    print 'End process', index
    return index

if __name__ == '__main__':
    pool = Pool(processes=3)
    for i in xrange(4):
        result = pool.apply_async(function, (i,))
        print result.get()
    print "Started processes"
    pool.close()
    pool.join()
    print "Subprocess done."

结果:

Start process:  0
End process 0
0
Start process:  1
End process 1
1
Start process:  2
End process 2
2
Start process:  3
End process 3
3
Started processes
Subprocess done.

有一个非常好用的map方法。

如果你现在有一堆数据要处理,每一项都需要经过一个方法来处理,那么map非常适合。

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=3)
    urls = [
        'https://www.baidu.com',
        'http://www.meituan.com/',
        'http://blog.csdn.net/',
        'http://xxxyxxx.net'
    ]
    pool.map(scrape, urls)

初始化一个Pool,指定进程数为3,如果不指定,那么会自动根据CPU内核来分配进程数。

然后有一个链接列表,map函数可以遍历每个URL,然后对其分别执行scrape方法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值