随笔录--线程池进阶

1.参数 `max_workers` 的设定

接前文  《Python多线程和线程池的下载实战用法 》中案例,继续分析

`concurrent.futures.ThreadPoolExecutor()` 是 Python 中的一个线程池实现,用于执行并发的任务。它可以通过参数 `max_workers` 来设置线程池的最大工作线程数。

在你提供的代码中,使用 `ThreadPoolExecutor()` 构建了一个线程池执行器,并通过列表推导式将 `download_with_delay(url)` 函数提交给线程池执行。这样可以并发地下载多个 URL。

区别在于是否指定 `max_workers` 参数:

1. 不指定 `max_workers` 参数:如果不指定 `max_workers` 参数,`ThreadPoolExecutor()` 会根据系统自动选择一个合适的默认值作为最大工作线程数。这意味着线程池会根据需要动态调整线程数量,以适应当前的任务负载。

with concurrent.futures.ThreadPoolExecutor() as executor:

       [executor.submit(download_with_delay, url) for url in urls]

2. 指定 `max_workers` 参数:如果指定了 `max_workers` 参数,线程池会创建一个固定数量的工作线程。这些线程会被循环利用,直到所有任务完成。

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

       [executor.submit(download_with_delay, url) for url in urls]

指定 `max_workers` 参数可以控制线程池中的并发线程数量。根据具体的情况,你可以根据系统资源和任务需求来调整最大工作线程数。如果任务较少或资源有限,可以选择较小的值;如果任务较多且有足够的资源,可以选择较大的值。

2. `max_workers` 参数好还是不指定参数好

对于 `ThreadPoolExecutor()` 中的 `max_workers` 参数,是否指定应该根据具体情况而定。不同的情况下,可能需要不同的线程池配置。

如果你的系统资源有限,或者任务量较小,那么可以考虑不指定 `max_workers` 参数,让线程池根据需要动态调整线程数量,以避免占用过多的资源。

但是如果你的系统拥有足够的资源,并且需要处理大量的并发任务,那么可以通过指定 `max_workers` 参数来控制线程池中的并发线程数量。这样可以确保系统不会使用过多的资源,并提高任务执行效率。

另外,需要注意的是,在实际开发中,还需要考虑以下因素:

- 任务及其执行时间:如果任务需要花费较长的时间才能完成,那么较大的线程池可能会占用过多的资源,导致整个系统的性能下降。

- 系统资源:如果系统资源有限,那么较大的线程池可能会导致系统崩溃或出现其他问题。

- 线程安全:线程池中的线程需要保持线程安全,避免出现数据竞争、死锁等问题。

因此,在实际开发中,需要综合考虑以上因素来确定是否需要指定 `max_workers` 参数,并根据具体情况进行调整。

3.如果考虑线程安全,该怎么指定`max_workers` 参数

在考虑线程安全时,需要确保多个线程并发执行时不会导致数据不一致或其他并发问题。对于 `ThreadPoolExecutor()` 中的 `max_workers` 参数,指定参数与线程安全本身没有直接关联,但是可以通过控制并发线程数量来间接影响线程安全。

在考虑线程安全时,可以采取以下措施:

1. 使用线程安全的数据结构:选择使用线程安全的数据结构(例如 `queue.Queue`)来存储共享数据,以避免多个线程同时修改数据导致的问题。

2. 合理地控制并发度:通过指定 `max_workers` 参数来控制线程池中的并发线程数量,避免因线程过多而导致资源竞争和性能下降。根据系统资源和任务需求,合理地设置最大工作线程数。

3. 使用锁或同步机制:在必要的情况下,可以使用锁或其他同步机制来保护共享资源,确保多个线程访问时的线程安全性。

4. 编写线程安全的代码:在编写并发代码时,需要考虑多线程并发执行可能带来的问题,编写线程安全的代码逻辑,避免出现数据竞争、死锁等问题。

