python并发编程实战

python并发编程有三种

  • 多线程Thread
  • 多进程Process
  • 多协程Coroutine

cpu密集型计算

cpu密集型也叫计算密集型,是指I/O在很短的时间就可以完成,cpu需要大量的计算处理,特点是cpu占用率相当高

例如:压缩解压缩、加密解密、正则表达式搜索

IO密集型

IO密集型指的是系统运作大部分的状态是CPU在等I/O(硬盘/内存)的读/写操作,cpu占用率仍然较低

例如:文件处理程序、网络爬虫程序、读写数据库程序

多线程、多进程、多协程的对比

多进程

  • 优点:可以利用多核CPU并行计算
  • 缺点:占用资源最多、可启动数目比线程少
  • 适用于:CPU密集型计算

多线程

  • 优点:相比进程,更轻量级、占用资源少
  • 缺点:
    • 相比进程:多线程只能并发执行,不能利用多CPU(GIL)
    • 相比协程:启动数目有限,占用内存资源,有线程切换开销
  • 适用于:IO密集型计算、同时运行的任务数目要求不多

多协程

  • 优点:内存开销最少、启动协程数量最多
  • 缺点:支持的库有限制(aiohttp vs request)、代码实现复杂
  • 适用于:IO密集型计算、需要超多任务运行、但有现成库支持的场景

python慢的头号嫌疑犯——全局解释器锁GIL

python速度慢的原因一:动态类型语言,边解释边执行

python速度慢的原因二:GIL无法利用多核CPU并发执行

GIL是什么

全局解释器(Global Interpreter Lock)

是计算机程序设计语言解释器用于同步线程的一种机制,它使得任何时刻仅有一个线程在执行

即便在多核心处理器上,使用GIL的解释器也只允许同一时间执行一个线程

怎么规避GIL带来的限制

1.多线程threading机制依然是有用的,用于IO密集型计算

因为在I/O期间,线程会释放GIL,实现CPU和IO的并行

因此多线程用于IO密集型计算依然可以大幅提升速度

但是多线程用于CPU密集型计算时,只会更加拖慢速度

2.使用multiprocessing的多进程机制实现并行计算、利用多核cpu优势

为了应对GIL的问题,python提供了multiprocessing

python利用多线程加速爬虫

blog_spider.py

import requestsurls = [f"https://www.cnblogs.com/#p{page}" for page in range(1, 51)]def craw(url):r = requests.get(url)print(url, len(r.text))if __name__ == '__main__':craw(urls[0])

multi_thread_craw.py

import threading
import timeimport blog_spider# 单线程爬取
def single_thread():print("单线程爬取开始")for url in blog_spider.urls:blog_spider.craw(url)print("单线程爬取结束")# 多线程爬取
def multi_thread():print("多线程爬取开始")threads = []for url in blog_spider.urls:threads.append(threading.Thread(target=blog_spider.craw, args=(url,)))for thread in threads:thread.start()for thread in threads:thread.join()print("多线程爬取结束")if __name__ == '__main__':start = time.time()single_thread()end = time.time()single_time = end - startprint("单线程爬取时间:", single_time, "秒")print("------------------分割线---------------------")start = time.time()multi_thread()end = time.time()multi_time = end - startprint("多线程爬取时间:", multi_time, "秒")print("时间倍数:", single_time / multi_time)

单线程爬取时间:

image-20240929143010006

多线程爬取时间:

image-20240929143040682

python实现生产者消费者爬虫

多组件的Pipeline技术架构

复杂的事情一般都不会一下子做完,而是会分很多中间步骤一步一步完成

生产者消费者爬虫的架构

image-20240929144204508

多线程数据通信的queue.Queue

queue.Queue可以用于多线程之间的、线程安全的数据通信

blog_spider.py

import requests
from bs4 import BeautifulSoup
urls = [f"https://www.cnblogs.com/#p{page}" for page in range(1, 51)]def craw(url):r = requests.get(url)return r.textdef parse(html):# class="post-item-title"soup = BeautifulSoup(html, "html.parser")links = soup.find_all("a", class_="post-item-title")return [(link["href"], link.get_text()) for link in links]if __name__ == '__main__':for result in parse(craw(urls[3])):print(result)

