问题描述
在zdppy_api多个woker部署的时候,有多个进程,其中有一个类,在创建实例的时候会很吃GPU,但是实际上这个类全局只需要创建一次就行,而多进程中,这个类会被创建多次,也就是会多次消耗GPU,从而导致容器在启动时就崩溃,无法正常启动。
解决方案
让这个吃GPU的类,变成单例,这样的话,全局就只有一个对象,只会被创建一次。
参考代码
import threading
class Singleton:
_instance = None
_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
with cls._lock:
if cls._instance is None:
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance
def __init__(self):
print("aaaaaaaaaaaaa")
# 使用
instance1 = Singleton()
instance2 = Singleton()
print(instance1 is instance2) # 输出 True
单例能够保证_instance只有一个,也就是instance1 is instance2的结果是True,但是不会保证__init__只执行一次,__init__每次创建对象都会执行。所以控制台,会输出两次print的结果。
__new__和__init__的区别
在Python中,__new__和__init__是两个特殊的方法,它们都在创建类的新实例时被调用,但它们的用途和行为有所不同。
__new__
__new__是一个静态方法,也就是说它不需要在实例上被调用,它的作用是创建一个新实例。__new__必须返回一个实例对象,这个对象可以是类本身的实例,也可以是其他类的实例。__new__通常用于继承不可变类型(如int、str、tuple等)或者实现单例模式。- 如果
__new__方法在派生类中被重写,那么它必须调用父类的__new__方法,并且通常使用super().__new__(cls, ...)来完成。
__init__
__init__是一个实例方法,它在__new__之后被调用,用于初始化新创建的实例。__init__不返回任何值,或者更准确地说,它返回None。__init__用于设置实例变量和执行其他初始化操作。- 如果
__init__在派生类中被重写,它不需要(也不应该)调用父类的__init__方法,除非有特定的初始化需要。
区别
- 调用顺序:
__new__首先被调用以创建实例,然后__init__被调用以初始化这个实例。 - 返回值:
__new__必须返回一个对象,而__init__不返回任何值。 - 用途:
__new__用于控制对象的创建过程,而__init__用于对象的初始化。
示例
class MyClass:
def __new__(cls, *args, **kwargs):
print("Creating an instance of MyClass")
instance = super().__new__(cls)
return instance
def __init__(self, value):
print("Initializing the instance")
self.value = value
# 创建实例
obj = MyClass(10)
在这个例子中,当你创建MyClass的一个实例时,首先会打印出"Creating an instance of MyClass",然后打印出"Initializing the instance"。这展示了__new__和__init__的调用顺序和它们在对象创建过程中的不同作用。
总结来说,__new__负责创建对象,而__init__负责初始化对象。理解这两个方法的区别对于深入掌握Python面向对象编程非常重要。
抽象可能有问题代码
A是需要消耗GPU的那个类,B是调用A的类。
class A:
def __init__(self):
print("a need gpu")
def fa(self):
print("fa")
class B:
def __init__(self):
self.a = A()
def __call__(self):
self.a.fa()
print("ba....")
b = B()
b()
b1 = B()
b1()
在原本代码中,B里面的a并不是单例的,所以每次执行,都会创建新的对象,每次创建新的对象,都会消耗较高GPU,一旦对象创建过多,GPU不够,程序就会崩溃。
__call__可以让类对象当成方法使用
在Python中,__call__ 方法是一个特殊方法,它允许一个实例变得可调用,即可以像函数那样被调用。当一个类的实例被用作函数时,Python会自动调用这个方法。
__call__ 的作用
- 使实例可调用: 当你想要让你的类的实例表现得像一个函数时,你可以实现
__call__方法。这在创建需要延迟计算或需要缓存结果的自定义对象时非常有用。 - 工厂模式:
__call__方法常用于工厂模式,其中类的实例负责返回其他类的实例。 - 装饰器: 在装饰器模式中,
__call__方法用于包装函数,并在调用被装饰函数前后执行额外的代码。 - 元类: 在创建类的类(即元类)时,
__call__方法用于创建类的实例。
以下是一些__call__方法的使用示例:
示例1:使实例可调用
class Greeting:
def __init__(self, name):
self.name = name
def __call__(self):
return f"Hello, {self.name}!"
g = Greeting("Kimi")
print(g()) # 输出: Hello, Kimi!
在这个例子中,Greeting类的实例g表现得像一个函数,当调用g()时,实际上是调用了g的__call__方法。
示例2:工厂模式
class Greeter:
def __call__(cls, name):
return cls().get_greeting(name)
@staticmethod
def get_greeting(name):
return f"Hello, {name}!"
# 使用
greeting = Greeter()
print(greeting("Kimi")) # 输出: Hello, Kimi!
在这个例子中,Greeter类有一个__call__方法,它接受一个名字并返回一个问候语。
示例3:装饰器
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Something is happening before the function is called.")
result = func(*args, **kwargs)
print("Something is happening after the function is called.")
return result
return wrapper
@my_decorator
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Kimi")
虽然这个例子没有直接使用__call__,但my_decorator函数返回的wrapper函数实际上是一个可调用对象,它在被调用时执行额外的代码。这是装饰器模式的一个典型应用,__call__方法在内部被用来使wrapper可调用。
__call__方法是Python中一个非常强大的特性,它提供了极大的灵活性,允许你以函数的方式使用对象。
最终的解决方案
import threading
class A:
def __init__(self):
print("a need gpu")
def fa(self):
print("fa")
class B:
_a = None
_lock = threading.Lock()
def __init__(self):
with B._lock:
if B._a is None:
B._a = A()
def __call__(self):
self._a.fa()
print("ba....")
b = B()
b()
b1 = B()
b1()
这里核心是对B进行了改造。
- 当创建对象的时候,就判断a的实例是否已经被创建了
- 如果被创建了,就直接啥也不干
- 如果没被创建,就创建对象并赋值
- 这样,在
__call__,调用类的_a对象的时候,始终是唯一的 - 最终的结果就是,
need_gpu只输出了一次