在实际开发中,需要根据具体情况综合考虑上述因素,并根据需求来选择合适的线程安全策略和线程池配置。同时,也可以通过测试和性能分析来验证线程安全性和并发性能,从而优化线程池的配置参数。

4.经典案例

需求

有几万条数据,如果单个数据处理挨个处理的话,涉及下载,指定pdf文件进行ocr识别,其他pdf文件需求识别文件校验,最后把校验结果放在excel里,如何设计代码逻辑,使效率更快

分析

处理大量数据时,优化代码逻辑可以提高效率。根据你的描述,以下是一种可能的代码逻辑设计:

1. 使用多线程或异步操作:将下载、OCR识别和校验等任务分配给多个线程或异步操作,以实现并发处理。这样可以节省等待时间,并提高整体处理速度。

2. 使用连接池进行下载:使用连接池来管理下载请求,避免频繁地创建和关闭连接,提高下载效率。可以使用第三方库(如`requests`)提供的连接池功能。

3. 并行执行OCR识别和文件校验:对于不同的PDF文件,可以同时进行OCR识别和文件校验的操作,而不是等待一个文件处理完再处理下一个文件。可以使用线程池或异步操作来并行执行这两个任务。

4. 批量写入Excel:在处理完每个文件的校验结果后,将结果批量写入Excel而不是每次写入一个单元格。这样可以减少IO操作次数,提高写入效率。

5. 优化OCR识别算法:如果OCR识别是整个处理过程中的瓶颈,尝试使用更高效的OCR库或算法,或者对OCR参数进行调优,以提高识别速度。

6. 合理设置线程池大小:根据系统资源和任务需求,合理设置线程池的最大工作线程数。过小的线程池可能无法充分利用系统资源,而过大的线程池可能会导致资源竞争和性能下降。

7. 异常处理和错误重试:在处理过程中,合理处理可能出现的异常情况,并进行错误重试机制,以保证数据的完整性和准确性。

8. 监控和日志记录:对处理过程进行监控和日志记录,以便排查问题、优化代码和了解处理性能。

5.经典案例的代码示例

以下是一个简单的代码示例,展示了如何使用多线程和连接池来处理大量数据,并将校验结果写入Excel文件:

import os

import requests

import concurrent.futures

import pandas as pd

from pdf_ocr import ocr_function  # 假设有一个名为 pdf_ocr 的 OCR 模块


# 下载PDF文件
def download_pdf(url, save_path):
    response = requests.get(url)
    with open(save_path, 'wb') as f:
        f.write(response.content)



# OCR识别
def ocr_process(pdf_path):
    result = ocr_function(pdf_path)  # 使用自定义的OCR函数进行识别
    return result



# 校验文件
def check_file(pdf_path):

    # 执行文件校验逻辑,返回校验结果
    return True  # 假设所有文件都校验通过



# 主处理函数,处理单个数据

def process_data(data):
    url = data['url']
    pdf_path = data['pdf_path']



    # 下载PDF文件
    download_pdf(url, pdf_path)



    # OCR识别
    ocr_result = ocr_process(pdf_path)



    # 文件校验
    is_valid = check_file(pdf_path)



    # 返回校验结果
    return {'file': pdf_path, 'ocr_result': ocr_result, 'is_valid': is_valid}



if __name__ == '__main__':
    # 假设有一万条数据,存储在一个名为 data_list 的列表中
    data_list = [...]



    # 设置线程池大小
    max_workers = 10



    # 创建连接池
    with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
        # 处理数据并获取结果
        results = executor.map(process_data, data_list)



    # 将结果写入Excel文件
    df = pd.DataFrame(results)
    df.to_excel('result.xlsx', index=False)

6.主函数需要传更多参数时代码示例

主函数`process_data`可以传递更多参数。如果需要传递更多参数,只需在`process_data`函数的参数列表中添加相应的参数即可。

以下是修改后的代码示例,演示如何在`process_data`函数中传递额外的参数:

import os

import requests

import concurrent.futures

import pandas as pd

from pdf_ocr import ocr_function  # 假设有一个名为 pdf_ocr 的 OCR 模块