producer_customer_spider.py

import queue
import random
import threading
import timeimport blog_spider# 生产者
def do_craw(url_queue: queue.Queue, html_queue: queue.Queue):while True:url = url_queue.get()html = blog_spider.craw(url)html_queue.put(html)print(threading.current_thread().name, f"爬取:{url}", "url_queue队列大小:", url_queue.qsize())time.sleep(random.randint(1, 2))# 消费者
# 输出对象fout
def do_parse(html_queue: queue.Queue, fout):while True:html = html_queue.get()results = blog_spider.parse(html)for result in results:fout.write(str(result) + "\n")print(threading.current_thread().name, f"results大小:", len(results), "html_queue队列大小:", html_queue.qsize())time.sleep(random.randint(1, 2))if __name__ == '__main__':url_queue = queue.Queue()html_queue = queue.Queue()for url in blog_spider.urls:url_queue.put(url)for idx in range(3):t = threading.Thread(target=do_craw, args=(url_queue, html_queue), name=f"生产者{idx}")t.start()fout = open("data.txt", "w")for idx in range(2):t = threading.Thread(target=do_parse, args=(html_queue, fout), name=f"消费者{idx}")t.start()

线程安全问题以及Lock解决方案

线程安全

线程安全指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。

由于线程的执行随时会发生切换,就造就了不可预料的结果,出现线程不安全

Lock用于解决线程安全问题

用法一:try-finally模式

import threading
lock = threading.Lock()
lock.acquire()
try:#do something
finally:lock.release()

用法2:with模式

import threading
lock = threading.Lock()
with lock:#do something
线程不安全案例代码

unlock_thread.py

import threading
import timeclass Account:def __init__(self, balance):self.balance = balancedef draw(account: Account, amount):if account.balance>=amount:# 这里加阻塞为了保证线程不安全现象发生time.sleep(0.1)print(threading.current_thread().name,"取钱成功")account.balance -= amountprint(threading.current_thread().name,"余额:",account.balance)else:print(threading.current_thread().name,"取钱失败")if __name__ == '__main__':account = Account(1000)ta = threading.Thread(name="线程a", target=draw, args=(account, 800))tb = threading.Thread(name="线程b", target=draw, args=(account, 800))ta.start()tb.start()

image-20240929170656638

线程安全案例代码

lock_thread.py

import threading
import time# 获取lock对象
lock = threading.Lock()class Account:def __init__(self, balance):self.balance = balancedef draw(account: Account, amount):with lock:if account.balance >= amount:time.sleep(0.1)print(threading.current_thread().name, "取钱成功")account.balance -= amountprint(threading.current_thread().name, "余额:", account.balance)else:print(threading.current_thread().name, "取钱失败")if __name__ == '__main__':account = Account(1000)ta = threading.Thread(name="线程a", target=draw, args=(account, 800))tb = threading.Thread(name="线程b", target=draw, args=(account, 800))ta.start()tb.start()

image-20240929171145373

好用的线程池ThreadPoolExecutor

线程池的原理

新建线程系统需要分配资源、终止线程系统需要回收资源,如果可以重用线程,则可以减去新建/终止的开销

线程池的执行过程可以分为以下几个步骤‌:

  1. 核心线程数检查‌:当提交任务后,线程池首先会检查当前线程数。如果此时线程数小于核心线程数,则新建线程并执行任务。
  2. 任务队列处理‌:随着任务的不断增加,线程数会逐渐增加并达到核心线程数。此时,如果仍有任务被不断提交,这些任务会被放入阻塞队列中等待执行。
  3. 非核心线程创建‌:如果任务特别多,达到了任务队列的容量上限,线程池就会继续创建非核心线程来执行任务,直到达到最大线程数。
  4. 拒绝策略‌:当线程数达到最大线程数时,如果仍有任务提交,线程池会执行拒绝策略,如抛出异常、丢弃最旧的任务等。

image-20240929171834940

使用线程池的好处

  1. 提升性能:因为减去了大量新建、终止线程的开销,重用了线程资源
  2. 适用场景:适合处理突发性大量请求或需要大量线程完成任务、但实际任务处理时间较短
  3. 防御功能:能有效避免系统因为创建线程过多,而导致系统负荷过大相应变慢等问题
  4. 代码优势:使用线程池的语法比自己新建线程执行线程更加简洁

