在多线程环境中重用局部对象



我有以下场景:

    多线程应用程序
  • 我是不是控制线程创建。这是由框架(在本例中是芹菜)管理的
  • 我有一些对象是昂贵的实例化,和不是线程安全。使它们线程安全不是一个选项。
  • 对象可以在多个地方实例化,但是如果我在一个已经定义的线程中重新实例化同一个对象,则应该重用该对象。

我想出了下面的模式:

#!/usr/bin/env python
import threading
import time
class MyObj1:
    def __init__(self, name):
        self.name = name
local = threading.local()
def get_local_obj(key, create_obj, *pars, **kwargs):
    d = local.__dict__
    if key in d: obj = d[key]
    else       :
        obj = create_obj(*pars, **kwargs)
        d[key] = obj
    return obj
class Worker(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    def run(self):
        myobj1 = get_local_obj('obj1', MyObj1, (self.name))
        for _ in xrange(3):
            print myobj1.name
            time.sleep(1)
def test():
    ths = [Worker() for _ in xrange(2)]
    for t in ths : t.start()
test()

这里我自己创建线程,因为这只是一个测试,但如前所述,在实际应用程序中,我不能控制线程。

我感兴趣的是函数get_local_obj。我有几个问题:

    这个逻辑会保证对象不会在线程之间共享吗?
  1. 这个逻辑保证对象不会在一个线程中实例化超过一次吗?
  2. 这个内存会泄漏吗?
  3. 您对这种方法有什么总体评价吗?对于上面提到的场景有更好的建议吗?

编辑

只是澄清一下:我的应用程序是多线程的,但创建线程的不是我。我只是创建了一些对象,它们恰好在框架创建的线程中运行。我的一些对象不是线程安全的,所以每个线程只需要创建一次。因此get_my_object

编辑

local = threading.local()必须在全局作用域中定义。

这个怎么样?

class Worker (Thread):
  def __init__(self):
    super(Worker,self).__init__()
    self.m_local = threading.local()
  def get_my_obj(self):
    try:
      obj = self.m_local.my_object
    except AttributeError:
      self.m_local.my_object = create_object()
      obj = self.m_local.my_object
    return obj
  def run(self):
    my_obj = self.get_my_obj()
    # ...

最后它与您的示例类似,只是更干净。你把所有线程特定的代码放在一个地方,run函数"不知道"关于初始化的任何事情,它使用getter获得my_obj,并且getter只创建对象一次。线程。Local将保证数据是特定于线程的——这是它的工作。

我看不出有任何内存泄漏的原因。最后,您需要在python中出汗来获取泄漏:)

顺便说一句,这是你的代码的修改版本,在一定程度上是基于一个答案和另一个相关问题的简化。但基本上还是相同的模式。

#!/usr/bin/env python
import threading
import time
threadlocal = threading.local()
class MyObj1(object):
    def __init__(self, name):
        print 'in MyObj1.__init__(), name =', name
        self.name = name
def get_local_obj(varname, factory, *args, **kwargs):
    try:
        return getattr(threadlocal, varname)
    except AttributeError:
        obj = factory(*args, **kwargs)
        setattr(threadlocal, varname, obj)
        return obj
class Worker(threading.Thread):
    def __init__(self):
        super(Worker, self).__init__()
    def run(self):
        myobj1 = get_local_obj('obj1', MyObj1, self.name)
        for _ in xrange(3):
            print myobj1.name
            time.sleep(1)
def test():
    ths = [Worker() for _ in xrange(3)]
    for t in ths:
        t.start()
test()

实际上,没有get_local_obj()也可以做同样的事情:

#!/usr/bin/env python
import threading
import time
threadlocal = threading.local()
class MyObj1(object):
    def __init__(self, name):
        print 'in MyObj1.__init__(), name =', name
        self.name = name
class Worker(threading.Thread):
    def __init__(self):
        super(Worker, self).__init__()
    def run(self):
        threadlocal.myobj1 = MyObj1(self.name)
        for _ in xrange(3):
            print threadlocal.myobj1.name
            time.sleep(1)
def test():
    ths = [Worker() for _ in xrange(3)]
    for t in ths:
        t.start()
test()

这里有另一个不同的答案,它利用了我的一个线程级单例的想法。它完全摆脱了get_local_obj()函数。我没有做过很多测试,但到目前为止,它似乎是有效的。它可能比你想要的要多,因为它实际上实现了你在最后一个要点中所说的:

  • 对象可以在多个地方实例化,但是如果我在一个已经定义了对象的线程中重新实例化同一个对象,则应该重用该对象。

#!/usr/bin/env python
import threading
import time
threadlocal = threading.local()
class ThreadSingleton(type):
    # called when instances of client classes are created
    def __call__(cls, *args, **kwargs):
        instances = threadlocal.__dict__.setdefault(cls.__name__+'.instances', {})
        if cls not in instances:
            instances[cls] = super(ThreadSingleton, cls).__call__(*args, **kwargs)
        return instances[cls]
class MyClass(object):
    __metaclass__ = ThreadSingleton
    def __init__(self, name):
        print 'in MyClass.__init__(), name =', name
        self.name = name
class Worker(threading.Thread):
    def __init__(self):
        super(Worker, self).__init__()
    def run(self):
        myobj1 = MyClass(self.name)
        for _ in xrange(3):
            print 'myobj1.name:', myobj1.name
            myobj2 = MyClass(self.name+'#2') # this returns myobj1
            print 'myobj2.name:', myobj2.name # so this prints myobj1.name
            time.sleep(1)
def test():
    ths = [Worker() for _ in xrange(3)]
    for t in ths:
        t.start()
test()
注意,输出会有些混乱,因为它是由不同的线程生成的。这是可以修复的,但是我决定不添加它,使这个答案的本质复杂化。

相关内容

  • 没有找到相关文章

最新更新