【python 多线程】初体验+单线程下载器+多线程并行下载器+ 多进程下载器 以及线程和进程的切换成本比较

news2025/1/11 7:08:08

前置知识:
ref:https://www.osgeo.cn/pillow/reference/ImageFile.html
ref:https://blog.csdn.net/weixin_67510296/article/details/125207042

1.多线程初体验

主线程的id和进程的id是一个
查看进程pid下有多少个线程

ps  -T -p pid
(base) D:\code\python_project\python_coroutine>C:/ProgramData/Anaconda3/python.exe d:/code/python_project/python_coroutine/01demo.py
threading.active_count=1
i am producer cnt=1 thread_id1 = 15984 thread_id2 = 15984
threading.active_count=2
i am consumer cnt=0 thread_id1 = 12828 thread_id2 = 12828
threading.active_count=3
i am producer cnt=1 thread_id1 = 15984 thread_id2 = 15984
i am consumer cnt=0 thread_id1 = 12828 thread_id2 = 12828
i am producer cnt=1 thread_id1 = 15984 thread_id2 = 15984
import threading
import time
cnt = 0
def producer():
    global cnt
    while True:
        cnt += 1
        print("i am producer cnt={} thread_id1 = {} thread_id2 = {}".format(cnt,threading.get_ident(), threading.get_native_id()))
        time.sleep(1) 
    pass
def consumer():
    global cnt
    while True:
        if cnt <= 0:
            continue
        cnt -= 1
        print("i am consumer cnt={} thread_id1 = {} thread_id2 = {}".format(cnt,threading.get_ident(), threading.get_native_id()))
        time.sleep(1)
if __name__ == "__main__":
    print("threading.active_count={}".format(threading.active_count()))
    t1 = threading.Thread(target=producer)
    t2 = threading.Thread(target=consumer)
    t1.start()
    print("threading.active_count={}".format(threading.active_count()))
    t2.start()
    print("threading.active_count={}".format(threading.active_count()))

查看当前程序的活跃线程数量

threading.active_count()

在这里插入图片描述

2.单线程下载器

在这里插入图片描述
下载模块:从网络上下载图片 I/O密集型
哈希模块:cpu密集
存储模块:I/O密集
单线程的是串行的,先下载,再哈希计算重命名,再存储,都是在主线程完成。
实际上三个功能应该并行起来:主线程负责调度,三个线程分别实现下载,哈希和存储。

2.1 目录结构

在这里插入图片描述
modules 下的三个文件是对三个功能的实现,base是定义的抽象方法
const.py 是枚举类。
scheduler.py 是总调度方法

在写 base.py 和 其子类的时候,发现子类可以重载父类的方法,也就是参数可以不相同。
另外,base中定义的方法,需要raise 异常,以防 base 类中控制的总实现发现运行中,子类因为拼写错误而没有运行。

2.2 单线程下载器

在实现单线程下载器的时候,顺便完成了整个框架的搭建,可以方便的改成多线程下载器

import os
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from const import CaclType
class BaseModule():
    """
    抽象模块
    """
    def __init__(self) -> None:
        self.calc_type = CaclType.SingleThread
    
    def set_calc_type(self, type_):
        self.calc_type = type_
        
    def _process(self):
        raise NotImplementedError
    
    def _process_singlethread(self):
        raise NotImplementedError
    
    def process(self, list_):
        if self.calc_type == CaclType.SingleThread:
            return self._process_singlethread(list_)
        else:
            pass
    ```
    Downloader 下载器实现
    ```python
    import os
import sys

import requests
from PIL import ImageFile
import numpy as np
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from .base import BaseModule

