【问题标题】:What is so bad with threadlocalsthreadlocals 有什么不好
【发布时间】:2010-12-27 13:31:18
【问题描述】:

Django 世界中的每个人似乎都讨厌 threadlocals(http://code.djangoproject.com/ticket/4280, http://code.djangoproject.com/wiki/CookBookThreadlocalsAndUser)。我阅读了 Armin 关于此的文章 (http://lucumr.pocoo.org/2006/7/10/why-i-cant-stand-threadlocal-and-others),但其中大部分取决于 threadlocals 是不好的,因为它不优雅。

我有一个场景,theadlocals 会让事情变得更容易。 (我有一个应用程序,人们将拥有子域,因此所有模型都需要访问当前子域,并且从请求中传递它们是不值得的,如果 threadlocals 的唯一问题是它们不优雅,或者变得脆弱代码。)

还有很多 Java 框架似乎大量使用 threadlocals,那么它们的情况与 Python/Django 的情况有何不同?

【问题讨论】:

  • 尝试在没有threadlocals的情况下实现子域多租户,我完全可以同情。在经历了一些严重的挫折之后,threadlocals 真的最终成为了唯一的出路。我读了反对他们的论据,但他们不够有力。我认为拒绝使用 threadlocals 是 Sites 框架在某些情况下如此无用的主要原因之一。如果他们想出如何以一种可以适应您和我正在使用的多租户类型的方式合法地解决code.djangoproject.com/ticket/15089,将会很有趣。
  • Django Cookbook 链接已损坏。论文链接也坏了 (possible replacement)。

标签: python django thread-local


【解决方案1】:

我避免使用这种线程局部变量,因为它引入了隐式的非局部耦合。我经常以各种非 HTTP 导向的方式(本地管理命令、数据导入/导出等)使用模型。如果我在 models.py 中访问一些 threadlocals 数据,现在我必须找到一些方法来确保在我使用我的模型时始终填充它,这可能会变得非常丑陋。

在我看来,更明确的代码更简洁,更易于维护。如果模型方法需要一个子域才能运行,那么通过让该方法接受该子域作为参数,这一事实应该很明显。

如果我绝对找不到将请求数据存储在 threadlocals 中的方法,我至少会在一个单独的模块中实现包装器方法,该模块访问 threadlocals 并使用所需数据调用模型方法。这样,models.py 仍然是自包含的,并且可以在没有 threadlocals 耦合的情况下使用模型。

【讨论】:

  • Carl:我同意这会破坏局部性,但如果我必须在所有 100% 的.filter 调用中传递“subdoain”数据,这不会以更糟糕的方式破坏 DRY。这就是为什么我认为在这种情况下这是一个可以接受的权衡。
  • 在这种情况下权衡可能是可以接受的;只有你可以打那个电话。你问“threadlocals 有什么不好”,所以我回答了 :-) 我还描述了如果我确实觉得权衡是值得的,我可以如何减轻损失。
  • Django 中的方法是创建一个请求中间件,将线程本地数据附加到请求对象。这遵循 Django 的会话框架设计,通过该框架,中间件类确保存在足够的数据,以便在您想要访问某些按请求的数据时,您的应用程序不会崩溃。这也意味着这种敏感的耦合只能通过视图访问,我认为这不会限制任何人之前设计他们的应用程序。
  • Filip:在中间件的请求中附加东西是可以的。问题是 Django 的模型层不知道请求。 django中treadlocals最常见的原因是在模型层获取请求。在某些应用程序中,您需要始终将请求传递给特殊的模型层方法。
【解决方案2】:

我不认为 threadlocals 有什么问题 - 是的,它是一个全局变量,但除此之外它是一个普通的工具。我们仅将它用于此目的(将子域模型存储在对来自中间件的当前请求全局的上下文中)并且它工作得很好。

