[python] Python单例模式:__new__与线程安全解析

一 实例的创建过程

我们之前了解过在构造一个类的实例化对象时,会默认调用__init__方法,也就是类的初始化也叫构造函数,但其实在调用__init__方法前会首先调用__new__方法(只有在py3新式类才有)。即下面

  1. __new__(): 创建实例

作用: 在内存中分配对象空间 2 返回对象的引用self传递给init方法

    2.__init__():  初始化实例

  • 当我们手动重写这个方法后发现 构造函数没有被调用了,并且调用test会报错

  • 此时我们调用查看构造的对象 ,发现它其实就是None

  • 因为new方法被重写了,并没有创建对象。也没有分配资源空间

class Test(object):def __init__(self):print("__init__")def __new__(cls, *args, **kwargs):print("__new__")def test(self):print("test")
test = Test()
test.test()

此时我们重写__new__方法

1.1 重点(重写__new__)

  • 重写__new__方法时一定要返回父类的__new__方法否则无法成功分配内存,
    return super().__new__

  • 这时候发现首先调用了__new__方法,然后调用了__init__方法。并且成功创建了实例对象

class Test(object):def __init__(self):print("__init__")def __new__(cls, *args, **kwargs):print("__new__")res = super().__new__(cls,*args, **kwargs)return res
test = Test()
print(test)

接下来看看一个类实例化的过程

1.2 实例化过程

  1. 首先执行__new__(),如果没有重写__new__,默认调用object内的__new__返回一个实例对象
  2. 然后再去调用__init__去初始化对象

# __new__是创建对象,分配空间等, __init__是初始化对象
# __new__是返回对象引用,__init__是定义实例属性
# __new__是类级别的方法,__init__是实例级别的方法

二 单例

单例是软件23种设计模式之一,一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问这个实例

2.1 单例创建的几种方式

# 1 通过@classmethmod类方法
# 2 通过装饰器
# 3 通过重写__new__ (主要方法)
# 4 通过导入模块

2.2 通过重写__new__方法实现

2.2.1设计流程:

# 1 定义一个类属性,初始值为None,用来记录单例对象的引用
# 2 重写__new__方法
# 3 进行判断,如果类属性是None,把__new__返回的对象引用保存进去
# 4 返回类属性中记录的对象引用

2.2.2代码实现:

  • 这时候会发现无论创建多少次实例对象,返回的内存地址的引用不变
class Sinstance(object):obj = None"""这是一个重写__new__方法的单例类"""def __new__(cls, *args, **kwargs):if cls.obj is None:cls.obj = super().__new__(cls)return cls.objdef __init__(self):print("__init__")s = Sinstance()
s2 = Sinstance()
print(s)
print(s2)

2.2.3 线程安全问题

  • 上面这种方式在遇到多线程访问时就会出现线程不安全。
  • 两个线程可能同时执行到if cls.obj is None:这一行检查,发现cls.obj都为None,然后各自创建一个新实例,这就破坏了单例模式的目标。
  • 为了确保在多线程环境下的线程安全性,你需要引入某种形式的同步机制来防止多个线程同时进入创建实例的代码块。最常见的做法是使用锁(Lock)。
  • 这种情况只会在第一次创建对象时有加锁解锁的额外开销,并不会对性能有太大的影响

在这个版本中,我们使用了双重检查锁定模式:首先不加锁进行一次检查,如果obj还未被初始化,则获取锁后(上锁)(再此检查是担心有别的线程在这个线程还会加锁的时候完成了实例创建),再检查一次后,并在此时真正地创建实例。这样做不仅保证了线程安全性,还提高了性能,因为大多数情况下不会进入加锁的代码段。只有当obj确实为None时,才会尝试获取锁并再次检查是否需要创建实例。这样可以减少锁的竞争,从而提高并发性能。

import threadingclass Sinstance(object):_lock = threading.Lock() # 创建一个锁对象obj = Nonedef __new__(cls, *args, **kwargs):if cls.obj is None:with cls._lock: #在此处加锁if cls.obj is None: #双重检查锁定,避免不必要的加锁开销cls.obj = object.__new__(cls)return cls.objdef __init__(self):print('__init__')

2.2.4 测试

线程安全

  • 这是一个创建10个线程来获取单例的方法,打印发现他们的地址引用是同一个
  • 则说明这种是线程安全的
# 测试函数:获取单例实例并打印其ID
def test_singleton():instance = Sinstance()print(f"Instance ID: {id(instance)}")threads = []
for _ in range(10):t = threading.Thread(target=test_singleton)threads.append(t)t.start()
# 等待所有线程完成
for t in threads:t.join()

