多线程
1.多线程
线程是真正工作的单位,进程是为线程提供资源的单位
1.1 进程与线程
类比:
-
一个工厂,至少有一个车间,一个车间中至少有一个工人,最终是工人在工作。
-
一个程序,至少有一个进程,一个进程中至少有一个线程,最终是线程在工作。
一个串行的代码就是一个程序,在使用python xx.py 运行时,内部就创建一个进程(主进程),在进程中创建了一个线程(主线程),由线程逐行运行代码。
进程和线程:
- 线程,是计算机中可以被cpu调度的最小单元(真正在工作)。
- 进程,是计算机资源分配的最小单元(进程为线程提供资源)。
一个进程中可以有多个线程,同一个进程中的线程可以共享此进程中的资源。
串行的程序:排队逐一执行,前面未完成,后面也无法继续。
import time
result = 0
for i in range(100000000):
result += i
print(result)
import time
import requests
url_list = [
("东北F4模仿秀.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f570000bvbmace0gvch7lo53oog"),
("卡特扣篮.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f3e0000bv52fpn5t6p007e34q1g"),
("罗斯mvp.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg")
]
print(time.time())
for file_name, url in url_list:
res = requests.get(url)
with open(file_name, mode='wb') as f:
f.write(res.content)
print(file_name, time.time())
然而,通过 进程 和 线程 都可以将 串行
的程序变为并发
,上面的示例代码就是串行
1.1.1 多线程
基于多线程对上述串行示例进行优化:
- 一个工厂,创建一个车间,这个车间中创建 3个工人,并行处理任务。
- 一个程序,创建一个进程,这个进程中创建 3个线程,并行处理任务。
import time
import requests
import threading
url_list = [
("夜晚.mp4", "https://assets.mixkit.co/videos/11/11-720.mp4"),
("汽车.mp4", "https://assets.mixkit.co/videos/43925/43925-720.mp4"),
("马路.mp4", "https://assets.mixkit.co/videos/1755/1755-720.mp4")
]
def task(file_name, url):
res = requests.get(url)
with open(file_name,mode="wb") as f:
f.write(res.content)
print(time.time())
for name,url in url_list:
# 创建线程,让每个线程都去执行task函数(参数不同)
t = threading.Thread(target=task,args=(name,url))
t.start()
1.1.2 多进程
基于多进程对上述串行示例进行优化:
- 一个工厂,创建 三个车间,每个车间 一个工人(共3人),并行处理任务。
- 一个程序,创建 三个进程,每个进程 一个线程(共3人),并行处理任务。
import time
import requests
import multiprocessing
url_list = [
("夜晚.mp4", "https://assets.mixkit.co/videos/11/11-720.mp4"),
("汽车.mp4", "https://assets.mixkit.co/videos/43925/43925-720.mp4"),
("马路.mp4", "https://assets.mixkit.co/videos/1755/1755-720.mp4")
]
def task(file_name, url):
res = requests.get(url)
with open(file_name,mode="wb") as f:
f.write(res.content)
print(time.time())
if __name__ == '__main__':
print("程序开始:",time.time())
for name, url in url_list:
t = multiprocessing.Process(target=task, args=(name, url))
t.start()
不管是多线程还是多进程,速度都会提高很多,但是多进程 的开销比 多线程 的开销大
1.1.3 GIL锁
GIL, 全局解释器锁(Global Interpreter Lock),是CPython解释器特有一个东西,让一个进程中同一个时刻只能有一个线程可以被CPU调用。
如果程序想利用 计算机的多核优势,让CPU同时处理一些任务,适合用多进程开发(即使资源开销大)。
如果程序不利用 计算机的多核优势,适合用多线程开发。
常见的程序开发中,计算操作需要使用CPU多核优势,IO操作不需要利用CPU的多核优势,所以:
- 计算密集型,用多进程,例如:大量的数据计算【累加计算示例】。
- IO密集型,用多线程,例如:文件读写、网络数据传输【下载视频示例】。
累加计算示例(计算密集型):
-
串行处理
import time start = time.time() result = 0 for i in range(100000000): result += i print(result) end = time.time() print("耗时:", end - start) # 耗时: 9.522780179977417
-
多进程处理
import time import multiprocessing def task(start, end, queue): result = 0 for i in range(start, end): result += i queue.put(result) if __name__ == '__main__': queue = multiprocessing.Queue() start_time = time.time() p1 = multiprocessing.Process(target=task, args=(0, 50000000, queue)) p1.start() p2 = multiprocessing.Process(target=task, args=(50000000, 100000000, queue)) p2.start() v1 = queue.get(block=True) #阻塞 v2 = queue.get(block=True) #阻塞 print(v1 + v2) end_time = time.time() print("耗时:", end_time - start_time) # 耗时: 2.6232550144195557
在程序开发中 多线程 和 多进程 是可以结合使用,例如:创建2个进程(建议与CPU个数相同),每个进程中创建3个线程。
import multiprocessing
import threading
def thread_task():
pass
def task(start, end):
t1 = threading.Thread(target=thread_task)
t1.start()
t2 = threading.Thread(target=thread_task)
t2.start()
t3 = threading.Thread(target=thread_task)
t3.start()
if __name__ == '__main__':
p1 = multiprocessing.Process(target=task, args=(0, 50000000))
p1.start()
p2 = multiprocessing.Process(target=task, args=(50000000, 100000000))
p2.start()
2 多线程开发
import threading
def task(arg):
pass
# 创建一个Thread对象(线程),并封装线程被CPU调度时应该执行的任务和相关参数。
t = threading.Thread(target=task,args=('xxx',))
# 线程准备就绪(等待CPU调度),代码继续向下执行。
t.start()
print("继续执行...") # 主线程执行完所有代码,不结束(等待子线程)
线程的常见方法:
-
t.start()
,当前线程准备就绪(等待CPU调度,具体时间是由CPU来决定)。import threading loop = 10000000 number = 0 def _add(count): global number for i in range(count): number += 1 # 创建一个子线程 t = threading.Thread(target=_add,args=(loop,)) # 线程准备就绪,等待被cpu调度 t.start() # 主线程继续执行,但不结束,等待子线程执行完就结束 print(number)
-
t.join()
,等待当前线程的任务执行完毕后再向下继续执行。import threading number = 0 def _add(): global number for i in range(10000000): number += 1 # 创建一个子线程 t = threading.Thread(target=_add) # 线程准备就绪,等待被cpu调度 t.start() # 主线程卡这里,等待子线程执行完后,继续往下执行 t.join() # 主线程等待中... print(number)
import threading number = 0 def _add(): global number for i in range(10000000): number += 1 def _sub(): global number for i in range(10000000): number -= 1 t1 = threading.Thread(target=_add) # 创建t1子线程 t2 = threading.Thread(target=_sub) # 创建t2子线程 t1.start() # t1子线程准备完毕,等待cpu被调度 t1.join() # t1线程执行完毕,才继续往后走 t2.start() # t2子线程准备完毕,等待cpu被调度 t2.join() # t2线程执行完毕,才继续往后走 print(number)
import threading loop = 10000000 number = 0 def _add(count): global number for i in range(count): number += 1 def _sub(count): global number for i in range(count): number -= 1 t1 = threading.Thread(target=_add, args=(loop,)) # 创建t1子线程 t2 = threading.Thread(target=_sub, args=(loop,)) # 创建t2子线程 t1.start() # t1子线程准备完毕,等待cpu被调度 t2.start() # t2子线程准备完毕,等待cpu被调度 # 此时,涉及到Cpython中的GIL锁的问题,在同一个进程中,只能有一个线程被cpu调度,可以是t1执行一会,然后t2执行,容易造成数据混乱 t1.join() # t1线程执行完毕,才继续往后走 t2.join() # t2线程执行完毕,才继续往后走 print(number)
-
t.setDaemon(布尔值)
,守护线程(必须放在start之前)-
t.setDaemon(True)
,设置为守护线程,主线程执行完毕后,子线程也自动关闭。 -
t.setDaemon(False)
,设置为非守护线程,主线程等待子线程,子线程执行完毕后,主线程才结束。(默认)
import threading import time def task(arg): time.sleep(5) print('任务') t = threading.Thread(target=task, args=(11,)) t.setDaemon(True) # True/False t.start() print('END')
-
-
线程名称的设置和获取
import threading def task(arg): # 获取当前执行此代码的线程 name = threading.current_thread().getName() print(name) for i in range(10): t = threading.Thread(target=task, args=(11,)) t.setName('alex-{}'.format(i)) t.start()
-
自定义线程类,直接将线程需要做的事写到run方法中。
import threading class MyThread(threading.Thread): def run(self): print('执行此线程', self._args) t = MyThread(args=(100,)) t.start()
import threading class VideoThread(threading.Thread): def run(self): file_name, video_url = self._args res = requests.get(video_url) with open(file_name, mode='wb') as f: f.write(res.content) url_list = [ ("夜晚.mp4", "https://assets.mixkit.co/videos/11/11-720.mp4"), ("汽车.mp4", "https://assets.mixkit.co/videos/43925/43925-720.mp4"), ("马路.mp4", "https://assets.mixkit.co/videos/1755/1755-720.mp4") ] for item in url_list: t = VideoThread(args=(item[0],item[1])) t.start()
3 线程安全
一个进程中可以有多个线程,且线程共享所有进程中的资源。
多个线程同时去操作一个"东西",可能会存在数据混乱的情况,例如:
-
示例1
import threading loop = 10000000 number = 0 def _add(count): global number for i in range(count): number += 1 def _sub(count): global number for i in range(count): number -= 1 t1 = threading.Thread(target=_add, args=(loop,)) t2 = threading.Thread(target=_sub, args=(loop,)) t1.start() t2.start() t1.join() # t1线程执行完毕,才继续往后走 t2.join() # t2线程执行完毕,才继续往后走 print(number)
加锁
import threading lock_object = threading.RLock() loop = 10000000 number = 0 def _add(count): lock_object.acquire() # 加锁 global number for i in range(count): number += 1 lock_object.release() # 释放锁 def _sub(count): lock_object.acquire() # 申请锁(等待) global number for i in range(count): number -= 1 lock_object.release() # 释放锁 t1 = threading.Thread(target=_add, args=(loop,)) t2 = threading.Thread(target=_sub, args=(loop,)) t1.start() t2.start() t1.join() # t1线程执行完毕,才继续往后走 t2.join() # t2线程执行完毕,才继续往后走 print(number)
-
示例2:
import threading num = 0 def task(): global num for i in range(1000000): num += 1 print(num) for i in range(2): t = threading.Thread(target=task) t.start()
基于显式实现
acquire()
和release()
import threading num = 0 lock_object = threading.RLock() def task(): print("开始") lock_object.acquire() # 第1个抵达的线程进入并上锁,其他线程就需要再此等待。 global num for i in range(1000000): num += 1 lock_object.release() # 线程出去,并解开锁,其他线程就可以进入并执行了 print(num) for i in range(2): t = threading.Thread(target=task) t.start()
使用上下文管理器
with
来自动处理锁的获取和释放import threading num = 0 lock_object = threading.RLock() def task(): print("开始") with lock_object: # 基于上下文管理,内部自动执行 acquire 和 release global num for i in range(1000000): num += 1 print(num) for i in range(2): t = threading.Thread(target=task) t.start()
4 线程锁
在程序中如果想要自己手动加锁,一般有两种:Lock 和 RLock。
区别:是否支持锁的嵌套
-
Lock 不支持的 适合锁的嵌套
-
RLock 支持 且一次锁,一次解的效率是比RLock要高的
-
Lock,同步锁。
import threading num = 0 lock_object = threading.Lock() def task(): print("开始") lock_object.acquire() # 第1个抵达的线程进入并上锁,其他线程就需要再此等待。 global num for i in range(1000000): num += 1 lock_object.release() # 线程出去,并解开锁,其他线程就可以进入并执行了 print(num) for i in range(2): t = threading.Thread(target=task) t.start()
-
RLock,递归锁。
import threading num = 0 lock_object = threading.RLock() def task(): print("开始") lock_object.acquire() # 第1个抵达的线程进入并上锁,其他线程就需要再此等待。 global num for i in range(1000000): num += 1 lock_object.release() # 线程出去,并解开锁,其他线程就可以进入并执行了 print(num) for i in range(2): t = threading.Thread(target=task) t.start()
RLock支持多次申请锁和多次释放;Lock不支持。例如:
import threading
import time
lock_object = threading.RLock()
def task():
print("开始")
lock_object.acquire()
lock_object.acquire()
print(123)
lock_object.release()
lock_object.release()
for i in range(3):
t = threading.Thread(target=task)
t.start()
import threading
lock = threading.RLock()
# 程序员A开发了一个函数,函数可以被其他开发者调用,内部需要基于锁保证数据安全。
def func():
with lock:
pass
# 程序员B开发了一个函数,可以直接调用这个函数。
def run():
print("其他功能")
func() # 调用程序员A写的func函数,内部用到了锁。
print("其他功能")
# 程序员C开发了一个函数,自己需要加锁,同时也需要调用func函数。
def process():
with lock:
print("其他功能")
func() # ----------------> 此时就会出现多次锁的情况,只有RLock支持(Lock不支持)。
print("其他功能")
5 死锁
死锁,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。
-
Lock申请多次,会导致死锁
import threading num = 0 lock_object = threading.Lock() def task(): print("开始") lock_object.acquire() # 第1个抵达的线程进入并上锁,其他线程就需要再此等待。 lock_object.acquire() # 第1个抵达的线程进入并上锁,其他线程就需要再此等待。 global num for i in range(1000000): num += 1 lock_object.release() # 线程出去,并解开锁,其他线程就可以进入并执行了 lock_object.release() # 线程出去,并解开锁,其他线程就可以进入并执行了 print(num) for i in range(2): t = threading.Thread(target=task) t.start()
-
两个线程手中各拥有一把锁,还想获得对方手中的锁,但是又不释放自己手中的锁 ,就会导致竞争锁的情况
import threading import time lock_1 = threading.Lock() lock_2 = threading.Lock() def task1(): lock_1.acquire() time.sleep(1) lock_2.acquire() print(11) lock_2.release() print(111) lock_1.release() print(1111) def task2(): lock_2.acquire() time.sleep(1) lock_1.acquire() print(22) lock_1.release() print(222) lock_2.release() print(2222) t1 = threading.Thread(target=task1) t1.start() t2 = threading.Thread(target=task2) t2.start()
6 线程池
Python3中官方才正式提供线程池。
线程不是开的越多越好,开的多了可能会导致系统的性能更低了,因为开启多个线程,底层是有分片机制的,涉及到上下文切换也是会耗时的
不建议:无限制的创建线程。
import threading
def task(video_url):
pass
url_list = ["www.xxxx-{}.com".format(i) for i in range(30000)]
for url in url_list:
t = threading.Thread(target=task, args=(url,))
t.start()
# 这种每次都创建一个线程去操作,创建任务的太多,线程就会特别多,可能效率反倒降低了。
建议:使用线程池
示例1:
import time
from concurrent.futures import ThreadPoolExecutor
# pool = ThreadPoolExecutor(100)
# pool.submit(函数名,参数1,参数2,参数...)
def task(video_url,num):
print("开始执行任务", video_url)
time.sleep(5)
# 创建线程池,最多维护10个线程。
pool = ThreadPoolExecutor(10)
url_list = ["www.xxxx-{}.com".format(i) for i in range(300)]
for url in url_list:
# 在线程池中提交一个任务,线程池中如果有空闲线程,则分配一个线程去执行,执行完毕后再将线程交还给线程池;如果没有空闲线程,则等待。
pool.submit(task, url,2)
print("END")
示例2:等待线程池的任务执行完毕。
import time
from concurrent.futures import ThreadPoolExecutor
def task(video_url):
print("开始执行任务", video_url)
time.sleep(5)
# 创建线程池,最多维护10个线程。
pool = ThreadPoolExecutor(10)
url_list = ["www.xxxx-{}.com".format(i) for i in range(300)]
for url in url_list:
# 在线程池中提交一个任务,线程池中如果有空闲线程,则分配一个线程去执行,执行完毕后再将线程交还给线程池;如果没有空闲线程,则等待。
pool.submit(task, url)
print("执行中...")
pool.shutdown(True) # 主线程等待线程池中的任务执行完毕后,在继续执行
print('继续往下走')
示例3:任务执行完任务,再干点其他事。
import time
import random
from concurrent.futures import ThreadPoolExecutor, Future
def task(video_url):
print("开始执行任务", video_url)
time.sleep(2)
return random.randint(0, 10)
def done(response):
print("任务执行后的返回值", response.result())
# 创建线程池,最多维护10个线程。
pool = ThreadPoolExecutor(10)
url_list = ["www.xxxx-{}.com".format(i) for i in range(15)]
for url in url_list:
# 在线程池中提交一个任务,线程池中如果有空闲线程,则分配一个线程去执行,执行完毕后再将线程交还给线程池;如果没有空闲线程,则等待。
future = pool.submit(task, url)
future.add_done_callback(done) # 是子主线程执行
# 可以做分工,例如:task专门下载,done专门将下载的数据写入本地文件【做持久化】。
示例4:最终统一获取结果。
import time
import random
from concurrent.futures import ThreadPoolExecutor,Future
def task(video_url):
print("开始执行任务", video_url)
time.sleep(2)
return random.randint(0, 10)
# 创建线程池,最多维护10个线程。
pool = ThreadPoolExecutor(10)
future_list = []
url_list = ["www.xxxx-{}.com".format(i) for i in range(15)]
for url in url_list:
# 在线程池中提交一个任务,线程池中如果有空闲线程,则分配一个线程去执行,执行完毕后再将线程交还给线程池;如果没有空闲线程,则等待。
future = pool.submit(task, url)
future_list.append(future)
pool.shutdown(True)
for fu in future_list:
print(fu.result())
案例:基于线程池下载豆瓣图片。
26044585,Hush,https://hbimg.huabanimg.com/51d46dc32abe7ac7f83b94c67bb88cacc46869954f478-aP4Q3V
19318369,柒十一,https://hbimg.huabanimg.com/703fdb063bdc37b11033ef794f9b3a7adfa01fd21a6d1-wTFbnO
15529690,Law344,https://hbimg.huabanimg.com/b438d8c61ed2abf50ca94e00f257ca7a223e3b364b471-xrzoQd
18311394,Jennah·,https://hbimg.huabanimg.com/4edba1ed6a71797f52355aa1de5af961b85bf824cb71-px1nZz
18009711,可洛爱画画,https://hbimg.huabanimg.com/03331ef39b5c7687f5cc47dbcbafd974403c962ae88ce-Co8AUI
30574436,花姑凉~,https://hbimg.huabanimg.com/2f5b657edb9497ff8c41132e18000edb082d158c2404-8rYHbw
17740339,小巫師,https://hbimg.huabanimg.com/dbc6fd49f1915545cc42c1a1492a418dbaebd2c21bb9-9aDqgl
18741964,桐末tonmo,https://hbimg.huabanimg.com/b60cee303f62aaa592292f45a1ed8d5be9873b2ed5c-gAJehO
30535005,TANGZHIQI,https://hbimg.huabanimg.com/bbd08ee168d54665bf9b07899a5c4a4d6bc1eb8af77a4-8Gz3K1
31078743,你的老杨,https://hbimg.huabanimg.com/c46fbc3c9a01db37b8e786cbd7174bbd475e4cda220f4-F1u7MX
25519376,尺尺寸,https://hbimg.huabanimg.com/ee29ee198efb98f970e3dc2b24c40d89bfb6f911126b6-KGvKes
21113978,C-CLong,https://hbimg.huabanimg.com/7fa6b2a0d570e67246b34840a87d57c16a875dba9100-SXsSeY
24674102,szaa,https://hbimg.huabanimg.com/0716687b0df93e8c3a8e0925b6d2e4135449cd27597c4-gWdv24
30508507,爱起床的小灰灰,https://hbimg.huabanimg.com/4eafdbfa21b2f300a7becd8863f948e5e92ef789b5a5-1ozTKq
12593664,yokozen,https://hbimg.huabanimg.com/cd07bbaf052b752ed5c287602404ea719d7dd8161321b-cJtHss
16899164,一阵疯,https://hbimg.huabanimg.com/0940b557b28892658c3bcaf52f5ba8dc8402100e130b2-G966Uz
847937,卩丬My㊊伴er彎,https://hbimg.huabanimg.com/e2d6bb5bc8498c6f607492a8f96164aa2366b104e7a-kWaH68
31010628,慢慢即漫漫,https://hbimg.huabanimg.com/c4fb6718907a22f202e8dd14d52f0c369685e59cfea7-82FdsK
13438168,海贼玩跑跑,https://hbimg.huabanimg.com/1edae3ce6fe0f6e95b67b4f8b57c4cebf19c501b397e-BXwiW6
28593155,源稚生,https://hbimg.huabanimg.com/626cfd89ca4c10e6f875f3dfe1005331e4c0fd7fd429-9SeJeQ
28201821,合伙哼哼,https://hbimg.huabanimg.com/f59d4780531aa1892b80e0ec94d4ec78dcba08ff18c416-769X6a
28255146,漫步AAA,https://hbimg.huabanimg.com/3c034c520594e38353a039d7e7a5fd5e74fb53eb1086-KnpLaL
30537613,配䦹,https://hbimg.huabanimg.com/efd81d22c1b1a2de77a0e0d8e853282b83b6bbc590fd-y3d4GJ
22665880,日后必火,https://hbimg.huabanimg.com/69f0f959979a4fada9e9e55f565989544be88164d2b-INWbaF
16748980,keer521521,https://hbimg.huabanimg.com/654953460733026a7ef6e101404055627ad51784a95c-B6OFs4
30536510,“西辞”,https://hbimg.huabanimg.com/61cfffca6b2507bf51a507e8319d68a8b8c3a96968f-6IvMSk
30986577,艺成背锅王,https://hbimg.huabanimg.com/c381ecc43d6c69758a86a30ebf72976906ae6c53291f9-9zroHF
26409800,CsysADk7,https://hbimg.huabanimg.com/bf1d22092c2070d68ade012c588f2e410caaab1f58051-ahlgLm
30469116,18啊全阿,https://hbimg.huabanimg.com/654953460733026a7ef6e101404055627ad51784a95c-B6OFs4
17473505,椿の花,https://hbimg.huabanimg.com/0e38d810e5a24f91ebb251fd3aaaed8bb37655b14844c-pgNJBP
19165177,っ思忆゜♪,https://hbimg.huabanimg.com/4815ea0e4905d0f3bb82a654b481811dadbfe5ce2673-vMVr0B
16059616,格林熊丶,https://hbimg.huabanimg.com/8760a2b08d87e6ed4b7a9715b1a668176dbf84fec5b-jx14tZ
30734152,sCWVkJDG,https://hbimg.huabanimg.com/f31a5305d1b8717bbfb897723f267d316e58e7b7dc40-GD3e22
24019677,虚无本心,https://hbimg.huabanimg.com/6fdfa9834abe362e978b517275b06e7f0d5926aa650-N1xCXE
16670283,Y-雨后天空,https://hbimg.huabanimg.com/a3bbb0045b536fc27a6d2effa64a0d43f9f5193c177f-I2vHaI
21512483,汤姆2,https://hbimg.huabanimg.com/98cc50a61a7cc9b49a8af754ffb26bd15764a82f1133-AkiU7D
16441049,笑潇啸逍小鱼,https://hbimg.huabanimg.com/ae8a70cd85aff3a8587ff6578d5cf7620f3691df13e46-lmrIi9
24795603,v,https://hbimg.huabanimg.com/a7183cc3a933aa129d7b3230bf1378fd8f5857846cc5-3tDtx3
29819152,妮玛士珍多,https://hbimg.huabanimg.com/ca4ecb573bf1ff0415c7a873d64470dedc465ea1213c6-RAkArS
19101282,陈勇敢❤,https://hbimg.huabanimg.com/ab6d04ebaff3176e3570139a65155856871241b58bc6-Qklj2E
28337572,爱意随风散,https://hbimg.huabanimg.com/117ad8b6eeda57a562ac6ab2861111a793ca3d1d5543-SjWlk2
17342758,幸运instant,https://hbimg.huabanimg.com/72b5f9042ec297ae57b83431123bc1c066cca90fa23-3MoJNj
18483372,Beau染,https://hbimg.huabanimg.com/077115cb622b1ff3907ec6932e1b575393d5aae720487-d1cdT9
22127102,栽花的小蜻蜓,https://hbimg.huabanimg.com/6c3cbf9f27e17898083186fc51985e43269018cc1e1df-QfOIBG
13802024,LoveHsu,https://hbimg.huabanimg.com/f720a15f8b49b86a7c1ee4951263a8dbecfe3e43d2d-GPEauV
22558931,白驹过隙丶梨花泪う,https://hbimg.huabanimg.com/e49e1341dfe5144da5c71bd15f1052ef07ba7a0e1296b-jfyfDJ
11762339,cojoy,https://hbimg.huabanimg.com/5b27f876d5d391e7c4889bc5e8ba214419eb72b56822-83gYmB
30711623,雪碧学长呀,https://hbimg.huabanimg.com/2c288a1535048b05537ba523b3fc9eacc1e81273212d1-nr8M4t
18906718,西霸王,https://hbimg.huabanimg.com/7b02ad5e01bd8c0a29817e362814666a7800831c154a6-AvBDaG
31037856,邵阳的小哥哥,https://hbimg.huabanimg.com/654953460733026a7ef6e101404055627ad51784a95c-B6OFs4
26830711,稳健谭,https://hbimg.huabanimg.com/51547ade3f0aef134e8d268cfd4ad61110925aefec8a-NKPEYX
import os
import requests
from concurrent.futures import ThreadPoolExecutor,Future
base_path = os.path.dirname(os.path.abspath(__file__))
target_path = os.path.join(base_path,"image")
if not os.path.exists(target_path):
os.makedirs(target_path)
# 创建线程池,最多维护10个线程。
pool = ThreadPoolExecutor(10)
url_list = []
class Content:
def __init__(self,content, name , url):
self.content = content
self.name = name
self.url = url
with open("data.csv",mode="r",encoding="utf-8") as file_object:
for line in file_object:
xid, name, url = line.split(',')
url_list.append({"name":name,"url":url.strip()})
def download(video_url,name):
res = requests.get(url=video_url)
return Content(res.content,name,video_url)
def save(response):
result = response.result()
save_path = os.path.join(target_path, result.name)
with open(f'{save_path}.jpg',mode="wb") as fj:
fj.write(result.content)
for item in url_list:
# 在线程池中提交一个任务,线程池中如果有空闲线程,则分配一个线程去执行,执行完毕后再将线程交还给线程池;如果没有空闲线程,则等待。
future = pool.submit(download, item['url'],item['name'])
future.add_done_callback(save)