【问题标题】:how do I make a 2.7 python context manager threadsafe如何使 2.7 python 上下文管理器线程安全
【发布时间】:2016-01-29 04:45:34
【问题描述】:

我有一个在 Django 服务上运行的大型 python 应用程序。我需要关闭某些操作的权限测试,所以我创建了这个上下文管理器:

class OverrideTests(object):

    def __init__(self):
        self.override = 0

    def __enter__(self):
        self.override += 1

    # noinspection PyUnusedLocal
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.override -= 1
        assert not self.override < 0

    @property
    def overriding(self):
        return self.override > 0

override_tests = OverrideTests()

然后应用程序的各个部分可以使用上下文管理器覆盖测试:

with override_tests:
    do stuff
    ...

在 do stuff 中,上述上下文管理器可以在不同的功能中多次使用。计数器的使用可以控制这一点,并且它似乎工作正常......直到涉及线程。

一旦涉及到线程,全局上下文管理器就会被重新使用,因此,测试可能会被错误地覆盖。

这是一个简单的测试用例 - 如果将 thread.start_new_thread(do_id, ()) 行替换为简单的 do_it 行,但会出现如下所示的异常失败:

def stat(k, expected):
    x = '.' if override_tests.overriding == expected else '*'
    sys.stdout.write('{0}{1}'.format(k, x))


def do_it_inner():
    with override_tests:
        stat(2, True)
    stat(3, True)  # outer with context makes this true


def do_it():
    with override_tests:
        stat(1, True)
        do_it_inner()
    stat(4, False)


def do_it_lots(ntimes=10):
    for i in range(ntimes):
        thread.start_new_thread(do_it, ())

我怎样才能使这个上下文管理器线程安全,以便在每个 Python 线程中,即使它是可重入的,也能始终如一地使用它?

【问题讨论】:

  • 您是否尝试过不创建单个上下文管理器,而是使用with OverrideTests(),为每次使用创建单独的实例?
  • 测试代码访问上下文管理器单例上的“全局值”,以了解是否应用测试。这不在 with 语句的直接上下文中。测试方法中有if override_tests.overriding: return True,因此它们被覆盖。如果有多个上下文管理器实例,我不能这样做。
  • 任何依赖于改变全局状态的东西都会遇到线程问题。听起来您可能需要重新考虑这里的整个结构。通过检查那种全局标志来改变它们的行为的函数不是很健壮。例如,您可以将所有测试函数放入一个类中,以便每个实例存储自己的覆盖状态,然后为每个线程创建该类的新实例。
  • 无法将测试集中到一个类中。我正在查看 threading.local() 但我还没有设法完成这项工作。

标签: python django multithreading python-2.7


【解决方案1】:

这是一种似乎的工作方式:让您的 OverrideTests 类成为threading.local 的子类。为了安全起见,您应该在 __init__ 中调用超类 __init__(尽管即使您不这样做,它似乎也可以工作):

class OverrideTests(threading.local):

    def __init__(self):
        super(OverrideTests, self).__init__()
        self.override = 0

    # rest of class same as before

override_tests = OverrideTests()

然后:

>>> do_it_lots()
1.1.1.2.2.1.1.1.1.1.1.3.3.2.2.2.2.2.2.4.4.3.1.3.3.3.3.4.3.2.4.4.2.4.3.4.4.4.3.4.

但是,我不会在这种情况下不失败,特别是如果您的实际应用程序比您在此处显示的示例更复杂时。最终,你真的应该重新考虑你的设计。在您的问题中,您关注的是如何“使上下文管理器线程安全”。但真正的问题不仅在于您的上下文管理器,还在于您的函数(在您的示例中为stat)。 stat 依赖于全局状态(全局 override_tests),这在线程环境中本质上是脆弱的。

【讨论】:

  • OK - 到目前为止,您的解决方案似乎有效。我已将更改提交到我们的 QA 服务器,如果我们在接下来的 24 小时内没有看到重现,我将批准此答案。
  • 总会有一些时候需要依赖一些全局状态,而完全通过函数参数化传递这种状态是不切实际的。我在现实世界中的示例实际上与此示例一样简单,并且看起来运行良好。
  • 这已经运行了一段时间,似乎已经完全解决了这个问题。
猜你喜欢
  • 1970-01-01
  • 2011-12-14
  • 2013-02-07
  • 2018-01-01
  • 2015-03-25
  • 2012-05-13
  • 2012-04-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多