线程不安全

import threading
import time
class Sinstance(object):obj = Nonedef __new__(cls, *args, **kwargs):if cls.obj is None:# 模拟一些工作负载time.sleep(0.001)cls.obj = object.__new__(cls)return cls.objdef __init__(self):print('__init__')# 测试函数:获取单例实例并打印其ID
def test_singleton():instance = Sinstance()print(f"Instance ID: {id(instance)}")threads = []
for _ in range(10):t = threading.Thread(target=test_singleton)threads.append(t)t.start()
# 等待所有线程完成
for t in threads:t.join()
  • 经过测试如果不加锁,确实线程是不安全的

2.3 通过模块导入的方式

利用模块导入的方式实现单例模式,在Python中实际上是一种非常简单且线程安全的方法。这是因为在Python中,模块在第一次被导入时会执行其顶层代码,并且Python的模块导入机制保证了每个模块只会被加载一次,即使多次导入同一个模块,也只会执行一次模块中的代码。这种特性天然地支持了单例模式的需求。

  • 这是因为 Python 的模块只会被加载一次,即使你多次导入同一个模块,
  • 在后续的导入操作中,Python只是重复使用已经加载的模块对象。
  • 这个在多线程的方式下是安全的.

首先我们再pymodule.py文件中创建这个te实例对象

import threading
import time
import threadingclass Sinstance(object):_lock = threading.Lock() # 创建一个锁对象obj = Nonedef __new__(cls, *args, **kwargs):if cls.obj is None:time.sleep(0.001)with cls._lock: #在此处加锁if cls.obj is None: #双重检查锁定,避免不必要的加锁开销cls.obj = object.__new__(cls)return cls.objdef __init__(self):print('__init__')
te = Sinstance()

接着我们再另一个py文件里调用

from pymodule import te as instance01
from pymodule import te as instance02
print(instance01)
print(instance02)
  • 这个是线程安全的

2.4 应用场景

# 1 系统缓存/软件内部配置
# 2 数据库连接池
# 3 任务调度器

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

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

相关文章

笔记本电脑打开网页很慢,一查ip地址网段不对怎么处理

我有一个笔记本,在家里连WIFI后获取到的ip地址网段不对,那么常规做法是手动去配置个静态IP和DNS,要知道笔记本IP地址默认采用的是DHCP,也就是动态获取ip地址。如果手动设置静态IP,也就是固定IP的话,你换个场…

怎样将MM模块常用报表设置为ALV默认格式(MB52、MB5B、ME2M、ME1M等)

【SAP系统研究】 对SAP系统中的报表,最方便的格式就是ALV了,可排序、可导出,非常友好。 但有些常见报表却不是默认ALV界面的,譬如MB52: 是不是有点别扭?但其实是可以后台配置进行调整的。 现将一些常用报表修改为默认ALV的方法进行总结,便于大家使用。 一、MB52、MB5…

Redis——达人探店

达人探店 发布探店笔记 探店笔记类似点评网站的评价,往往是图文结合,对应的表有两个: 发布博文对应两个接口 案例:实现查看发布探店笔记的接口 需求:点击首页的探店笔记,会进入详情页面,实现…

Git初始化相关配置

Git配置 在Git安装完成后,windows操作系统上会多出一个Git Bash的软件,如果是linux或者是macOS,那么直接打开终端,在终端中敲击命令即可 # 检查git版本 git -v # 或 git --version在使用git时,需要配置一下用户名和邮…

MySQL JSON_ARRAYAGG 实现汇总+明细数据展示

一、业务场景 在投注记录查询功能中,我们需要展示每个彩票期号(userId lotteryIssue分组)的汇总数据(总金额、总注数),同时也要显示该期号下的所有明细投注记录。 解决方案:JSON_ARRAYAGG MySQL 5.7 提供的 JSON_A…

【Lua】Redis 自增并设置有效期

【Lua】Redis 自增并设置有效期 方案一 每次执行都会更新有效期 EVAL "local current redis.call(INCRBY, KEYS[1], ARGV[1]);if tonumber(ARGV[2]) > 0 then redis.call(EXPIRE, KEYS[1], ARGV[2]) end;return current;" 1 mycounter 1 10 参数: 1 代表KEY…

CCF第七届AIOps国际挑战赛季军分享(RAG)

分享CCF 第七届AIOps国际挑战赛的季军方案,从我们的比赛经历来看,并不会,相反,私域领域问答的优秀效果说明RAG真的很重要 历经4个月的时间,从初赛赛道第1,复赛赛道第2,到最后决赛获得季军&…

YOLO v2:目标检测领域的全面性进化

