【python爬虫 系列】10.线程池与进程池

第十节:线程池与进程池
10.1使用线程池与进程池

线程池或进程池是用于在程序中优化 线程池里有多个线程,不用我们
和简化线程/进程的使用。 创建和销毁,这样资源消耗就会
很小,减少了cpu的压力。

通过池,你可以提交任务给executor。
池由两部分组成,一部分是内部的队
列,存放着待执行的任务;另一部分
是一系列的进程或线程,用于执行这
些任务。

池的概念主要目的是为了重用:让线
程或进程在生命周期内可以多次使用。
它减少了创建创建线程和进程的开销,
提高了程序性能。重用不是必须的规
则,但它是程序员在应用中使用池的
主要原因。

1)concurrent模块
Python3.2带来了concurrent.futures 模块(不需要下载,标准库),这个模块具有线程池和进程池、
管理并行编程任务、处理非确定性的执行流程、进程/线程同步等功能。(还可以和异步耦合,目前最快方式:多进程下包含多线程,多线程里面包含异步)
此模块由以下部分组成:

concrent.futures.Executor:

这是一 个虚拟基类,提供了异步执行的方法。

submit(function, argument):  #提交

调度函数(可调用的对象)的执行,将argument作为参数传入。

map(function, argument):

将argument作为参数执行函数,以异步的方式。

shutdown(Wait=True):

发出让执行者释放所有资源的信号。

concurrent.futures.Future:

其中包括函数的异步执行。Future对象是submit任务(即带有参数的functions) 到executor的实例。

Executor是抽象类,可以通过子类访问,即线程或进程的ExecutorPools。
因为,线程或进程的实例是依赖于资源的任务,所以最好以“池”的形式将他
们组织在 一起, 作为可以重用的launcher或 executor。

2)准备工作
current.Futures模块提供了两种Executor的子类,
各自独立操作一个线程池和一个进程池。这两个子类分别是:

concurrent.futures.ThreadPoolExecutop(max_ Workers)
concurrent.futures. ProcessPoolExecutor(max_ workers) 
max_ workers参数表示最多有多少个worker并行执行任务。

单线程裸奔,线程池执行cpu密集型比较:

import concurrent
from concurrent import futures
import time 

number_list=[1,2,3,4,5,6,7,8,9,10]


def evaluate_item(x):
	#计算总和,这里只是为了消耗时间
	result_itme = count(x)
	#打印输入输出解
	return result_itme

def count(number):
	for i in range(0,100000000):
		i=i+1
	return i*number

if __name__ == '__main__':
	#顺序执行
	start_time = time.time()
	for item in number_list:
		print(evaluate_item(item))
	print("单线程裸奔time:{}".format(str(time.time()-start_time)))


	#线程池执行
	start_time_1=time.time()
	with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
		futures=[executor.submit(evaluate_item,item) for item in number_list]
		for future in concurrent.futures.as_completed(futures):
			print(future.result())
		print("线程池time:{}".format(str(time.time()-start_time_1)))

输出:

100000000
200000000
300000000
400000000
500000000
600000000
700000000
800000000
900000000
1000000000
单线程裸奔time:52.656559228897095
300000000
100000000
200000000
500000000
400000000
600000000
700000000
800000000
900000000
1000000000
线程池time:50.70752263069153

可以看到因为GIL锁的缘故,两者相差不多
而我们利用进程池就可以突破这个限制,

start_time_2=time.time()
	with concurrent.futures.ProcessPoolExecutor(max_workers=5) as executor:
		futures=[executor.submit(evaluate_item,item) for item in number_list]
		for future in concurrent.futures.as_completed(futures):
			print(future.result())
		print("进程池time:{}".format(str(time.time()-start_time_2)))

输出:进程池time:15.539561986923218

通过输出我们就可以看到,对于cpu密集型,进程池效率是很高的

Cpu密集型任务:加减乘除,解析页面
Io:输入输出,get页面,写入文件

实战:爬取电影天堂的详细信息:

get_html
运行在进程池里面
用于获取单个分类页面
get_ link
运行在线程池里边
用于获取每个分类下的书籍
参考代码 :
#多线程多进程爬取电影天堂
import requests
from bs4 import BeautifulSoup
import multiprocessing
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from lxml import etree