from const import CaclType
class Downloader(BaseModule):
    """下载模块
    """
    def __init__(self) -> None:
        super().__init__()
    
    def _process(self,url):
        print("download url:" + url)
        response = requests.get(url)
        content = response.content
        # 直接从网络上面抓取的数据,没有经过任何解码,所以是一个 bytes类型,其实在硬盘上和在网络上传输的字符串都是 bytes类型,
        parser = ImageFile.Parser()
        parser.feed(content)
        img = parser.close()
        # img.save("./a.jpg")
        # 修改成numpy的格式
        img = np.array(img)
        return img
    
    def _process_singlethread(self,list_):
        response_list = []
        for url in list_:
            img = self._process(url)
            response_list.append(img)
        return response_list
    
    def process(self, list_):
        if self.calc_type == CaclType.SingleThread:
            return self._process_singlethread(list_)
        else:
            pass
    
    
if __name__ == "__main__":
    dl = Downloader()
    list_ = ["https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2023%2F0907%2Fb90e12c7j00s0lqz0000lc000ku00dpm.jpg&thumbnail=660x2147483647&quality=80&type=jpg"]
    aa = dl.process(list_)
    print(aa)

哈希实现:
···python
import os
import sys
from scipy import signal
import requests
from PIL import ImageFile,Image
import numpy as np
import hashlib
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(file))))
from .base import BaseModule

class Hasher(BaseModule):
“”"
哈希模块
“”"
def init(self) -> None:
super().init()

def _process(self,item):
    # 卷积
    conv = [[[0.1],[0.05],[0.1]]]
    img = signal.convolve(item,conv)
    img = Image.fromarray(img.astype("uint8")).convert("RGB")
    # 哈希
    md5 = hashlib.md5(str(img).encode("utf-8")).hexdigest()
    return md5

def _process_singlethread(self,list_):
    md5_list = []
    for img in list_:
        md5 = self._process(img)
        md5_list.append(md5)
    return md5_list

···
存储实现
···python
import os
import sys
from scipy import signal
import requests
from PIL import ImageFile,Image
import numpy as np
import hashlib
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(file))))
from .base import BaseModule
class Storager(BaseModule):
def init(self) -> None:
super().init()

def _process(self,item):
    content,path = item
    print("save path:{}".format(path))
    content = Image.fromarray(content.astype('uint8')).convert("RGB")
    content.save(path)

def _process_singlethread(self,list_):
    # item = (content, path)
    for item in list_:
        self._process(item)

···
总调度实现

import os
from modules.downloader import Downloader
from modules.hasher import Hasher
from modules.storager import Storager
class Scheduler:
 """调度模块
 """
 def __init__(self) -> None:
     self.downloader = Downloader()
     self.hasher = Hasher()
     self.storager = Storager()
 def _wrap_path(self, md5):
     filename = '{}.jpg'.format(md5)
     STORAGE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'images')
     path = os.path.join(STORAGE_PATH, filename)
     return path
 def process(self):
     # 1.加载图片url列表
     url_list = ["https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2023%2F0907%2Fb90e12c7j00s0lqz0000lc000ku00dpm.jpg&thumbnail=660x2147483647&quality=80&type=jpg"]
     # 2. 调度下载模块
     content_list = self.downloader.process(url_list)
     # 3. 调度hash模块
     md5_list = self.hasher.process(content_list)
     # 4. 调度存储模块
     item_list = []
     for content, md5 in zip(content_list, md5_list):
         save_path = self._wrap_path(md5)
         item = (content,save_path)
         item_list.append(item) 
     self.storager.process(item_list)


if __name__ == "__main__":
 scheduler = Scheduler()
 scheduler.process()
     
