Python多线程与线程池(python线程池ThreadPoolExecutor)concurrent.futures高级别异步执行封装

本文介绍了Python中的多线程概念,GIL的限制,以及如何通过线程池提高并发性能。重点讲解了线程池的使用,包括Python的ThreadPoolExecutor模块和其带来的优势,如控制并发、性能提升和代码简化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Python多线程与线程池

一、Python多线程

在进行复杂的计算或处理大量数据时,可以通过创建多个线程来同时执行多个任务,从而提高程序的执行效率。这种技术称为多线程编程。

1.1 线程简介

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
在这里插入图片描述

1.2 Python中的多线程

Python中的threading模块提供了对线程的支持。使用threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run方法。

import threading

class MyThread(threading.Thread):
    def __init__(self, n):
        super(MyThread, self).__init__()
        self.n = n
    
    def run(self):
        print('running task', self.n)

t1 = MyThread(1)
t2 = MyThread(2)

t1.start()
t2.start()

在这里插入图片描述

1.3 GIL限制

由于Python解释器设计中的全局解释器锁(Global Interpreter Lock,GIL)的存在,使得Python的多线程并不能利用多核优势。GIL是计算机程序设计语言解释器用于同步线程的工具,使得任何时刻只有一个线程在执行,即使在多核CPU平台上,Python的线程也无法同时执行。

在这里插入图片描述

二、线程池

线程池是一种基于池化思想管理线程的工具。在开始任务时不再重新创建新的线程,而是直接从线程池中获取一个空闲线程来执行。如果线程池中没有空闲线程,新的任务就会等待(排队),直到有线程空闲。当任务执行完毕后,线程并不立即销毁,而是返回线程池等待下次被利用。

2.1 Python中的线程池

Python的concurrent.futures模块提供了高级别的异步执行封装,包括线程池ThreadPoolExecutor和进程池ProcessPoolExecutor,它们都是Executor的子类。

from concurrent.futures import ThreadPoolExecutor

def func(n):
    print(n)

with ThreadPoolExecutor(max_workers=4) as executor:
    executor.map(func, range(1,5))

其中max_workers参数表示线程池中最多可以同时运行的线程数量。

在这里插入图片描述

线程池优势

Python线程池具有以下优势:

  1. 控制并发数:Python的线程池可以控制系统中运行的线程数量,避免了因为创建过多线程而导致系统资源耗尽。

  2. 提高性能:通过复用已经创建的线程,可以避免频繁地创建和销毁线程所带来的性能开销。

  3. 简化代码:使用线程池,我们只需要将任务提交给线程池,无需手动管理每个线程的生命周期。

  4. 异步处理:Python的线程池提供了异步处理任务的能力。当我们提交任务给线程池后,线程池会在后台进行处理,不会阻塞主程序的执行。

  5. 调度方便:线程池还提供了一些调度功能,如定时执行、周期执行等。

  6. 任务队列:线程池内部维护了一个任务队列,如果线程池中的所有线程都在忙,新来的任务会被放入队列中等待执行,这样可以保证所有提交给线程池的任务都会被执行,不会丢失。

总的来说,使用Python线程池可以简化并发编程的复杂性,提高程序的性能和稳定性。

三、代码分析

import requests
from requests.models import PreparedRequest
import json
import concurrent.futures

def get_score_models(url):
    url_score = "https://bizapi.csdn.net/trends/api/v1/get-article-score"

    headers = {
        "accept": "application/json, text/plain, */*",
        "x-ca-key": "203930474",
        "x-ca-nonce": "22cd11a0-760a-45c1-8089-14e53123a852",
        "x-ca-signature": "RaEczPkQ22Ep/k9/AI737gCtn8qX67CV/uGdhQiPIdQ=",
        "x-ca-signature-headers": "x-ca-key,x-ca-nonce",
        "x-ca-signed-content-type": "multipart/form-data"
    }

    data = {"url": url}
    response = send_request(url_score, data, headers)
    data1 = response.json()

    score_model = data1["data"]

    return score_model


def send_request(url, data, headers):
    session = requests.Session()
    prepared_request = PreparedRequest()
    prepared_request.prepare(method='POST', url=url,
                             headers=headers, data=data)
    return session.send(prepared_request)


def process_article_json(article):
    # score_model = get_score_models(article['article_url'])
    score_model = get_score_models(article['url'])
    article['article_score'] = score_model['score']
    print(article["url"])
    return article


if __name__ == '__main__':
    # 读取articles.json文件
    with open('articles.json', 'r') as f:
        articles = json.load(f)

    # 创建一个 ThreadPoolExecutor 实例,max_workers 表示线程池中最多可以同时运行的线程数量
    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
        # 使用 map 函数将 process_article_json 应用到每个元素,并在多线程环境下并行处理
        processed_articles = list(executor.map(process_article_json, articles))

    # 保存处理后的结果到新的JSON文件
    output_file = 'processed_articles.json'
    with open(output_file, 'w') as f:
        json.dump(processed_articles, f, ensure_ascii=False, indent=4)

