【问题标题】:Python3 weakref WeakMethod and thread safetyPython3 weakref 弱方法和线程安全
【发布时间】:2017-12-12 08:45:02
【问题描述】:

我正在尝试创建一个可以从另一个线程注册到对象的简单回调。在这种情况下,调用回调的初始对象在自己的线程上运行。

下面的例子可以很好地说明这一点:

from pprint import pprint
import sys
import weakref
import threading
import time

class DummyController(object):

  def __init__(self):
    self.name = "fortytwo"

  def callback(self):
    print("I am number : " + self.name)

class SomeThread(threading.Thread):

  def __init__(self, listener):
    threading.Thread.__init__(self)
    self.listener = listener

  def run(self):
    time.sleep(1)
    dummy = DummyController()
    self.listener.register_callback(dummy.callback)
    time.sleep(5)
    del dummy



class Listener(threading.Thread):

  def __init__(self):
    threading.Thread.__init__(self)
    self.runner = weakref.WeakMethod(self.default_callback)
    self.counter = 20

  def default_callback(self):
    print("Not implemented")

  def register_callback(self, function):
    self.runner = weakref.WeakMethod(function)

  def run(self):
    while self.counter:
      try:
        self.runner()()
      except Exception as e:
        pprint(e)

      self.counter -= 1
      time.sleep(1)



listen = Listener()
some = SomeThread(listen)

listen.start()
some.start()

现在上面的代码可以正常工作了。但我担心这里的线程安全。通读weakref docs,不是很清楚weakref是否真的是线程安全的,除了这行:

3.2 版更改:添加了对 thread.lock、threading.Lock 和代码对象的支持。

我可能只是没有读对。我是否需要添加锁定,或者一切实际上都很好并且线程安全?

非常感谢

【问题讨论】:

  • 文档意味着在 3.2 版本中,您可以对 thread.lock、threading.Lock 和代码对象进行弱引用,但并不意味着存在内部锁。而且我不明白哪个部分可能不安全?能举个例子解释一下吗?
  • 我的担心来自Listener运行对象函数的回调,但是另一个线程(SomeThread)然后删除了对象。因为它使用weakref.WeakMethod,所以对象应该立即从内存中删除,而不需要垃圾收集器的参与,所以我想知道如果删除回调函数对象时是否会导致竞争条件,如果对象被删除并且函数被调用同时。是不是清楚一点?

标签: python multithreading


【解决方案1】:

好的,我明白了。这不是线程安全的问题,只是weak reference的问题。

有一个可执行的例子:

from pprint import pprint
import sys
import weakref
import threading
import time
import gc

class SomeThread(threading.Thread):

    def __init__(self, listener):
        threading.Thread.__init__(self)
        self.listener = listener

    def run(self):
        class test:    # simplify this example.
            def callback(self, count):
                print(count)
        time.sleep(1)
        dummy = test()
        self.listener.register_callback(dummy.callback)
        time.sleep(5)
        del dummy
        gc.collect()  # add this line to do garbage collecting.


class Listener(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)
        self.runner = weakref.WeakMethod(self.default_callback)
        self.counter = 20

    def default_callback(self):
        print("Not implemented")

    def register_callback(self, function):
        self.runner = weakref.WeakMethod(function)

    def run(self):
        while self.counter:
            try:
                self.runner()(self.counter)
            except Exception as e:
                pprint(e)

            self.counter -= 1
            time.sleep(1)


listen = Listener()
some = SomeThread(listen)

listen.start()
some.start()

输出:

TypeError('default_callback() takes 1 positional argument but 2 were given',)
TypeError('default_callback() takes 1 positional argument but 2 were given',)
18
17
16
15
TypeError("'NoneType' object is not callable",)
TypeError("'NoneType' object is not callable",)
TypeError("'NoneType' object is not callable",)

如果您显式调用gc.collect()callback 将失去其最后一个强引用,然后变为None。你永远不知道gc 什么时候会收集垃圾,这是一个潜在的问题。

不管你是否使用线程,只是weak reference的正常行为。

顺便说一句,请注意退出 SomeThread.run 也会隐含 del dummy,您可以通过删除 del dummy 并将 gc.collect() 移动到 try 块中来测试它。

【讨论】:

  • 是的,这是我使用 wakref 的原因之一,因为我“需要”回调失去其最后的强引用。因此异常捕获,但是我担心这样一个事实,即强引用在另一个线程中被破坏的事情会产生竞争条件。非常感谢您的回答
猜你喜欢
  • 1970-01-01
  • 2016-09-05
  • 2012-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-31
  • 1970-01-01
相关资源
最近更新 更多