Python的“全局”变量是线程局部的吗?

9 投票
2 回答
2437 浏览
提问于 2025-04-17 18:49

我想在内存中使用一个线程本地的缓存,来存储一个数据库中的值,这个值在请求和响应的过程中不会改变,但可能会被调用几百次(甚至几千次)。我了解到,使用一个“全局”的模块变量是一种实现这种缓存的方法。

例如:

#somefile.py

foo = None

def get_foo(request):
  global foo
  if not foo:
    foo = get_foo_from_db(request.blah)
  return foo

我在想,使用这种“全局”变量在Python中是否是线程安全的?这样我就可以放心地认为在Django中,get_foo_from_db()这个函数在每个请求和响应的过程中只会被调用一次(无论是用runserver还是gunicorn+gevent)。我的理解对吗?因为这个函数被调用的次数很多,即使使用memcached来存储这个值也会成为瓶颈(我正在进行性能分析)。

2 个回答

7

不,访问全局变量不是线程安全的。线程之间并不会各自拥有全局变量的副本,全局变量是线程共享的。

这段代码:

if not foo:
    foo = get_foo_from_db(request.blah)

会被编译成几条 Python 字节码语句:

  2           0 LOAD_FAST                1 (foo)
              3 POP_JUMP_IF_TRUE        24

  3           6 LOAD_GLOBAL              0 (get_foo_from_db)
              9 LOAD_FAST                0 (request)
             12 LOAD_ATTR                1 (blah)
             15 CALL_FUNCTION            1
             18 STORE_FAST               1 (foo)
             21 JUMP_FORWARD             0 (to 24)

在每执行完一条字节码后,线程可能会切换,所以在你检查完 foo 之后,另一个线程可能会对它进行修改。

4

不,你的理解有两个地方是错的。

首先,提到“线程”这个词有点模糊。根据服务器的配置,Django可以通过线程、进程,或者两者结合的方式来运行(想了解更多可以查看mod_wsgi的文档)。如果每个进程只有一个线程,那么可以保证每个进程中只有一个模块实例可用。但这完全取决于具体的配置。

即便如此,也不能说每次请求/响应的循环中“恰好会有一次”对那个函数的调用。这是因为一个进程的生命周期和这个循环是完全无关的。一个进程会处理多个请求,所以那个变量会在所有这些请求中保持不变。

撰写回答