···
枚举实现:
```python
from enum import Enum

class CaclType(Enum):
 SingleThread = 0
 MultiThread = 1
 MultiProcess = 2
 PyCoroutine = 3
 
if __name__ == "__main__":
 print(CaclType.SingleThread.value==0)

10 张图片的耗时:{‘network_time’: [0.913635], ‘cpu_time’: [1.68479], ‘disk_time’: [0.14452]}
-------------------------------------------------------------- 至此,一个单线程的下载器已经完成------

2.3 多线程下载器

{‘network_time’: [0.920507], ‘cpu_time’: [1.766117], ‘disk_time’: [0.146528]}
{‘network_time’: [0.450087], ‘cpu_time’: [1.698905], ‘disk_time’: [0.089213]}
多线程用线程池的方式实现,2个线程,一共url是7-10个,具体忘了
下载:

    def _process_multithread(self,list_):
        response_list = []
        task_list = []
        for url in list_:
            task = tp.submit(self._process,(url))
            task_list.append(task)
        for task in task_list:
            img = task.result()
            response_list.append(img)
        return response_list

哈希:

    def _process_multithread(self,list_):
        task_list = []
        md5_list = []
        for img in list_:
            task = tp.submit(self._process,(img))
            task_list.append(task)
        for task in task_list:
            md5 = task.result()
            md5_list.append(md5)
        return md5_list

存储:

    def _process_multithread(self,list_):
        task_list = []
        for item in list_:
            task = tp.submit(self._process,(item))
            task_list.append(task)
        for task in task_list:
            task.result()
        # 这里仍然需要等待完成的结果,否则计算时间会提前

三者的形式是相同的,都是submit发送task,然后 result等待收结果。
观察可得,下载和存储多线程有效果,而hash多线程没啥效果,甚至会变慢(因为上下文切换的原因),是因为python的多线程是伪多线程,因为GIL的存在,但对i/o操作还是可以加速的。

那么python 为啥不去掉GIL呢,而在操作内存的地方,追加锁,这样会增加和释放很多个锁,有人做过实验,会减慢单线程的操作,使得它的性能下降50%。
在这里插入图片描述

后来在python3.x的时候 python的GIL做过优化,python解释器讲python代码解释成字节码片段,100个ticks讲进行一次检查,若没有切换,则强制切换。这样有个不好的地方,字节码片段的执行并不是等时长的,有的字节码耗时长,有的耗时短。
在这里插入图片描述
后来变成了,5毫秒强制切换,和遇到i/o也切换。

在这里插入图片描述
在这里插入图片描述

3.多进程

进程池和线程池的用法几乎完全一样。
在这里插入图片描述
线程和进程都是将job抽象成了task任务,进程/线程池维护在了一个队列里面,由ThreadPoolExecutor/ProcessPoolExecutor维护。
不同的是,进程间的数据是相互隔离的,而线程池的全局数据是共享的,而进程间的数据需要同步,是用共享内存的方式实现的,有几种数据结构,queue,map等等好几个。

量化分析结果

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
造成这个差异的原因是因为管理上下文的成本!那么抛出下面的问题?

进程的上下文切换会比线程的上下文切换差多少???

上下文切换的命令:

sar -w 1 1000

在这里插入图片描述
在2个多线程和2个多进程的比较,多进程的切换,是多线程切换的2~3倍,而且线程和进程的数量越多,拉开的差距越大
在这里插入图片描述
在这里插入图片描述

cpu核数、并发数和性能的关系

如何在环境中配置最合理的进程数量,才能达到最好的并发,来指导我们调优。

统筹方法

就是数学方法,就是小学的华罗庚数学家的那个…甘特图,或者前置项。。。。步骤间和任务内的并行关系。
在这里插入图片描述
但是步骤3拆分的进程/线程越多越好么? 答案当然是否定的,但是有误量化的标准呢?
用阿姆达尔定律,代表理论的最大加速比

阿姆达尔定律

在这里插入图片描述
上面的例子,
N = 4
P = 25 /31 = 0.8065,
代入到阿姆达尔定律
在这里插入图片描述
实际计算的结果,用N=4

31/12.25 X100% = 253.06%
假定 P=0.8065 提升N, 是否永远有效?答案不是的。
在这里插入图片描述

总结

至此已完成了单线程和多线程的下载器改造,并分析了python的多线程为啥是伪多线程,适合在什么场景使用。
这是慕课的一个系列课程,之前断断续续学习过一遍,那时候没咋用过python,用python两年后再看,还是收获很大!
还有协程的知识参加我的另一篇文章: https://mp.csdn.net/mp_blog/creation/success/130478060

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

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

相关文章

《追逐胜利:编程之路上的三子棋游戏实践》

文章目录 前言一、三子棋游戏规则二、步骤详解1.游戏菜单的实现2.棋盘的实现2.1 初始化棋盘2.2 打印棋盘 3.游戏逻辑实现3.1 玩家下棋3.2 电脑下棋 4.判断输赢4.1 win函数实现 5.完整代码 总结 前言 大家好&#xff01;我是艾老虎尤&#xff01;今天我很高兴来和大家分享我最近…

【linux基础(五)】Linux中的开发工具(上)---yum和vim

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到开通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; Linux中的开发工具 1. 前言2.…

CMD 命令和 ENTRYPOINT 命令的区别

目录 CMD 命令CMD-shell 形式1. 创建 Dockerfile12. 构建和运行新镜像3. 覆盖 CMD4. 添加命令选项 CMD-exec形式1. 创建Dockerfile2、构建和运行新镜像2.覆盖 CMD和添加命令选项 ENTRYPOINT 命令ENTRYPOINT-shell1. 创建Dockerfile3、构建和运行新镜像2. 覆盖 ENTRYPOINT 和 添…

华为云云耀云服务器L实例评测|了解配置和管理L型云服务器

华为云云耀云服务器L实例配置和管理教程 华为云云耀云服务器L实例的介绍概述特点优势与弹性云服务器&#xff08;ECS&#xff09;的对比 注册和创建L型云服务器注册华为云账号创建L型云服务器实例配置实例参数配置其他参数尝试登录 远程登录 L实例查看公网ip通过本地shell远程连…

QT Pyside2 Designer 的基本使用

文章目录 前言PySide2PySide2 Designer 一、安装PySide2、PyQt5二、使用designer.exe2.1 工具的大致介绍2.2 创建一个新的UI2.3 UI文件另存为/保存(CtrlS)2.4 使用python操作UI文件 总结 前言 PySide2 QT PySide2 是一个用于 Python 编程语言的开源框架&#xff0c;它提供了与…

【建站教程】使用阿里云服务器怎么搭建网站?

使用阿里云服务器快速搭建网站教程&#xff0c;先为云服务器安装宝塔面板&#xff0c;然后在宝塔面板上新建站点&#xff0c;阿里云服务器网以搭建WordPress网站博客为例&#xff0c;阿小云来详细说下从阿里云服务器CPU内存配置选择、Web环境、域名解析到网站上线全流程&#x…

(10)(10.9) 术语表(一)

文章目录 前言 1 2.4Ghz 2 AGL 3 AHRS 4 APM 5 AMA 6 Arduino 7 APM (AutoPilot Mega) 8 ATC 9 Copter 10 Plane 11 Rover 12 BEC 13 Bootloader 14 COA 15 DCM 16 Eagle file 17 ESC 18 Firmware 19 FPV 20 FTDI 前言 &#xff01;Note 术语表未编入索…

C++零碎记录(十二)

22. 菱形继承 22.1 菱形继承简介 ① 菱形继承概念&#xff1a; 1. 两个派生类继承同一个基类 2. 又有某个类同时继承两个派生类 3. 这种继承被称为菱形继承 ② 羊继承了动物的数据&#xff0c;驼同样继承了动物的数据&#xff0c;当草泥马使用数据是&#xff0c;就会产生二义…

重构:在新底座之上让应用重生

应用重构正在开启一条云原生时代的新赛道。 数字化发展到今天&#xff0c;企业面临的挑战不仅来自技术层面&#xff0c;更来自认知层面。新架构、新应用正在重新定义数字生产力&#xff0c;重塑商业模式与市场核心竞争力。对金融行业来说&#xff0c;也是如此&#xff0c;一场…

计算机网络第六章——应用层(上)

人生若只如初见&#xff0c;何事秋风悲画扇 文章目录 基于服务的使用以及服务的提供而诞生的两个应用模型&#xff0c; 传输层提供一种端到端的服务&#xff0c;但是不同的网络应用的应用进程之间还需要有一些不同的通信规则&#xff0c;因此在传输层之上建立了一个应用层&am…

信息化管理工程验收评测规范

一、信息工程验收程序&#xff1a; 1.信息化建设项目验收分为初步验收和竣工验收两个阶段。验收由建设单位自行对照招标文件、投标文件和合同执行&#xff0c;并提交初验报告&#xff1b;企业与第三方联系实施验收。 2.工程试运行后30个工作日内&#xff0c;项目建设单位应将…

[计算机入门] 设置日期和时间

3.8 设置日期和时间 在任务栏的最右边是可以看到当前的日期和时间的。当然&#xff0c;如果这里的显示不对&#xff0c;也是可以进行设置的。 1、在任务栏的日期和时间位置&#xff0c;右键鼠标&#xff0c;在弹出的菜单中&#xff0c;点击调整日期/时间。 2、一般情况下&am…

easypoi和poi版本兼容问题记录

最近在开发导出word的功能&#xff0c;遇到下面的问题 提示xml报错的问题&#xff0c;我一度以为是项目换了java11造成的。经过询问朋友&#xff0c;得知有可能是版本冲突造成的&#xff0c;就猛然想起来&#xff0c;我的项目里面还引入了poi这个包。 于是我吧poi的版本降低到了…

【算法训练-数组 五】【二分查找】:旋转数组的最小数字、旋转数组的指定数字

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【数组的二分查找】&#xff0c;使用【数组】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为…

C#,《小白学程序》第二十一课:大数(BigInteger)的四则运算之二,减法

1 文本格式 using System; using System.Linq; using System.Text; using System.Collections.Generic; /// <summary> /// 大数的&#xff08;加减乘除&#xff09;四则运算、阶乘运算 /// 乘法计算包括小学生算法、Karatsuba和Toom-Cook3算法 /// </summary> p…

GO语言网络编程(并发编程)Goroutine池

GO语言网络编程&#xff08;并发编程&#xff09;Goroutine池 1. Goroutine池 1.1.1. worker pool&#xff08;goroutine池&#xff09; 本质上是生产者消费者模型可以有效控制goroutine数量&#xff0c;防止暴涨需求&#xff1a; 计算一个数字的各个位数之和&#xff0c;例…

2.5 循环结构语句

在程序设计中&#xff0c;有时需要反复执行一段相同的代码&#xff0c;这时就需要使用循环结构来实现&#xff0c;Java语言提供了while循环、do-while循环、for循环。 一般情况下&#xff0c;一个循环结构包含四部分内容&#xff1a; 初始化部分&#xff0c;设置循环开始时变量…

正交试验设计法

正交实验设计 一、什么是正交试验设计法&#xff1f; 是一种成对测试交互的系统的统计方法。它提供了一种能对所有变量对的组合进行典型覆盖&#xff08;均匀分布&#xff09;的方法。 可以从大量的试验点中挑出适量的、有代表性的点&#xff0c;利用“正交表”&#xff0c;…

Rsync远程同步+inotify监控

一、rsync同步简介 一款快速增量备份工具 rsync&#xff08;Remote Sync&#xff0c;远程同步&#xff09; 是一个开源的快速备份工具&#xff0c;可支持本地复制&#xff0c;或者与其他SSH,rsync主机同步。 cp&#xff1a;将原文件完整的复制到指定的路径下&#xff0c;而且…

Vector底层原理——面试之我答

Vector概述 vector是STL中最常用的容器&#xff0c;vector主要功能是作动态数组来弥补传统数组的缺点&#xff0c;如&#xff1a;不灵活&#xff0c;不方便插入等等。 Vector支持随机访问&#xff0c;因此访问某一个元素的时间复杂度是O(1)。 vector中存储着许多易用的函数方法…