所以我说,使用正确的工具来完成这项工作,在这种情况下,threadlocals 使您的应用程序比在所有模型方法中传递子域模型更加优雅(更不用说它甚至不总是可能的事实 - 当您正在覆盖 django 管理器方法以限制子域的查询,例如,您无法将任何额外的东西传递给 get_query_set - 所以 threadlocals 是自然且唯一的答案)。

【讨论】:

  • 来自实现相同事物的人(我们存储请求,而不是模型,但存储到相同的目的)我完全同意。人们会反对 threadlocals,直到他们真正需要使用 django 实现动态多租户,此时,他们会意识到这绝对是那些“实用性胜过纯粹性”的时刻之一。
  • 通过隐藏依赖项让应用程序更优雅?我第一次读到。了解 django 可能不会让您以任何其他方式进行操作。这是一个django问题。但优雅,让我们同意不同意这一点。
【解决方案3】:

还有很多 Java 框架似乎大量使用 threadlocals,那么它们的情况与 Python/Django 的情况有何不同?

CPython 的解释器有一个全局解释器锁 (GIL),这意味着解释器在任何给定时间只能执行一个 Python 线程。我不清楚 Python 解释器实现是否必然需要使用多个操作系统线程来实现这一点,尽管实际上 CPython 确实如此。

Java 的主要锁定机制是通过对象的监视器锁定。这是一种分散的方法,允许在多核和/或多处理器 CPU 上使用多个并发线程,但也会产生更复杂的同步问题供程序员处理。

这些同步问题只出现在“共享可变状态”中。如果状态不是可变的,或者在 ThreadLocal 的情况下它不是共享的,那么对于 Java 程序员来说,这是一个不太复杂的问题。

CPython 程序员仍然需要处理可能出现的竞态条件,但一些更深奥的 Java 问题(例如发布)可能已经由解释器解决了。

CPython 程序员还可以选择在不适用 GIL 限制的 Python 可调用 C 或 C++ 代码中编写性能关键代码。从技术上讲,Java 程序员通过 JNI 也有类似的选择,但无论正确还是错误地认为,Java 中的接受程度都低于 Python。

【讨论】:

    【解决方案4】:

    当您使用多个线程并希望将某些对象本地化到特定线程时,您想使用 threadlocals,例如。每个线程都有一个数据库连接。 在您的情况下,您希望更多地将其用作全局上下文(如果我理解正确的话),这可能是一个坏主意。它会让你的应用程序变得更慢、更耦合并且更难测试。

    为什么从请求中传递它不值得?为什么不将其存储在会话或用户配置文件中?

    与 Java 的不同之处在于,Web 开发比 Python/PERL/PHP/Ruby 世界中的状态要多得多,因此人们习惯于各种上下文和类似的东西。我不认为这是一个优势,但一开始看起来确实如此。

    【讨论】:

    • Why don't you store it in session or user profile? 因为我需要从模型中访问它。
    • In your case, you want to use it more as a global context 不是真的,我想在中间件中设置它,并让它在 modles.py 中可访问,没有视图,每次都明确地将其发送到 models.py。
    【解决方案5】:

    我发现使用 ThreadLocal 是在 HTTP 请求/响应环境(即任何 web 应用程序)中实现依赖注入的绝佳方式。您只需设置一个 servlet 过滤器,以便在接收请求时将所需的对象“注入”到线程中,并在返回响应时将其“取消注入”。

    这是一个聪明人的 DI,没有所有的 XML 丑陋,没有 Spring Jars 的 MB(更不用说它的学习曲线),也没有所有神秘的重复 @annotation 废话,因为它不会单独注入许多对象实例依赖关系,它可能快得多并且使用更少的内存。

    效果非常好,我们开源了 exPOJO 过滤器,它可以使用 ThreadLocal 注入 Hibernate 会话或 JDO PersistenceManager:

    http://www.expojo.com

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-19
      • 2011-02-06
      • 1970-01-01
      • 2011-06-19
      • 1970-01-01
      • 2021-09-20
      • 2011-01-27
      • 1970-01-01
      相关资源
      最近更新 更多