在多线程环境中重用本地对象

时间:2012-12-21 11:58:24

标签: python python-multithreading

我有以下情况:

  • 多线程应用程序
  • 控制线程创建。这由框架(在这种情况下是芹菜)管理
  • 我有一些实例化成本很高的对象,并且线程安全。使它们成为线程安全不是一种选择。
  • 可以在多个位置实例化对象,但如果我在一个已经定义的线程中重新实例化同一个对象,则应该重用该对象。

我想出了以下模式:

#!/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. 这个内存会泄漏吗?
  4. 您对此方法有任何一般性评论吗?对于上面建议的场景有什么更好的建议吗?
  5. 修改

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

    修改

    必须在全局范围内定义

    local = threading.local()。

3 个答案:

答案 0 :(得分:1)

这个怎么样?

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只创建一次对象。 threading.local将保证数据是特定于线程的 - 这是它的工作。

我没有看到任何内存泄漏的原因。最后,你需要出汗才能在python中获得泄漏:)

答案 1 :(得分:1)

FWIW,这是您的代码的修改版本,根据answeranother相关问题进行了简化。但它仍然基本上是相同的模式。

#!/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()

答案 2 :(得分:0)

这是另一个不同的答案,它利用了我拥有线程级单例的想法。它完全摆脱了你的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()
请注意,由于它是由不同的线程生成的,因此输出会有些混乱。这可以修复,但我决定不通过添加它来使这个答案的本质复杂化。