1. 线程池
开启线程的成本要比开启进程的成本低,但是也不能任意的开线程,可以使用线程池----使用concurrent的futures模块(futures.ThreadPoolExecutor 导入线程池; futures.ProcessPoolExecutor 导入进程池);
一般开进程池的数目是 cpu个数+1;
开线程池的数目是cpu 个数*5;
开启线程---submit()
from concurrent import futures import time import random # futures.ThreadPoolExecutor-----导入线程池 # futures.ProcessPoolExecutor---导入进程池 def func(n): time.sleep(random.random()) print("thread--%s"%n) thread_pool=futures.ThreadPoolExecutor(5) # 一般开的线程池,开的线程数是cpu个数*5 但是这里我就开五个线程 for i in range(10): # 有10个任务需要执行。开五个线程轮流去执行这10个任务 thread_pool.submit(func,i) # thread_pool.submit() 相当于t=Thread(func,i)创建线程 和t.start() 开启线程两步
运行结果:(执行过程,最多五个线程并发执行)
而且futures.ThreadPoolExexutor(num)是可以接收线程执行函数的返回值的,等到的对象使用result()方法去获取:
# 如果想使用futures.ThreadPoolExecutor(num)创建的线程池对象thread_pool,使用thread_pool.submit(func,args)开启线程;
# 如果线程执行的函数有返回值,submit()是可以拿到返回值ret的,直接使用ret.result()就可以获得:(但是多个线程之间就变同步了,因为每一个线程必须得去执行函数,拿到返回值才会轮到下一个线程执行) from concurrent import futures import time import random # futures.ThreadPoolExecutor-----导入线程池 # futures.ProcessPoolExecutor---导入进程池 def func(n): time.sleep(random.random()) print("thread--%s" % n) return n*"*" # 线程执行函数具有返回值 thread_pool=futures.ThreadPoolExecutor(5) # 一般开的线程池,开的线程数是cpu个数*5 但是这里我就开五个线程 for i in range(10): # 有10个任务需要执行。开五个线程轮流去执行这10个任务 ret=thread_pool.submit(func,i) # thread_pool.submit() 相当于t=Thread(func,i)创建线程 和t.start() 开启线程两步 print(ret.result()) # futures.ThreadPoolExecutor(num) 建立线程池对象thread_pool,然后使用thread_pool线程池对象.submit(func,args)方法开启线程 # 如果线程执行的函数具有返回值,则submit()的结果是可以拿到返回值的,直接使用ret.result()方法获取 # 但是这样 多个线程之间执行就变同步了,因为每当执行一个线程就需要result()获取线程执行函数的返回值,就需要把函数执行完,才可以拿到,然后才会轮到下一个线程执行
运行结果:
如果既想拿到线程执行函数的返回值,又想多个线程之间异步并发执行:
# 既想拿到线程执行函数的返回值,又想实现多线程之间的异步并发: from concurrent import futures import time import random # futures.ThreadPoolExecutor-----导入线程池 # futures.ProcessPoolExecutor---导入进程池 def func(n): time.sleep(random.random()) print("thread--%s" % n) return n*"*" # 线程执行函数具有返回值 thread_pool=futures.ThreadPoolExecutor(5) # 一般开的线程池,开的线程数是cpu个数*5 但是这里我就开五个线程 ret_lst=[] # 存放开启线程执行函数的返回值,先同时开启很多个线程,但是并不一定要去执行函数拿到返回值,最后统一获取返回值,这样多个线程仍然是异步并发执行的 for i in range(10): # 有10个任务需要执行。开五个线程轮流去执行这10个任务 ret=thread_pool.submit(func,i) # thread_pool.submit() 相当于t=Thread(func,i)创建线程 和t.start() 开启线程两步 # print(ret.result()) # futures.ThreadPoolExecutor(num) 建立线程池对象thread_pool,然后使用thread_pool线程池对象.submit(func,args)方法开启线程 # 如果线程执行的函数具有返回值,则submit()的结果是可以拿到返回值的,直接使用ret.result()方法获取 ret_lst.append(ret) [print(ret.result()) for ret in ret_lst] # 这样多个线程就实现异步并发,之前是一个线程必须去执行函数才能拿到返回值,接着才轮到下一个线程,这样就可以异步开多个线程,先不一定要真的去执行func # 这种方法可以实现多个线程异步,而且打印多个线程的返回值,有可能是跟上面线程执行函数打印的结果交叉 # (因为很有可能有的线程执行的快,结果都要返回了,其他线程可能才去执行函数,做线程内的打印)
运行结果:
如果想在所有线程异步执行完函数内代码打印,然后再统一打印线程执行函数的返回值呢(就是打印thread-n 和 打印***分开)可以使用thread_pool.shutdown() 相当于进程池在主进程中p.close() p.join() 是一样的道理(就是不允许往进程池添加任务,然后让主进程等待主线程执行完毕)这里也是一样的
from concurrent import futures import time import random # futures.ThreadPoolExecutor-----导入线程池 # futures.ProcessPoolExecutor---导入进程池 def func(n): time.sleep(random.random()) print("thread--%s" % n) return n*"*" # 线程执行函数具有返回值 thread_pool=futures.ThreadPoolExecutor(5) # 一般开的线程池,开的线程数是cpu个数*5 但是这里我就开五个线程 ret_lst=[] # 存放开启线程执行函数的返回值,先同时开启很多个线程,但是并不一定要去执行函数拿到返回值,最后统一获取返回值,这样多个线程仍然是异步并发执行的 for i in range(10): # 有10个任务需要执行。开五个线程轮流去执行这10个任务 ret=thread_pool.submit(func,i) # thread_pool.submit() 相当于t=Thread(func,i)创建线程 和t.start() 开启线程两步 ret_lst.append(ret) thread_pool.shutdown() # 相当于进程池的p.close() p.join() 等待上面所有线程执行函数结束(异步并发),才会统一打印线程的执行函数的返回值 [print(ret.result()) for ret in ret_lst] # 这样多个线程就实现异步并发,之前是一个线程必须去执行函数才能拿到返回值,接着才轮到下一个线程,这样就可以异步开多个线程,先不一定要真的去执行func # 这种方法可以实现多个线程异步,而且打印多个线程的返回值,有可能是跟上面线程执行函数打印的结果交叉 # (因为很有可能有的线程执行的快,结果都要返回了,其他线程可能才去执行函数,做线程内的打印)
运行结果:
开启线程----map()函数
from concurrent import futures import time import random def func(n): time.sleep(random.random()) print("thread--%s"%n) return n*"*" # 使用map()开启线程,即使线程执行函数有返回值,map()也拿不到 thread_pool=futures.ThreadPoolExecutor(5) # 线程池有五个线程 thread_pool.map(func,range(10)) # map()函数接收第一个参数是多个线程需要执行的任务,第二个参数是一个可迭代的对象---10个任务,但是五个线程去执行
运行结果:
需要注意: map() 得到的多个线程执行任务是异步并发的,但是多个线程执行函数有返回值时,map()是获取不到的;
2. 回调函数---参数就是多线程执行函数的返回值
from concurrent import futures import time import random def func(n): time.sleep(random.random()) print("thread--%s"%n) return n*"*" # 线程执行函数的返回值传给回调函数 def call_func(args): # 回调函数的参数接收的是多线程执行函数的返回值 print(args.result()) # submit()可以拿到多线程执行函数的返回值,但是得使用result()才可以获得 thread_pool=futures.ThreadPoolExecutor(5) # 线程池开五个线程 for i in range(10): # 有10个任务,但是开五个线程轮流执行 thread_pool.submit(func,i).add_done_callback(call_func)
运行结果:
3. 作业--使用futures模块的ThreadPoolExecutor线程池实现网页爬取
import requests from concurrent import futures def get_url(url): ret=requests.get(url) return {"url":url, "status_code":ret.status_code, "content":ret.text, "length":len(ret.text)} def parser(dic): print(dic.result()) # 回调函数的参数dic就是线程执行函数的返回值,但是必须使用result()才可以拿到返回值 url_lst=["http://www.baidu.com","http://www.sougou.com","https://home.cnblogs.com/","https://www.bilibili.com/","http://www.python.org", "https://music.163.com/#","https://www.douban.com/","https://www.1point3acres.com/bbs/","https://blog.csdn.net/","https://map.baidu.com/"] thread_pool=futures.ThreadPoolExecutor(5) # 线程池开五个线程 ret_lst=[] # 存放线程执行get_url 回调函数分析爬取结果,把结果存到列表,最后打印,可以实现多线程之间的异步并发 for url in url_lst: thread_pool.submit(get_url,url).add_done_callback(parser) # 开启五个线程执行get_url 然后把爬取的结果使用回调函数去执行parser
运行结果: