from queue import Queue as T_Queue # 线程队列
from multiprocessing import Process, Queue as P_Queue # 进程队列
1. 线程间通信
同一个进程中的多个线程,数据可以直接共享
数据收集:定义全局容器,常用的容器 - 线程队列 (定义全局队列直接使用)
添加数据: 队列.put(数据)
获取数据: 队列.get() 、 队列.get(timeout=时间)
2. 进程间通信
只能通过进程队列来进行数据交流
在一个进程中定义的进程队列,如果需要在其他进程中使用,必须通过函数的参数传递到其他进程中
添加数据: 队列.put(数据)
获取数据: 队列.get() 、 队列.get(timeout=时间)
线程池的作用
import time
from datetime import datetime
from random import randint
from threading import Thread, current_thread
from concurrent.futures import ThreadPoolExecutor
def download(name):
print(f'{name}开始下载:{datetime.now()}')
print(current_thread())
time.sleep(randint(2, 6))
print(f'{name}下载结束:{datetime.now()}')
if __name__ == '__main__':
names = [f'电影{x}' for x in range(1, 11)]
# 方案一:
# for x in range(2):
# ts = []
# for y in range(5):
# t = Thread(target=download, args=(names[x*5 + y],))
# t.start()
# ts.append(t)
# for t in ts:
# t.join()
# print(f'=======================第{x+1}批完成===========================')
# 方案二
pool = ThreadPoolExecutor(max_workers=5)
pool.map(download, names)
1. 线程池
线程池:装线程的池子(一种存放线程的容器),一个线程池中可以保存多个线程。
主要负责保存线程和给线程分配任务
线程池的工作原理:提前创建指定个数线程,然后添加多个任务,线程池自动将任务分配线程去执行
2.使用线程池
1)创建线程池对象
线程池对象 = ThreadPoolExecutor(最大线程数)
2)添加任务
a. 一次添加一个任务(任务对应的函数的参数的个数可以是任意多个)
线程池对象.submit(函数名, 实参1, 实参2, 实参3,…)
返回值是一个future对象,future对象.result()可以得到任务对应的函数的返回值
a. 一次添加多个任务(任务对应的函数必须是有且只有一个参数的函数)
线程池对象.map(函数名, 序列)
返回值是一个生成器,生成器中的元素是函数的返回值
3)关闭线程池
线程池关闭不影响已经添加的任务,只是让线程池不能再添加新的任务
线程池.shutdown()除了可以关闭线程池,还具备等待线程池中所有的任务都完成的功能
参数wait - 是否等待线程任务全部结束,默认是True
import time
from datetime import datetime
from random import randint
from concurrent.futures import ThreadPoolExecutor
def func1():
print('函数1')
return 111
def func2(a):
print(f'函数2:{a}')
return 222
def func3(x, y):
print(f'函数3:{x}, {y}')
return 333
def download(name):
print(f'{name}开始下载:{datetime.now()}')
time.sleep(randint(2, 6))
print(f'{name}下载结束:{datetime.now()}')
if __name__ == '__main__':
# 1. 创建线程池
pool = ThreadPoolExecutor(10)
# 2. 添加任务(任务添加后会马上进入等待被执行的阶段)
# 1) 任务一个一个的添加
result1 = pool.submit(func1)
result2 =pool.submit(func2, 10)
result3 =pool.submit(func3, 100, 200)
# 2)一次性添加多个任务
result4 = pool.map(func2, [10, 20, 30, 40])
result5 = pool.map(download, ['肖生克的救赎', '阿甘正传', '霸王别姬'])
# 3)关闭线程池
# 线程池关闭不影响已经添加的任务,只是让线程池不能再添加新的任务
# 线程池.shutdown()除了可以关闭线程池,还具备等待线程池中所有的任务都完成的功能
# 参数wait - 是否等待线程任务全部结束,默认是True
pool.shutdown()
print('==================')
# 4) 获取结果(了解)
print(result1.result(), result2.result(), result3.result())
print(result4, result5)
for x in result4:
print(x)
进程池
# 导入构建进程池的函数: Pool
from multiprocessing import Pool
import time
from random import randint
def func1():
print('函数1开始执行')
time.sleep(randint(1, 3))
print('函数1执行完成')
def func2(x, y):
print(f'函数2开始执行:{x, y}')
time.sleep(randint(1, 3))
print('函数2执行完成')
def func3(x):
print(f'函数3开始执行:{x}')
time.sleep(randint(1, 3))
print('函数3执行完成')
if __name__ == '__main__':
# 1. 创建进程池,指定进程数量
pool = Pool(5)
# 2. 添加任务
"""
1)一次添加一个任务
进程对象.apply(函数, 实参元组) - 用这个函数添加的多个任务在进程池中同步(串行)执行
进程对象.apply_async(函数, 实参元组) - 用这个函数添加的多个任务在进程池中异步(并行)执行
2)一次添加多个任务
进程池对象.map(函数, 序列) - 同时添加多个任务异步执行,添加完以后不用close和join
进程池对象.map_async(函数, 序列) - 同时添加多个任务异步执行,添加完以后必须使用close和join
注意:如果用带async的方法添加任务,任务添加完以后必须用进程池调用close方法,再调用join方法,
任务才会启动
"""
# 一次添加一个任务
# pool.apply_async(func1)
# pool.apply_async(func2, (100, 200))
pool.map(func3, range(100, 10000, 100))
# 3.关闭进程池
# pool.close()
# 4.等待进程池中的任务全部完成
# pool.join()
线程和进程使用总结
1. 概念:
线程
进程
线程池
进程池(几乎不用)
2. 使用的方案
1)单纯使用多个线程(Thread)
2)单纯使用多个进程(Process)
3)子进程中有子线程(Process、Thread)
4)线程池(ThreadPoolExecutor)
5)子进程中线程池(Process、ThreadPoolExecutor)
网站信息爬取练习
import requests
from concurrent.futures import ThreadPoolExecutor
from bs4 import BeautifulSoup
from re import sub
from multiprocessing import Process, Queue
import csv
q = Queue()
def get_html(page):
# 获取数据
url = f'https://cd.zu.ke.com/zufang/pg{page}/#contentList'
response = requests.get(url)
# 解析数据
soup = BeautifulSoup(response.text, 'lxml')
house_divs = soup.select('.content__list>.content__list--item')
houes_data = []
for house in house_divs:
# 名字
name = house.select_one('.twoline').text.strip()
# 价格
price = house.select_one('.content__list--item-price').text.replace(' ', '')
message = house.select_one('.content__list--item--des').text
message = sub(r'\s+', '', message).split('/')
# 标签
if len(message) == 6:
tag = message[0]
else:
tag = ''
# 位置
address = message[-5]
region = address.split('-')[0]
# 面积
area = message[-4]
# 方位
orientation = message[-3]
# 户型
house_type = message[-2]
# 楼层
floor = message[-1]
houes_data.append([name, price, region, address, area, orientation, house_type, floor, tag])
q.put(houes_data)
def save_data(q: Queue):
writer = csv.writer(open('files/租房信息.csv', 'w', newline=''))
writer.writerow(['标题', '价格', '区域', '地址', '面积', '方位', '户型', '标签'])
while True:
house_data = q.get()
if house_data == 'end':
break
writer.writerows(house_data)
print('一页数据写入成功!')
if __name__ == '__main__':
pool = ThreadPoolExecutor(10)
pool.map(get_html, range(1, 101))
p = Process(target=save_data, args=(q,))
p.start()
pool.shutdown()
q.put('end')
数据处理练习
import csv
# 练习:统计不同区域租房的平均价格(每平米)
# 区域 均价
# 成华 20元/平米
# 青羊 35元/平米
if __name__ == '__main__':
reader = csv.DictReader(open('files/租房信息.csv', encoding='utf-8', newline=''))
# {'高新': [20, 34], '双流': [12, 20, 30, 12]}
data = {}
for x in reader:
region = x['区域']
price = float(x['价格'][:-3])
area = float(x['面积'][:-1])
one_price = price / area
all_price = data.get(region, [])
all_price.append(one_price)
data[region] = all_price
new_data = {x: f'{sum(data[x])/len(data[x]):.2f}元/㎡' for x in data}
print(new_data)