day31
1、event事件
1.1 使用
模块:from threading import Event
实例化:e = Event()
方法:
e.wait(),线程任务调用了该方法时,该线程任务即会进入阻塞态
e.set(),线程任务调用了该方法时,其他的调用了e.wait()方法的线程任务即会从阻塞态进入就绪态、运行态
1.2 作用
控制线程的执行
2、线程池与进程池
2.1 什么是线程池、进程池
控制当前程序的允许创建的线程或进程数量
2.2 作用
保证在硬件允许的方位内创建线程或进程的数量
2.3 使用
模块:from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
实例化:
pool = ThreadPoolExecutor(n) 无参时,默认为系统CPU数量的5倍
pool = ProcessPoolExcutor(n) 无参时,默认为系统CPU数量
方法:
pool.submit('函数对象'):异步提交任务,相当于实例化一个进程或线程并start()
pool.submit('函数对象').add_done_callback('回调函数对象'):将函数对象的返回值传给回调函数,并执行
pool.shutdown():让所有线程池的任务结束后,才往下执行代码
代码:
1、异步提交pool.submit('函数对象')
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import time
pool = ThreadPoolExecutor(5)
def task():
print('线程开始执行任务')
time.sleep(10)
print('线程结束任务')
while True:
pool.submit(task)
2、异步提交任务,并立即回调函数,参数为任务函数返回值
任务函数必须有返回值,回调函数拿到的是值的地址,需要通过.result()方法读取
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import time
pool = ThreadPoolExecutor(5)
# 异步提交任务
def task():
print('线程开始执行任务')
time.sleep(10)
print('线程结束任务')
return 2
# 回调函数
def call_back(res):
print(f'任务结果:{res}')
# 注意的是,若在此处需要利用res进行赋值操作,那么不要与接收的res同名,因为在高并发时,可能会导致出现BUG
res2 = res.result() + 2
print(f'回调函数处理结果:{res2}')
return 2
for i in range(10):
pool.submit(task).add_done_callback(call_back)
pool.shutdown()
print('所有线程池的任务都结束了')
3、协程
3.1 进程、线程、协程的区别
进程:操作系统最小的资源单位
线程:操作系统最小的执行单位
携程:在单线程下实现并发,不是操作系统的资源单位
3.2 使用协程的目的
操作系统实现并发:
- 多道技术,切换+保存状态
- 遇到I/O时触发
- 缺点是CPU执行时间过长
协程实现并发:
- 通过手动模拟操作系统“多道技术”,实现切换+保存状态
- 省略操作系统切换任务时的资源消耗,提高CPU执行效率
3.3 实现协程的方法
1、yield
2、第三方模块:gevent
gevent的作用:监听I/O操作,并自动实现切换任务
使用gevent的目的:为了实现单线程下,遇到I/O时,切换+保存任务
方法:
monkey.patch_all():可以监听该程序下所有的gevent可以识别的I/O操作,但是time.sleep()这样的I/O不能识别,因此本段代码就是一个补丁,将之写在 import time之前即可
spawn('函数对象',函数参数):执行对应的任务,并且可以在I/O时保存当前任务状态并切换到下一个任务
join():spawm()执行任务完成后,线程才会结束
joinall((函数对象,函数对象,)):等待所有的任务结束,才会结束线程
from gevent import spawn,joinall # # 用于做切换 + 保存状态
from gevent import monkey
monkey.patch_all() # 可以监听该程序下所有的I/O操作
def func1():
print('1')
# IO操作
time.sleep(1)
def func2():
print('2')
time.sleep(3)
def func3():
print('3')
time.sleep(5)
start_time = time.time()
s1 = spawn(func1)
s2 = spawn(func2)
s3 = spawn(func3)
# s2.join() # 发送信号,相当于等待自己 (在单线程的情况下)
# s1.join()
# s3.join()
# 必须传序列类型
joinall([s1, s2, s3])
end_time = time.time()
print(end_time - start_time)
4、多线程爬取梨视频
import requests
import re
from concurrent.futures import ThreadPoolExecutor
import uuid
pool = ThreadPoolExecutor(100)
# 主页
URL=r'https://www.pearvideo.com/'
def get_page(url):
'''发送请求,获取网页信息'''
response = requests.get(url)
return response
def get_vedio_page_url_list(response):
'''提取网页信息,获取所有视频信息页url'''
re_format = '<a href="video_(.*?)".*?>'
res = re.findall(re_format,response,re.S)
for i in range(len(res)):
res[i] = 'https://www.pearvideo.com/video_' + res[i]
url_list = res
return url_list
def get_video_data_url_list(video_page_url_list):
'''通过视频信息也链接,发送请求并提取所有视频数据url'''
video_data_url_list = []
for url in video_page_url_list:
response = get_page(url)
print(response)
re_format = 'srcUrl="(.*?)"'
video_data_url = re.findall(re_format,response.text,re.S)[0]
print(video_data_url)
video_data_url_list.append(video_data_url)
return video_data_url_list
def download(res):
'''提取视频二进制数据,保存到本地'''
res = res.result() # 作为回调函数时使用
name = str(uuid.uuid4())
print(f'{name}.mp4视频开始保存...')
with open(f'{name}.mp4','wb') as fw:
fw.write(res.content)
if __name__ == '__main__':
# 访问主页
res = get_page(URL)
#提取所有的视频详情页链接url
video_page_url_list = get_vedio_page_url_list(res.text)
# 提取所有的视频数据链接url
video_data_url_list = get_video_data_url_list(video_page_url_list)
# 多线程下载视频保存到本地
for url in video_data_url_list:
movie_data_url = pool.submit(get_page,url).add_done_callback(download)
pool.shutdown(wait=True)

浙公网安备 33010602011771号