ThreadPoolExecutor的使用语法

from concurrent.futures import ThreadPoolExecutor, as_completed

用法1:map函数,简单,注意map的结果和入参时顺序对应的

with ThreadPoolExecutor() as pool:results = pool.map(craw,urls)for result in results:print(result)

用法2:future模式,更强大。注意如果用as_completed顺序是不定的

with ThreadPoolExecutor() as pool:futures = [pool.submit(craw,url) for url in urls]for future in futures:print(future.result())for future in as_completed(futures):print(future.result())
代码演示
import concurrent.futures
import blog_spider#爬取
with concurrent.futures.ThreadPoolExecutor() as pool:htmls = pool.map(blog_spider.craw,blog_spider.urls)htmls = list(zip(blog_spider.urls,htmls))for url, html in htmls:print(url, len(html))
print("爬取结束")# 解析
with concurrent.futures.ThreadPoolExecutor() as pool:futures = {}for url, html in htmls:future = pool.submit(blog_spider.parse,html)futures[future] = url#有序打印for future, url in futures.items():print(url, future.result())print("开始无序打印")#无序打印for future in concurrent.futures.as_completed(futures):url = futures[future]print(url, future.result())

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis:持久化

1. Redis持久化机制 Redis 支持 RDB 和 AOF 两种持久化机制,持久化功能有效地避免因进程退出造成数据丢失问题, 当下次重启时利⽤之前持久化的文件即可实现数据恢复。 2.RDB RDB 持久化是把当前进程数据⽣成快照保存到硬盘的过程,触发 RDB…

从AR眼镜到智能巡检:XR技术的演变与未来潜力

XR,即扩展现实(Extended Reality),是一个涵盖了增强现实(AR)、虚拟现实(VR)和混合现实(MR)的广泛概念。 从我们最初接触到的手机应用到Hololens,…

Linux 网络配置 (深入理解)

前言 前期我比较迷惑Ubuntu 的网络配置。 我接触比较多的 Linux 发行版都是 Ubuntu ,我按照网上的一些教程配置网络发现,没有相关网络配置文件夹。然后我发现不是我的问题而是不同版本的配置方式和工具是不一样的。然后有些配置已经弃用了。 常见的网络…

fmql之Linux异步通知

正点原子第35章。 Linux下的异步通知_linux异步通知-CSDN博客 异步通知简介 驱动中的信号处理 应用程序对异步通知的处理 代码 async.c asyncAPP.c 运行

作家依靠AI一年内创作120部作品

近期,Tim Boucher因声称自己依托人工智能(AI)完成了逾120部作品而在社交网络上引起广泛关注。 Boucher的这种创作手法引发了众多讨论和争议。一些批评者对他依靠AI写作表示不满,认为这种做法缺乏诚实性,甚至涉嫌抄袭。…

利用git将项目上传到github

采用git而不是在pycharm中共享的原因:可能会出现上图报错 目录 1、创建github仓库2、在 git bash 中初始化Git仓库,添加文件,上传代码 1、创建github仓库 2、在 git bash 中初始化Git仓库,添加文件,上传代码

基于CNN+Transformer混合模型实现交通流量时序预测(PyTorch版)

前言 系列专栏:【深度学习:算法项目实战】✨︎ 涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域,讨论了各种复杂的深度神经网络思想,如卷积神经网络、循环神经网络、生成对…

Ansible集群服务部署案例

案例描述 本案例共讲述了多个节点部署Elk集群日志分析系统,分别在三个节点使用ansible部署Kibana、Logstash以及Elasticsearch服务。 案例准备 1. 规划节点 IP 主机名 节点 192.168.100.25 ansible Ansible节点 192.168.100.35 node1 Elasticsearch/Kiba…

利用低代码快速搭建电商小程序之商品列表页

目标: 搭建商城的一个商品列表页面(先做静态页) 开发环境: 访问白码低代码平台:https://www.bnocode.com/ 白码的新自定义页功能(使用vue框架) 前期准备: 需要先准备商品数据表…

