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() 开启线程两步

 

运行结果:(执行过程,最多五个线程并发执行)

python 线程池 Python 线程池 线程数一般多少_python 线程池

 

而且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()获取线程执行函数的返回值,就需要把函数执行完,才可以拿到,然后才会轮到下一个线程执行

 

运行结果:

python 线程池 Python 线程池 线程数一般多少_回调函数_02

 

 如果既想拿到线程执行函数的返回值,又想多个线程之间异步并发执行:

# 既想拿到线程执行函数的返回值,又想实现多线程之间的异步并发:
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
            # 这种方法可以实现多个线程异步,而且打印多个线程的返回值,有可能是跟上面线程执行函数打印的结果交叉
            # (因为很有可能有的线程执行的快,结果都要返回了,其他线程可能才去执行函数,做线程内的打印)

运行结果:

python 线程池 Python 线程池 线程数一般多少_线程池_03

 

如果想在所有线程异步执行完函数内代码打印,然后再统一打印线程执行函数的返回值呢(就是打印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
            # 这种方法可以实现多个线程异步,而且打印多个线程的返回值,有可能是跟上面线程执行函数打印的结果交叉
            # (因为很有可能有的线程执行的快,结果都要返回了,其他线程可能才去执行函数,做线程内的打印)

 

运行结果:

python 线程池 Python 线程池 线程数一般多少_进程池_04

 

开启线程----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个任务,但是五个线程去执行

运行结果:

python 线程池 Python 线程池 线程数一般多少_并发编程+数据库+前端_05

需要注意: 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)

运行结果:

python 线程池 Python 线程池 线程数一般多少_线程池_06

 

 

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://www.bilibili.com/","http://www.python.org",
         "https://music.163.com/#","https://www.douban.com/","https://www.1point3acres.com/bbs/","","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

 

运行结果:

python 线程池 Python 线程池 线程数一般多少_进程池_07

 

talk is cheap,show me the code