本文首发于知乎
python中的多进程编程方式和多线程很是类似,几乎能够说只是换了一些函数,有了以前讲过的多线程基础,不少地方我就只展现一些代码,在涉及到差异的地方再着重说明。css
本文分为以下几个部分html
有两点在写代码时须要注意python
if __name__ == '__main__'
里面import multiprocessing
import time
def myfun(num):
time.sleep(1)
print(num + 1)
if __name__ == '__main__':
for i in range(5):
p = multiprocessing.Process(target = myfun, args = (i, ))
p.start()
复制代码
另外,join is_alive daemon name current_process
等也都是同样的。编程
import multiprocessing
import requests
from bs4 import BeautifulSoup
class MyProcess(multiprocessing.Process):
def __init__(self, i):
multiprocessing.Process.__init__(self)
self.i = i
def run(self):
url = 'https://movie.douban.com/top250?start={}&filter='.format(self.i*25)
r = requests.get(url)
soup = BeautifulSoup(r.content, 'html.parser')
lis = soup.find('ol', class_='grid_view').find_all('li')
for li in lis:
title = li.find('span', class_="title").text
print(title)
if __name__ == '__main__':
for i in range(10):
p = MyProcess(i)
p.start()
复制代码
import requests
from bs4 import BeautifulSoup
from multiprocessing import Pool, current_process
def get_title(i):
print('start', current_process().name)
title_list = []
url = 'https://movie.douban.com/top250?start={}&filter='.format(i*25)
r = requests.get(url)
soup = BeautifulSoup(r.content, 'html.parser')
lis = soup.find('ol', class_='grid_view').find_all('li')
for li in lis:
title = li.find('span', class_="title").text
# return title
title_list.append(title)
print(title)
return(title_list)
if __name__ == '__main__':
pool = Pool()
for i in range(10):
pool.apply_async(get_title, (i, ))
pool.close()
pool.join()
print('finish')
复制代码
这里要说明一下安全
Pool
时,不指定进程数量,则默认为CPU核心数量Pool(10)
就能够同时开启10个进程进行抓取多进程与多线程最大的不一样在于,多进程的每个进程都有一份变量的拷贝,进程之间的操做互不影响,咱们先来看看下面的例子ruby
import multiprocessing
import time
zero = 0
def change_zero():
global zero
for i in range(3):
zero = zero + 1
print(multiprocessing.current_process().name, zero)
if __name__ == '__main__':
p1 = multiprocessing.Process(target = change_zero)
p2 = multiprocessing.Process(target = change_zero)
p1.start()
p2.start()
p1.join()
p2.join()
print(zero)
复制代码
运行结果以下多线程
Process-1 1
Process-1 2
Process-1 3
Process-2 1
Process-2 2
Process-2 3
0
复制代码
上面结果显示,新建立的两个进程各自把值增长到了3,两者不是一块儿将其加到了6的。同时,主进程的值仍是0。因此说每一个进程都是将数据拷贝过去本身作,并无将结果与其余进程共享。app
可是对于写入文件则不一样dom
import multiprocessing
import time
def write_file():
for i in range(30):
with open('try.txt', 'a') as f:
f.write(str(i) + ' ')
if __name__ == '__main__':
p1 = multiprocessing.Process(target = write_file)
p2 = multiprocessing.Process(target = write_file)
p1.start()
p2.start()
p1.join()
p2.join()
复制代码
获得的try.txt
文件内容以下async
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 0 15 2 16 17 3 4 18 19 5 20 6 21 22 8 9 23 10 11 25 26 12 13 27 28 14 29 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
复制代码
可见两个进程都将数据写入了同一份文件中。
下面咱们要讨论第一种状况,若是真的要在两个进程之间共享变量须要怎么办
这里介绍进程之间的第一种交流方式——队列。multiprocessing
模块中提供了multiprocessing.Queue
,它和Queue.Queue
的区别在于,它里面封装了进程之间的数据交流,不一样进程能够操做同一个multiprocessing.Queue
。
from multiprocessing import Process, Queue
def addone(q):
q.put(1)
def addtwo(q):
q.put(2)
if __name__ == '__main__':
q = Queue()
p1 = Process(target=addone, args = (q, ))
p2 = Process(target=addtwo, args = (q, ))
p1.start()
p2.start()
p1.join()
p2.join()
print(q.get())
print(q.get())
复制代码
运行结果以下
1
2
复制代码
这个队列是线程、进程安全的,即对队列的每一次修改中间不会被中断从而形成结果错误。
pipe
的功能和Queue
相似,能够理解成简化版的Queue
。咱们先来看下面一个例子
import random
import time
from multiprocessing import Process, Pipe, current_process
def produce(conn):
while True:
new = random.randint(0, 100)
print('{} produce {}'.format(current_process().name, new))
conn.send(new)
time.sleep(random.random())
def consume(conn):
while True:
print('{} consume {}'.format(current_process().name, conn.recv()))
time.sleep(random.random())
if __name__ == '__main__':
pipe = Pipe()
p1 = Process(target=produce, args=(pipe[0],))
p2 = Process(target=consume, args=(pipe[1],))
p1.start()
p2.start()
复制代码
结果以下
Process-1 produce 24
Process-2 consume 24
Process-1 produce 95
Process-2 consume 95
Process-1 produce 100
Process-2 consume 100
Process-1 produce 28
Process-2 consume 28
Process-1 produce 62
Process-2 consume 62
Process-1 produce 92
Process-2 consume 92
....................
复制代码
上面使用了pipe
来实现生产消费模式。
总结Queue
与pipe
之间的差异以下
Queue
使用put get
来维护队列,pipe
使用send recv
来维护队列pipe
只提供两个端点,而Queue
没有限制。这就表示使用pipe
时只能同时开启两个进程,能够像上面同样,一个生产者一个消费者,它们分别对这两个端点(Pipe()
返回的两个值)操做,两个端点共同维护一个队列。若是多个进程对pipe
的同一个端点同时操做,就会发生错误(由于没有上锁,相似线程不安全)。因此两个端点就至关于只提供两个进程安全的操做位置,以此限制了进程数量只能是2Queue
的封装更好,Queue
只提供一个结果,它能够被不少进程同时调用;而Pipe()
返回两个结果,要分别被两个进程调用Queue
的实现基于pipe
,因此pipe
的运行速度比Queue
快不少pipe
更快,当须要多个进程同时操做队列时,使用Queue
当咱们不是想维护一个队列,而只是多个进程同时操做一个数字,就须要提供一个能够在多个进程之间共享的方法,即Value
from multiprocessing import Process, Value
def f1(n):
n.value += 1
def f2(n):
n.value -= 2
if __name__ == '__main__':
num = Value('d', 0.0)
p1 = Process(target=f1, args=(num, ))
p2 = Process(target=f2, args=(num, ))
p1.start()
p2.start()
p1.join()
p2.join()
print(num.value)
复制代码
运行结果为
-1.0
复制代码
其中Value('d', 0.0)
中的d
表示双精度浮点数,更多类型能够看这里。
除了Value
,模块还提供了相似的Array
,感兴趣的读者能够去官网查看用法
既然变量在进程之间能够共享了,那么同时操做一个变量致使的不安全也随之出现。同多线程同样,进程也是经过锁来解决,并且使用方法都和多线程里相同。
lock = multiprocessing.Lock()
lock.acquire()
lock.release()
with lock:
复制代码
这些用法和功能都和多线程是同样的
另外,multiprocessing.Semaphore Condition Event RLock
也和多线程相同
专栏主页:python编程
专栏目录:目录
版本说明:软件及包版本说明