[网络]NAT、代理服务、内网穿透、内网打洞

目录 一、NAT 1.1 NAT 技术背景 1.2 NAT IP 转换过程 1.3 NAPT(Network Address Port Translation) 1.地址转换表 2. NAPT(网络地址端口转换Network Address Port Translation) 3. NAT技术的缺陷 二、代理服务器 2.1 正向…

济南站活动回顾|IvorySQL中的Oracle XML函数使用示例及技术实现原理

近日,由中国开源软件推进联盟PG分会 & 齐鲁软件园联合发起的“PostgreSQL技术峰会济南站”在齐鲁开源社举办。瀚高股份IvorySQL作为合作伙伴受邀参加此次活动。 瀚高股份IvorySQL技术工程师 向逍 带来「IvorySQL中的Oracle XML函数兼容」的议题分享。在演讲中&a…

EasyCVR视频汇聚平台:解锁视频监控核心功能,打造高效安全监管体系

随着科技的飞速发展,视频监控技术已成为现代社会安全、企业管理、智慧城市构建等领域不可或缺的一部分。EasyCVR视频汇聚平台作为一款高性能的视频综合管理平台,凭借其强大的视频处理、汇聚与融合能力,在构建智慧安防/视频监控系统中展现出了…

后端Java-SpringBoot整合MyBatisPlus步骤(超详细)

1.新建项目。 2.点击完上一步的next之后,选择pom.xml文件中的依赖。 3.点击pom文件进行项目初始化。 按照下面的俩步骤刷新一下maven ,让文件生效 4.新建一个application.yml文件 5. 新建一个数据库mp,在数据库中新建一张user表 6.连接数据…

pdf文档翻译成英文很简单?搞定复杂的外语文献文档就靠它

转眼间又是一年国庆,怎么样,大一新生们适应得还好吗? 没事,刚到陌生的校园环境中,分分钟都在想家,还没适应集体宿舍生活很正常的...... 什么?已经有同学在着手准备写论文了?而且需要…

Facebook对现代社交互动的影响

自2004年成立以来,Facebook已经成为全球最大的社交媒体平台之一,改变了人们的交流方式和社交互动模式。作为一个数字平台,Facebook不仅为用户提供了分享生活点滴的空间,也深刻影响了现代社交互动的各个方面。本文将探讨Facebook如…

git使用“保姆级”教程4——版本回退及分支讲解

一、版本回退 1、历史回退(版本回退)——命令行git reset --hard 版本编号 注意:当前命令会让工作区的内容发生改变,可以理解成历史区(master分支)直接回到工作区比如:从版本4回到版本3,则工作区只会显示版本3的代码内容 1.1、指…

【Postgresql】安装新手教程

在以下postgresql官网下载软件 https://www.enterprisedb.com/downloads/postgres-postgresql-downloads下载完成后安装,找个记事本记录下安装过程中填写的数据库管理原的password和port 在所有程序目录中打开pgadmin 输入刚才的数据库管理员密码 自动跳转到以下…

腾讯二面:40亿QQ号, 1G内存,怎么去重?

面试这种场合,大家都懂,面试官总喜欢出点“奇奇怪怪”的题目,问你点“看似不可能”的问题——比如: 40亿个QQ号要去重,内存还只能给你1GB。 就像老板让你用两块钱预算搞个全公司团建,还得拍成《向往的生活》…

速通数据结构与算法第七站 排序

系列文章目录 速通数据结构与算法系列 1 速通数据结构与算法第一站 复杂度 http://t.csdnimg.cn/sxEGF 2 速通数据结构与算法第二站 顺序表 http://t.csdnimg.cn/WVyDb 3 速通数据结构与算法第三站 单链表 http://t.csdnimg.cn/cDpcC 4 速通…

1. 如何在服务器上租GPU跑实验 (以AutoDL为例) - 深度学习·科研实践·从0到1

目录 前言 1. 在AutoDL上注册账号 2. 在算力市场选择GPU 3. 创建实例 4. 控制台-容器实例界面(核心) 4.1 无卡模式(常用) 5. 帮助文档 前言 好记性不如烂笔头,本专栏将详细记录下本人学习深度学习工程实践&…