[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.pswp.cn/news/905495.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/905495.shtml
英文地址,請注明出處:http://en.pswp.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,用于表示鏈表中的一個結點。這個結構…