python:并发编程(十七)

news2024/9/21 14:51:34

前言

本文将和大家一起探讨python并发编程的实际运用,会以一些我实际使用的案例,或者一些典型案例来分享。本文使用的案例是我实际使用的案例(中篇),是基于之前效率不高的代码改写成并发编程的。让我们来看看改造的过程,这样就会对并发编程的高效率有个清晰地认知,也会在改造过程中学到一些知识。

本文为python并发编程的第十七篇,上一篇文章地址如下:

python:并发编程(十六)_Lion King的博客-CSDN博客

下一篇文章地址如下:

(暂无)
 

一、实施方案:使用多线程同时处理多个图片

多进程的处理速度我们已经了解了,如果基于相同代码的优化,那么多线程、多协程处理会怎么样呢?

1、使用10个线程直接测试40张图片的耗时

修改上一章版本的代码,只需要将进程池改为线程池即可,即ProcessPoolExecutor改为ThreadPoolExecutor,耗时大概为154秒(不用并发编程的情况下耗时 157 秒,在使用一个进程的情况下耗时192秒),并没有改善。

二、实施方案:使用多线程同时处理多个图片

1、使用10个协程直接测试40张图片的耗时

实验发现10个协程耗时大概为156秒(不用并发编程的情况下耗时 157 秒,在使用一个进程的情况下耗时192秒),并没有改善。这里装饰器没有监控到协程函数所用的时间,因此在代码里直接计算总体时间。

import os
from PIL import Image
import time
import concurrent.futures
import asyncio