'''headers={'Accept-Language': 'zh-CN,zh;q=0.9',
'Connection': 'keep-alive',
'Host': 'c.v4dwkcv.com',
'If-Modified-Since': 'Fri, 28 Feb 2020 08:06:04 GMT',
'Referer': 'https://www.ygdy8.net//html/gndy/china/20200217/59701.html',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3947.100 Safari/537.36'}'''

def get_link(url):
	print("link当前进程为{}".format(multiprocessing.current_process().pid))
	html=requests.get(url)
	html.encoding="gbk"
	soup=BeautifulSoup(html.text,"lxml")
	content=soup.select("p")
	for con in content:
		print(con.get_text())

def get_html(url):
	print("html当前进程为{}".format(multiprocessing.current_process().pid))  #pid是单独的独一无二的
	html=requests.get(url)
	#print(html.text)
	threadpools=ThreadPoolExecutor(max_workers=3)
	soup=BeautifulSoup(html.text,"lxml")
	links=soup.select('b a')
	for link in links:
		link=link["href"] #每本书的link
		link="https://www.ygdy8.net/"+link
		#print(l)
		if not link.endswith("index.html"):
			#print(link)
			threadpools.submit(get_link,link)


if __name__ == '__main__':
	urls=["https://www.ygdy8.net/html/gndy/dyzz/index.html","https://www.ygdy8.net/html/gndy/index.html,https://www.ygdy8.net/html/gndy/china/index.html","https://www.ygdy8.net/html/gndy/china/index.html"]
	with ProcessPoolExecutor(max_workers=3) as executor:
		futures=[executor.submit(get_html,url) for url in urls]

输出:

html当前进程为2440
html当前进程为12024
html当前进程为7096
link当前进程为7096
link当前进程为7096
link当前进程为7096
link当前进程为2440
link当前进程为2440
link当前进程为7096
link当前进程为2440
link当前进程为2440
link当前进程为7096
link当前进程为7096
link当前进程为2440
link当前进程为7096
link当前进程为2440
link当前进程为7096
link当前进程为2440
link当前进程为7096
link当前进程为7096
link当前进程为2440
link当前进程为2440
link当前进程为2440
link当前进程为12024

共有三个进程,一个进程里面包含三个线程
讲解:我们看输出的代码里,前面的html就代表我们的进程(将军),一共有三个,分别是2440,12024和7096,而后面的link代表我们的线程(小兵),它所属于我们的三个进程(进程),下面的输出就是让每一个线程(小兵)报出它所属的进程(将军),当然是只能属于2440,12024和7096这几个将军喽。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python的multiprocessing.Pool模块和multiprocessing.dummy.Pool模块都是用于创建进程线程池的工具。 进程(multiprocessing.Pool)是一组维护在进程中的工作者,它们可以并行地执行任务。该模块是基于multiprocessing模块实现的,它通过创建多个进程来并行执行任务。 下面是一个创建进程的示例: ``` python import multiprocessing def worker(process_num): print("Process %d is working" % process_num) if __name__ == '__main__': pool = multiprocessing.Pool(processes=4) for i in range(5): pool.apply_async(worker, args=(i,)) pool.close() pool.join() ``` 上面的示例中,我们创建了一个包含4个进程进程,并向进程中提交了5个任务,每个任务调用worker函数并传递一个进程编号作为参数。我们使用apply_async方法向进程中提交任务,并使用close和join方法管理进程线程池(multiprocessing.dummy.Pool)是一组维护在线程中的工作者,它们可以并行地执行任务。该模块是基于threading模块实现的,它通过创建多个线程来并行执行任务。 下面是一个创建线程池的示例: ``` python from multiprocessing.dummy import Pool import time def worker(thread_num): print("Thread %d is working" % thread_num) time.sleep(1) if __name__ == '__main__': pool = Pool(4) for i in range(5): pool.apply_async(worker, args=(i,)) pool.close() pool.join() ``` 上面的示例中,我们创建了一个包含4个线程的线程池,并向线程池中提交了5个任务,每个任务调用worker函数并传递一个线程编号作为参数。我们使用apply_async方法向线程池中提交任务,并使用close和join方法管理线程池。 需要注意的是,线程池进程的用法基本相同,但是由于线程在Python中不能真正地并行执行,因此线程池的性能可能比进程差。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值