并发编程—线程
目录
1、线程基础
1.1 什么是线程
操作系统中能够独立运行的基本单位
1.2 线程的出现
为了弥补线程的缺陷,引入了线程
进程缺陷:
1、进程通过一时间只能左一件事
2、若遇到IO,只能挂起/
硬件条件:
多对称处理机(SMP),可以满足多个运行单位
1.3 使用线程的好处
开启进程:开辟一个内存空间,占用内存资源,自带一个线程
开启线程:一个进程可以开启多个线程,线程的开销远小于进程
1.4 守护线程
2、线程创建与执行
1、自定义任务
import time
from threading import Thread
def task():
print('线程开启')
time.sleep(1)
print('线程结束')
# t = Thread() # 线程对象可以放在main外面实例化,但是进程对象不行
if __name__ == '__main__':
t = Thread(target=task)
t.start()
2、自定义类
import time
from threading import Thread
class MyThread(Thread):
def run(self):
print('线程开启')
time.sleep(1)
print('线程结束')
t = MyThread()
t.start()
3、多线程
3.1 什么是多线程
多个线程同时运行,需要注意的是,多线程只能并发,执行顺序与启动顺序无关
实现方法:
import time
from threading import Thread
def task():
print(f'任务执行')
for i in range(10):
t = Thread(target=task)
t.start()
3.2 线程池
见python标准模块concurrent.futures.md
3.3 多线程应用实例:爬取梨视频
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)
4、线程间通信
4.1 线程数据
进程是操作系统最小的资源单位,进程下可以开启多个线程
因此线程之间数据是共享的
4.2 什么是线程间通信
同一进程下,由于多个线程处理同一资源,但是各线程的执行任务任务不同
4.3 生产者与消费者
4.3.1 什么是生产者、消费者
生产者:产生数据的线程
消费者:使用数据的线程
4.3.1 单生产单消费
一个生产者和一个消费者
4.3.2 多生产多消费
多个生产者和多个消费者
代码:
import time
import random
from concurrent.futures import ThreadPoolExecutor
p = ThreadPoolExecutor(10)
num_list = []
# 生产者任务
def create_data(i):
while True:
if len(num_list) >=10:
time.sleep(1)
continue
num = random.random()
num_list.append(num)
print(f'{i}生产了数字:{num}')
time.sleep(1)
# 消费者任务
def get_data(i):
while True:
if not len(num_list):
continue
num = num_list[-1]
del num_list[-1]
print(f'{i}消耗掉了数字:{num}')
# 创建单生产者与单消费者
# p.submit(create_data,1)
# p.submit(get_data,2)
# 创建多生产者与多消费者
for i in range(10):
p.submit(create_data, i)
p.submit(get_data,i)
5、线程互斥锁
见互斥锁