def calculate_runtime(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        runtime = end_time - start_time
        print(f"程序运行时间:{runtime}秒")
        return result
    return wrapper

@calculate_runtime
async def image_gray(img):
    # 打开图片
    img = Image.open(img)

    # 计算平均灰度值
    gray_sum = 0
    count = 0
    for x in range(img.width):
        for y in range(img.height):
            if img.mode == "RGB":
                r, g, b = img.getpixel((x, y))
                gray_sum += (r + g + b) / 3
            elif img.mode == "L":
                gray_value = img.getpixel((x, y))
                gray_sum += gray_value
            count += 1

    avg_gray = gray_sum / count
    return avg_gray

@calculate_runtime
def find_image(folder_path):
    # 定义一个列表存储图片路径
    images = []
    # 遍历文件夹下的所有文件
    for root, dirs, files in os.walk(folder_path):
        for file in files:
            file_path = os.path.join(root, file)
            # 处理每个文件,将其添加到列表中
            images.append(file_path)
    return images[0:40]  # [0:40]

async def process_image(semaphore, img):
    async with semaphore:
        gray = await image_gray(img)
    if gray < 50:
        print(img, ":", gray)


@calculate_runtime
async def assert_run(folder_path):
    images = find_image(folder_path)

    # 创建一个事件循环
    loop = asyncio.get_event_loop()

    # 创建信号量控制并发数量
    semaphore = asyncio.Semaphore(10)

    # 创建任务列表
    tasks = [process_image(semaphore, img) for img in images]

    # 并发执行任务
    await asyncio.gather(*tasks)


if __name__ == "__main__":
    start_time = time.time()
    folder_path = r'C:\Users\yeqinfang\Desktop\临时文件\文件存储'
    asyncio.run(assert_run(folder_path))
    end_time = time.time()
    print("总耗时:", end_time-start_time)

三、关于多进程、多线程、多协程并发的探讨

上一章节我们已经了解到多进程带来了缩短时间的效果,而本章却看到了多线程、多协程几乎没啥 效果,以及实验中还有很多未解之谜,这些都将在本节一一解密。

1、为什么不是打开线程数越多,耗时越少?

上一章节中,无论使用10个、15个、20个还是60个进程,并发处理40张图片的耗时都相对稳定。这可能是由于以下原因:

CPU资源有限:如果系统的CPU资源有限,例如只有几个物理核心,无论启动多少个进程,实际上只有少数几个进程能够同时执行。这可能导致并发执行的进程数增加时,进程间的切换和调度开销变得更大,而实际的处理时间并没有显著改善。

其他因素的影响:除了进程数,还有许多其他因素可能影响程序的性能,例如磁盘IO、内存访问、操作系统调度等。这些因素可能导致并发执行的进程数增加时,性能并不线性增加。

为了更好地理解程序的性能瓶颈,你可以尝试对程序进行性能分析,例如使用Python的timeit模块或专业的性能分析工具,来测量每个任务的执行时间、系统资源的利用情况等。这样可以帮助你确定是否存在性能瓶颈,并找到进一步优化的方向。

2、我的内核为6,是不是同一时间只有6个进程同时运行?

不完全正确,因为6个进程有可能在一个核上切换运行,因此,应该说同一时间最多只有6个进程同时运行。在一个具有6个物理内核的系统上,可以同时运行多个进程。然而,同时运行的进程数量不仅取决于物理内核的数量,还受到操作系统调度器的影响以及各个进程的资源需求和优先级等因素的影响。

操作系统的调度器负责管理进程之间的分配和切换,以实现并发执行。它可以根据各个进程的需求和优先级来进行调度和分配处理器时间。这意味着在一个6个物理内核的系统上,可以同时运行多个进程,每个进程在一个物理内核上执行。当然,调度器会根据需要在不同的内核之间进行切换,以实现多任务处理。

此外,还有其他因素会影响并发执行的进程数量,例如进程之间的依赖关系、资源利用率、IO操作的等待时间等。因此,并发执行的进程数量可能会超过物理内核数量,但是同时运行的进程数量受到物理内核数量的限制。

需要注意的是,一个6个物理内核的系统上,同时运行的进程数量可能会受到其他系统资源(如内存、硬盘等)的限制。因此,系统的性能和负载平衡也会对同时运行的进程数量产生影响。

3、我的电脑6核,但使用6个进程花费48秒,使用10个进程花费38秒,这是为什么呢?

只有6个物理核心,那么同时运行10个进程可能会导致一些进程被分配到同一个物理核心上执行(超过物理核心数量)。这可能会导致竞争和调度开销,从而影响性能。一般情况下,与物理核心数量相匹配的进程数会更有效地利用系统资源。使用10个进程比使用6个进程更快可能是由于以下原因:

(1)并发性:使用更多的进程可以增加并发性,充分利用系统的多个物理内核。当您的系统有6个物理内核时,使用6个进程时每个物理内核都可以独立执行一个进程。但当使用10个进程时,仍有4个进程可以被调度到其他空闲的物理内核上,这样可以更好地利用系统资源,提高处理效率。

(2)系统负载:使用更多的进程可以帮助平衡系统负载。在并发处理的情况下,某些任务可能会耗费较长时间,如果只有6个进程,这些耗时任务可能会占用物理内核的时间,导致其他任务等待。而使用10个进程可以将这些耗时任务分散到更多的物理内核上,避免了单一物理内核的过度负载,从而提高整体处理速度。

(3)任务特性:不同的任务可能具有不同的特性,某些任务可能更适合并行执行,而某些任务可能更适合顺序执行。如果您的任务是I/O密集型的(如读取/写入文件、网络请求等),那么使用更多的进程可能可以更好地利用I/O等待时间,从而提高整体效率。

综上所述,具体的性能差异可能受到多个因素的影响,包括任务特性、系统负载和资源利用等。在选择并发数量时,需要综合考虑这些因素,并进行测试和评估以确定最佳的配置。

需要注意的是,使用更多的进程并不总是意味着更快的速度。并发编程的性能受到多个因素的影响,包括任务的性质、数据交互、通信开销等。在您的具体情况下,10个进程可能在任务分配和执行的方式上更适合您的系统,从而导致更快的处理速度。但具体的性能表现还取决于任务的特点和系统的实际情况,因此仍然需要根据实际情况进行测试和调优。

4、为什么20个进程跟10个进程一样快?

当运行多个进程时,系统的调度和资源管理会发挥作用。虽然系统只有6个物理核心,但操作系统可以使用调度算法来分配进程的执行时间,并在不同的核心之间进行切换。

当运行10个进程时,操作系统可能会在6个物理核心上轮流调度这些进程,每个进程分配到一定的执行时间。这种情况下,由于存在调度开销和切换开销,可能会导致一些性能损失。

但当增加到20个进程时,操作系统可能会更频繁地进行进程切换和调度,以满足更多的进程需求。尽管这会增加一些调度和切换开销,但由于进程之间的任务可能是I/O密集型的,多个进程可以更好地利用I/O等待时间,从而提高整体效率。

另外,操作系统可能还有一些优化策略,例如使用多级反馈队列调度算法来优化进程调度,以使性能得到改善。

总的来说,性能的差异可能是由于系统调度和资源管理的复杂性以及进程间任务特性的不同造成的。不同的情况下可能会有不同的结果,所以在选择并发数量时,需要进行测试和评估以确定最佳的配置。

5、为什么60个进程比10个进程更慢?

有几个可能的原因导致60个进程比10个进程更慢:

(1)资源竞争:当并发进程数量增加时,每个进程都需要共享系统资源(如CPU时间、内存等),因此会产生更多的资源竞争。当并发进程过多时,系统可能无法有效地满足每个进程的资源需求,导致性能下降。

(2)上下文切换开销:进程之间的切换需要操作系统进行上下文切换,这涉及到保存和恢复进程的执行状态,这个过程需要一定的时间和开销。当并发进程数量增加时,上下文切换的次数也会增加,从而增加了系统的开销。

(3)系统调度策略:操作系统使用调度策略来决定进程的执行顺序和分配CPU时间。不同的调度策略可能会以不同的方式处理并发进程。当并发进程数量过多时,调度策略可能无法有效地平衡进程的执行,导致性能下降。

(4)系统资源限制:除了物理核心数量外,系统还有其他资源限制,如内存大小、磁盘带宽等。当并发进程数量增加时,可能会出现资源瓶颈,导致性能下降。

综上所述,性能差异可能是由于资源竞争、上下文切换开销、调度策略和系统资源限制等因素的综合影响。在选择并发进程数量时,需要进行测试和评估,找到适合系统配置的最佳数量。

6、为什么说并发执行的进程数量通常应该小于或等于物理内核的数量

并发执行的进程数量通常应该小于或等于物理内核的数量,这是因为每个物理内核都可以同时执行一个进程的指令。每个物理内核都有自己的处理器和一组寄存器,可以独立执行指令,并且可以并行处理多个指令流。

当并发执行的进程数量超过物理内核数量时,就会引发资源竞争和上下文切换开销。由于物理内核的数量是固定的,而进程数量的增加会导致进程之间频繁地进行上下文切换,这会增加系统的开销并降低性能。

另外,一些计算密集型任务可能会受到物理内核数量的限制。当进程数量超过物理内核数量时,系统会通过时间片轮转算法在不同的进程之间切换,每个进程只能获得有限的CPU时间片。这可能会导致每个进程的执行速度变慢,因为它们无法充分利用物理内核的计算能力。

因此,通常建议在并发编程中,将并发执行的进程数量控制在物理内核数量的范围内,以获得更好的性能和资源利用率。

7、处理图片灰度值,应该采用哪种并发编程方式?

实验已经发现,多进程优势明显,只是在考虑并发编程时,如处理图片灰度值,还是可以采用多种并发编程方式,具体选择哪种方式取决于应用的需求和环境的限制。以下是几种常见的并发编程方式可以考虑:

(1)多进程:可以使用multiprocessing模块,将每张图片的处理任务分配给多个进程并行处理。由于每个进程有自己独立的内存空间,可以充分利用多核处理器的计算能力。

(2)多线程:可以使用threading模块,将每张图片的处理任务分配给多个线程并行处理。由于线程共享同一进程的内存空间,因此对于计算密集型任务,多线程并发执行可能不如多进程效果好,但对于IO密集型任务可以有效利用等待时间。

(3)异步编程:可以使用asyncio模块,将图片处理任务封装成协程函数,通过await关键字实现异步执行。异步编程适用于IO密集型任务,可以充分利用等待时间,提高程序的效率。

具体选择哪种并发编程方式,需要综合考虑任务的特点、硬件资源、性能要求和编程复杂度等因素。在某些情况下,结合多种并发编程方式,例如多进程+多线程或多进程+异步编程,可以进一步提高性能和效率。

8、为什么处理图片灰度值,多进程比多线程耗时更少,效果更好?

处理图片灰度值时,多进程比多线程耗时更少、效果更好的主要原因是与GIL(全局解释器锁)相关。

在Python中,由于GIL的存在,同一时间只有一个线程能够执行Python字节码。这意味着在多线程情况下,即使有多个线程并发执行,但由于GIL的限制,实际上并不能同时利用多核处理器的计算能力。因此,对于CPU密集型任务(如计算灰度值),多线程并不能充分发挥多核处理器的优势,导致耗时较长。

而多进程则可以克服GIL的限制。每个进程都有自己独立的Python解释器和GIL,因此多个进程能够并行地利用多个CPU核心,同时执行任务。对于CPU密集型任务,多进程可以更好地利用多核处理器的计算能力,从而加快任务的处理速度。

此外,多进程在处理I/O密集型任务时也具有优势。由于I/O操作通常涉及等待时间,使用多进程可以充分利用这些等待时间,让其他进程继续执行任务,从而提高整体的并发性能。

需要注意的是,多进程的资源消耗会更大,包括更多的内存和进程间的通信开销。因此,在选择多进程还是多线程时,需要综合考虑任务类型、计算密集型还是I/O密集型、资源限制等因素,并根据具体情况选择最适合的并发编程方式。

9、为什么计算时间的装饰器没有监控到协程函数所用的时间?

协程函数的执行时间可能无法被装饰器直接监控到,这是由于协程函数的特性所导致的。协程函数在调用时会返回一个协程对象,而不是立即执行函数体内的代码。协程对象需要通过事件循环(event loop)来驱动执行。

由于装饰器在协程函数返回协程对象时被调用,而不是在协程函数执行时被调用,所以装饰器无法准确地测量协程函数的执行时间。

如果你希望测量协程函数的执行时间,可以在协程函数内部使用 time 模块的函数来手动记录开始和结束时间,并计算时间差。

10、一个脚本使用多进程执行代码后,为什么立即再次执行耗时比之前更久?

在使用多进程执行代码后,再次执行相同的代码可能会出现耗时更长的情况,这可能是由于以下原因导致的:

(1)进程资源占用:多进程会占用系统资源,包括CPU、内存等。如果之前的多进程执行过程中没有完全释放资源,再次执行时可能会导致资源竞争,从而导致执行时间增加。

(2)进程创建和销毁开销:每次创建进程都会有一定的开销,包括创建进程的时间和资源分配等。如果多次执行代码,每次都重新创建进程,这些额外的开销会累积导致耗时增加。

(3)其他系统因素:在多进程执行代码时,系统的负载可能会增加,特别是如果系统中有其他进程或任务正在运行。系统负载的增加可能会导致进程间的竞争,从而导致执行时间增加。

为了避免这种情况,可以尝试以下方法:

(1)确保在每次执行完多进程代码后,正确释放相关资源,包括关闭进程、清理临时文件等。

(2)考虑使用进程池(Process Pool)来管理进程的创建和销毁,这样可以避免频繁的进程创建和销毁带来的额外开销。

(3)在执行代码之前,确保系统的负载较低,可以暂停其他可能导致负载增加的任务或进程。

(4)考虑使用其他并发编程模型,如多线程或协程,根据实际情况选择合适的并发方式。

总的来说,多进程执行代码的耗时增加可能是由于资源竞争、进程创建销毁开销或系统负载等因素导致的。通过合理管理资源、减少额外开销以及调整系统负载,可以尽量避免耗时增加的情况。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/674062.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

excel数据的编排与整理——行列的批量处理

excel数据的编排与整理——行列的批量处理 1 一次性插入多行多列 1.1 插入连续行 1.1.0 题目内容 1.1.1 选中插入的位置➡按住shift键➡往下选中2行 1.1.2 鼠标右击➡点击插入 1.1.3 插入后的效果 1.2 插入不连续行 1.2.0 题目内容 1.2.1 按下ctrl键➡选中插入的位置,需要插…

7.4_1B树(二序查找树BST的升级版)

如果需要查找的值比节点小&#xff0c;会向左子树方向查找&#xff0c;如果比节点值大&#xff0c;会向右子树方向查找 拓展为5叉的形态 5叉排序树的定义 num是这个节点中真实存在的节点个数 那么一个节点中 最少有1个关键字&#xff0c;两个分叉 最多有4个关键字&#xff0c…

数据结构:二叉树详解

目录 概念&#xff08;在做习题中常用的概念&#xff09; 两种特殊的二叉树 二叉树的性质 二叉树的遍历&#xff08;重点&#xff09; 如上图&#xff1a; 二叉树的构建&#xff08;代码表示一颗二叉树和一些操作二叉树的方法&#xff09; 二叉树的oj习题讲解&#xff0…

代码审计-Java项目Filter过滤器CNVD分析XSS跨站框架安全

文章目录 Demo-Filter-过滤器引用Demo-ST2框架-组件安全CNVD-Jeesns-XSS跨站绕过CNVD-悟空CRM-Fastjson组件 Demo-Filter-过滤器引用 Filter&#xff1a;Javaweb三大组件之一(另外两个是Servlet、Listener) 概念&#xff1a;Web中的过滤器&#xff0c;当访问服务器的资源时&am…

编程语言的优劣评选标准与未来发展趋势——探索最佳编程语言选择

编程语言的优劣评选标准与未来发展趋势——探索最佳编程语言选择 评判标准不同编程语言的优点与缺点分析对编程语言未来发展的猜测和未来趋势 &#x1f495; &#x1f495; &#x1f495; 博主个人主页&#xff1a; 汴京城下君–野生程序员&#x1f495; &#x1f495; &#x…

编程输出三位数的水仙花数

目录 题目 分析思路 代码 题目 编程输出三位数的水仙花数 标准的 水仙花数 就是三位数&#xff0c;即将三位数的个位&#xff1b;十位&#xff1b;百位取出来&#xff0c;分别三次方相加&#xff0c;若个位&#xff1b;十位&#xff1b;百位三次方相加与原来的三位数相等&a…

模拟电路系列文章-频率响应的描述

目录 概要 整体架构流程 技术名词解释 技术细节 1.为什么受频率的影响 2.频率响应 小结 概要 提示&#xff1a;这里可以添加技术概要 电容和电感是储能元件&#xff0c;对不同频率的交流信号&#xff0c;它们具有不同的感抗或者容抗。虽然它们不消耗功率&#xff0c;但同电阻一…

【PHP】文件写入和读取详解

一&#xff0e;实现文件读取和写入的基本思路&#xff1a; 1&#xff0e;通过fopen方法打开文件&#xff1a;$fp fopen(/*参数&#xff0c;参数*/)&#xff0c;fp为Resource类型 2&#xff0e;进行文件读取或者文件写入操作&#xff08;这里使用的函数以1中返回的$fp作为参数…

Python网络爬虫基础进阶到实战教程

文章目录 认识网络爬虫HTML页面组成Requests模块get请求与实战效果图代码解析 Post请求与实战代码解析 发送JSON格式的POST请求使用代理服务器发送POST请求发送带文件的POST请求 Xpath解析XPath语法的规则集&#xff1a;XPath解析的代码案例及其详细讲解&#xff1a;使用XPath解…

macOS Sonoma 14 beta 2 (23A5276g) ISO、IPSW、PKG 下载

macOS Sonoma 14 beta 2 (23A5276g) ISO、IPSW、PKG 下载 本站下载的 macOS 软件包&#xff0c;既可以拖拽到 Applications&#xff08;应用程序&#xff09;下直接安装&#xff0c;也可以制作启动 U 盘安装&#xff0c;或者在虚拟机中启动安装。另外也支持在 Windows 和 Linu…

【致敬未来的攻城狮计划】打卡2:keil 程序下载尝试

下载 终于考完试了&#xff0c;然而攻城狮的截止期限也快到了QAQ&#xff0c;得尽快水&#xff08;划掉&#xff09;写几篇文章了&#xff01; 先争取可以成功下载一个空的程序。 先对上一篇文章下载 DFP 也就是 keil MDK Software Packs 做一个补充。我们要下载的是 RA_DFP…

[进阶]TCP通信综合案例:群聊

代码演示如下&#xff1a; 客户端&#xff1a; public class Client {public static void main(String[] args) throws Exception{System.out.println("客户端开启&#xff01;");//1.创建Socket对象&#xff0c;并同时请求与服务端程序的连接。Socket socket new…

网线制作实验

计算机网络综合实训 网线制作 所在院系 计算机与信息工程学院 学科专业名称 计算机科学与技术 导师及职称 柯宗武 教授 提交时间 2023.4.30 计算机网络综合实训报告 一、实验目的 1、掌握三类UTP线缆的制作和测试方法 2、了解三类UTP线缆的作用及布线方式 二、实验内容 1、局…

第二章 进程的描述与控制

目录 一、进程的概念、组成、特征 1.1 进程的概念 1.1.1 程序 1.1.2 进程 1.2 进程的组成 1.3 进程控制块PCB 1.4 进程的特征 二、进程的状态与转换 2.1 进程的状态 2.2 创建态、就绪态 2.3 运行态 2.4 阻塞态 2.5 终止态 2.6 进程状态的转换 2.7 进程的组织 三…

【C++篇】C++字符串:标准库string类

友情链接&#xff1a;C/C系列系统学习目录 知识总结顺序参考C Primer Plus&#xff08;第六版&#xff09;和谭浩强老师的C程序设计&#xff08;第五版&#xff09;等&#xff0c;内容以书中为标准&#xff0c;同时参考其它各类书籍以及优质文章&#xff0c;以至减少知识点上的…

python3.9 安装 pyspider

安装pyspider pip install pyspider 直接报错 Please specify --curl-dir/path/to/built/libcurl 于是从PythonLibs官网 中获取依赖并自行下载到本地 下载与3.9对应的 执行安装 pip install D:\下载\ad3-2.2.1-cp39-cp39-win32.whl 安装成功之后 执行 pip install p…

1748_Perl中使用通配符处理文件

全部学习汇总&#xff1a; GreyZhang/perl_basic: some perl basic learning notes. (github.com) 使用通配符处理文件一般是用于文件的拷贝、删除以及对文件逐个分析等功能。在Perl中遇到的相应的功能基本上也是如此。通配符匹配处理文件&#xff08;文件名&#xff09;需要使…

前端JavaScript入门-day01

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 JavaScript 介绍 1 JavaScript 是什么 1. JavaScript &#xff08;是什么&#xff1f;&#xff09; 2. 作…

H. Don‘t Blame Me(DP)

Problem - 1829H - Codeforces 很遗憾&#xff0c;这道题目的出题人没有想到一个有趣的故事&#xff0c;所以他只是让你解决以下问题。 给定由n个正整数组成的数组a&#xff0c;计算具有子序列中元素的按位AND在其二进制表示中恰好有k个设置位的非空子序列的数量。答案可能很大…

Winwebmail搭建邮件服务器

配置环境 角色系统IP地址DNS邮件服务器WindowServer2016192.168.1.1/24192.168.1.1客户端1Window10192.168.1.10/24192.168.1.1客户端2Window10192.168.1.20/24192.168.1.1 Winwebmail介绍 WinWebMail是安全高速的全功能邮件服务器&#xff0c;融合强大的功能与轻松的管理为…