import requests
from requests.models import PreparedRequest
import json
import concurrent.futures
def get_score_models(url):
url_score = “https://bizapi.csdn.net/trends/api/v1/get-article-score”
headers = {
“accept”: “application/json, text/plain, /”,
“x-ca-key”: “203930474”,
“x-ca-nonce”: “22cd11a0-760a-45c1-8089-14e53123a852”,
“x-ca-signature”: “RaEczPkQ22Ep/k9/AI737gCtn8qX67CV/uGdhQiPIdQ=”,
“x-ca-signature-headers”: “x-ca-key,x-ca-nonce”,
“x-ca-signed-content-type”: “multipart/form-data”
}
data = {“url”: url}
response = send_request(url_score, data, headers)
data1 = response.json()
score_model = data1[“data”]
return score_model
def send_request(url, data, headers):
session = requests.Session()
prepared_request = PreparedRequest()
prepared_request.prepare(method=‘POST’, url=url,
headers=headers, data=data)
return session.send(prepared_request)
def process_article_json(article):
# score_model = get_score_models(article[‘article_url’])
score_model = get_score_models(article[‘url’])
article[‘article_score’] = score_model[‘score’]
print(article[“url”])
return article
if name == ‘main’:
# 读取articles.json文件
with open(‘articles.json’, ‘r’) as f:
articles = json.load(f)
# 创建一个 ThreadPoolExecutor 实例,max_workers 表示线程池中最多可以同时运行的线程数量
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
# 使用 map 函数将 process_article_json 应用到每个元素,并在多线程环境下并行处理
processed_articles = list(executor.map(process_article_json, articles))
# 保存处理后的结果到新的JSON文件
output_file = ‘processed_articles.json’
with open(output_file, ‘w’) as f:
json.dump(processed_articles, f, ensure_ascii=False, indent=4)

在这里插入图片描述

以上给出的代码片段主要涉及到的是线程池的使用。具体来说,首先从一个名为articles.json的文件中读取文章信息,然后利用线程池并发地获取每篇文章的评分,并将评分添加到文章信息中,最后将处理后的文章信息保存到新的JSON文件。

代码的主要部分如下:

with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    processed_articles = list(executor.map(process_article_json, articles))

在这里插入图片描述

这里,首先创建了一个ThreadPoolExecutor实例,并设置最大并发线程数为5。然后使用executor.map()函数将process_article_json函数应用到articles列表的每个元素上,这样就可以在多线程环境下并行处理每篇文章了。由于executor.map()函数返回的是一个迭代器,因此需要用list()函数将其转换为列表。

这种方式可以有效地提高处理大量文章信息的效率,特别是当获取文章评分的过程涉及到网络请求等I/O操作时,通过线程池并发处理可以显著减少总的处理时间。

在这里插入图片描述

四、参考资料

20230817

 with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:
            future_to_ip = {executor.submit(check_ping, ip): ip for ip in ips}
            for future in concurrent.futures.as_completed(future_to_ip):
                ip = future_to_ip[future]
                try:
                    if future.result():
                        print(f"IP [{ip}] 可以ping通")
                        return True
                except Exception as exc:
                    print('%r generated an exception: %s' % (ip, exc))

这段代码是使用Python的concurrent.futures模块创建一个线程池来并发执行任务。每个任务都在一个独立的线程中运行,最大并发线程数由max_workers参数指定。

  1. with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:

    这一行创建了一个线程池执行器(ThreadPoolExecutor),最大工作线程数量为20。

  2. future_to_ip = {executor.submit(check_ping, ip): ip for ip in ips}

    这一行通过线程池执行器提交任务。每个任务都是对函数check_ping的调用,并传入一个IP地址。这会返回一个Future对象,表示待完成的操作。然后将每个Future对象与对应的IP地址映射到字典future_to_ip中。

  3. for future in concurrent.futures.as_completed(future_to_ip):

    这一行遍历所有已完成的Future对象。as_completed函数返回一个迭代器,当一个Future对象完成时,它就会产生一个新的值。

    for future in concurrent.futures.as_completed(future_to_ip)这段代码是阻塞等待的。这个语句会迭代返回已经完成的Future实例。如果没有完成的Future实例,它就会等待,直到有Future实例完成。

    concurrent.futures.as_completed函数返回一个迭代器,这个迭代器会在每次Future实例完成(无论成功还是失败)时产生该Future。所以,在这个循环中,你可以立即处理已完成的任务,而不必等待所有任务都完成。但是,如果所有的Future都还未完成,那么这个循环就会阻塞,直到至少有一个Future完成。

  4. ip = future_to_ip[future]

    这一行从字典中获取与当前Future对象对应的IP地址。

  5. if future.result():

    这一行获取Future对象的结果。如果check_ping函数返回True,那么就打印出可以ping通的IP地址,并返回True。

  6. 如果在获取Future对象的结果时发生异常,那么就捕获这个异常,并打印出引起异常的IP地址和异常信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dontla

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值