# 下载PDF文件
def download_pdf(url, save_path):
    response = requests.get(url)
    with open(save_path, 'wb') as f:
        f.write(response.content)



# OCR识别
def ocr_process(pdf_path):
    result = ocr_function(pdf_path)  # 使用自定义的OCR函数进行识别
    return result


# 校验文件
def check_file(pdf_path):
    # 执行文件校验逻辑,返回校验结果
    return True  # 假设所有文件都校验通过


# 主处理函数,处理单个数据
def process_data(data, additional_param1, additional_param2):
    url = data['url']
    pdf_path = data['pdf_path']

    # 下载PDF文件
    download_pdf(url, pdf_path)
    # OCR识别
    ocr_result = ocr_process(pdf_path)
    # 文件校验
    is_valid = check_file(pdf_path)



    # 使用额外的参数执行其他逻辑

    # ...



    # 返回校验结果

    return {'file': pdf_path, 'ocr_result': ocr_result, 'is_valid': is_valid}



if __name__ == '__main__':

    # 假设有一万条数据,存储在一个名为 data_list 的列表中

    data_list = [...]



    # 设置线程池大小
    max_workers = 10
    # 定义额外的参数
    additional_param1 = ...
    additional_param2 = ...



    # 创建连接池

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

        # 处理数据并获取结果

        results = executor.map(process_data, data_list, [additional_param1]*len(data_list), [additional_param2]*len(data_list))



    # 将结果写入Excel文件

    df = pd.DataFrame(results)

    df.to_excel('result.xlsx', index=False)

在调用`executor.map()`时,传递了额外的参数`additional_param1`和`additional_param2`,这样在每次调用`process_data`函数时,都会将这两个额外的参数传递进去。

如果需要传递多个额外的参数,需要保证额外参数列表的长度与`data_list`的长度一致,可以使用列表推导式 `[additional_param]*len(data_list)` 来生成与`data_list`长度相同的参数列表。

根据流水号下载影像件时可能会有相同的文件夹名字,需要在文件夹里面读取文件,如果使用多线程进行操作,会有死锁风险吗

7.如何预防死锁

在多线程环境下进行文件读取时,如果多个线程同时尝试访问同一个文件或者共享的资源,可能会导致死锁风险。这是因为多线程操作文件时,需要保证对文件的互斥访问,以避免数据不一致性和冲突。

为了避免死锁风险,你可以使用适当的同步机制来管理文件的访问,常用的同步机制有锁(Lock)和信号量(Semaphore)。这样可以确保只有一个线程可以访问文件,其他线程需要等待。

以下是一个示例代码,展示如何使用`threading.Lock()`来确保线程安全的文件访问:

import os
import requests
import concurrent.futures
import threading


# 下载PDF文件
def download_pdf(url, save_path):
    response = requests.get(url)
    with open(save_path, 'wb') as f:
        f.write(response.content)


# 处理单个数据
def process_data(data):
    url = data['url']
    pdf_path = data['pdf_path']


    # 加锁,保证同一时间只有一个线程能够访问文件
    with lock:
        # 下载PDF文件
        download_pdf(url, pdf_path)


    # 其他处理逻辑...
    # ...


if __name__ == '__main__':
    # 假设有一万条数据,存储在一个名为 data_list 的列表中
    data_list = [...]


    # 创建锁对象
    lock = threading.Lock()


    # 设置线程池大小
    max_workers = 10


    # 创建连接池
    with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
        # 处理数据并获取结果
        results = executor.map(process_data, data_list)

在上述示例中,通过创建一个`threading.Lock()`对象 `lock` 来确保同一时间只有一个线程能够访问文件。在处理每个数据之前,使用`with lock:`语句来加锁,以保证同一时间只有一个线程可以执行文件操作。

这样可以有效避免多线程环境下的死锁风险。注意,使用锁会引入一定的性能开销,因此在设计多线程程序时需要权衡并根据实际需求和性能要求进行合理的优化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值