引言 在YOLO v1取得巨大成功之后,Joseph Redmon等人在2016年提出了YOLO v2(也称为YOLO9000),这是一个在准确率和速度上都取得显著提升的版本。YOLO v2不仅保持了v1的高速特性,还通过一系列创新技术大幅提高了检测精度…

Linux-Ubuntu安装Stable Diffusion Forge

SD Forge在Win上配置起来相对简单且教程丰富,而在Linux平台的配置则稍有门槛且教程较少。本文提供一个基于Ubuntu24.04发行版(对其他Linux以及SD分支亦有参考价值)的Stable Diffusion ForgeUI安装配置教程,希望有所帮助 本教程以N…

量子计算实用化突破:从云端平台到国际竞合,开启算力革命新纪元

在硅谷某生物医药实验室,研究员艾米丽正盯着量子计算模拟界面露出微笑 —— 搭载中电信 "天衍" 量子计算云平台的 880 比特超导量子处理器,用 17 分钟完成了传统超算需 3 个月才能跑完的新型抗生素分子键合模拟。这个场景标志着量子计算正从 &…

计算机操作系统(七)详细讲解进程的组成与特性,状态与转换

计算机操作系统(七)进程的组成与特性,状态与转换 前言一、进程的组成1. 什么是“进程”?2. 进程的三个核心组成部分2.1 PCB(进程控制块)—— 进程的“身份证户口本”2.2 程序段—— 进程的“任务清单”2.3 …

MapReduce基本介绍

核心思想 分而治之:将大规模的数据处理任务分解成多个可以并行处理的子任务,然后将这些子任务分配到不同的计算节点上进行处理,最后将各个子任务的处理结果合并起来,得到最终的结果。 工作流程 Map 阶段: 输入数据被…

Linux操作系统实战:中断源码的性能分析(转)

Linux中断是指在Linux操作系统中,当硬件设备或软件触发某个事件时,CPU会中断正在执行的任务,并立即处理这个事件。它是实现实时响应和处理外部事件的重要机制,Linux中断可以分为两种类型:硬件中断和软件中断&#xff0…

AI Agent开发第66课-彻底消除RAG知识库幻觉-带推理的RAG

开篇 在第64课《AI Agent开发第64课-DIFY和企业现有系统结合实现高可配置的智能零售AI Agent(上)》中我们提到了提示词Rewrite,同时还讲到了2024年年末开始出现的新的理论,并把RAG系统推入到了3.0模式,业界出现了“3R”理念的RAG引擎,基于“3R”理念可以彻底消除RAG的幻觉…

Clion内置宏$PROJECT_DIR$等

CLion 内置宏 文章目录 CLion 内置宏通用路径相关宏路径相对化宏 官方文档地址: https://www.jetbrains.com/help/clion/built-in-macros.html 通用路径相关宏 宏名称含义说明示例$WORKSPACE_DIR$当前项目所属的工作区根目录路径。/home/user/workspace$PROJECT_D…

机器学习基础课程-5-课程实验

5.1 实验介绍 实验背景 在这个项目中,您将使用1994年美国人口普查收集的数据,选用几个监督学习算法以准确地建模被调查者的收入。然后,您将根据初步结果从中选择出最佳的候选算法,并进一步优化该算法以最好地建模这些数据。你的目…

Android RecyclerView自带的OnFlingListener,Kotlin

Android RecyclerView自带的OnFlingListener,Kotlin Android启动应用时屏蔽RecyclerView滑动,延时后再允许滑动,Kotlin-CSDN博客 使用了GestureDetectorRecyclerView的setOnTouchListener检测用户的快滑fling事件。发现RecyclerView也自带了监…

第3.4节 调用链路分析服务开发

3.4.1 什么是Code Call Graph(CCG) Code Call Graph(CCG)即业务代码中的调用关系图,是通过静态分析手段分析并构建出的一种描述代码间关系的图。根据精度不同,一般分为类级别、方法级别、控制流级别&#x…

【Liblib】基于LiblibAI自定义模型,总结一下Python开发步骤

一、前言 Liblib AI(哩布哩布 AI)是一个集成了先进人工智能技术和用户友好设计的 AI 图像创作绘画平台和模型分享社区。 强大的图像生成能力 :以 Stable Diffusion 技术为核心,提供文生图、图生图、图像后期处理等功能&#xff…

编程日志5.5

树的结构代码 #include<iostream> using namespace std; //由于树的每个结点可能有一些孩子结点,这些孩子结点的数量不确定,所以可以用一个链表来把所有的孩子结点给串起来 //链表结点定义 //这段代码定义了一个结构体ListNode,用于表示链表中的一个结点。这个结构…