django @admin.register 非线程安全陷阱

一直以为注册的 ModelAdmin 是线程安全的,直到最近看到有人提到 ModelAdmin 不是线程安全的。

然后看了一下 Django admin.register 的源码,才发现确实不是线程安全的。 核心代码 如下:

# Instantiate the admin class to save in the registry
self._registry[model] = admin_class(model, self)

当配置如下代码后:

@admin.register(Foo)
class FooAdmin(admin.ModelAdmin):
    pass

实际上注册的是一个 FooAdmin 实例,也就是说 FooAdmin 这个类在启动的时候就实例化了,所有的请求访问的都是同一个实例。 所以类似下面的代码就会有非线程安全的问题,因为 FooAdmin 实例(self)会共享给所有子线程/所有请求:

@admin.register(Foo)
class FooAdmin(admin.ModelAdmin):

    def foobar(self):
        print(self.param)

    def changelist_view(self, request, extra_context=None):
        self.param = request.GET['param']
        return super(FooAdmin,self).changelist_view(request, extra_context=extra_context)

对于使用多线程的服务,可以使用 Thread-Locals (内置的 threading.local 或 werkzeug.local) 解决这个问题:

from threading import local
g = local()

@admin.register(Foo)
class FooAdmin(admin.ModelAdmin):

    def foobar(self):
        print(g.param)

    def changelist_view(self, request, extra_context=None):
        g.param = request.GET['param']
        return super(FooAdmin,self).changelist_view(request, extra_context=extra_context)

对于使用协程(比如,使用 gevent)的服务,可以用 werkzeug.local.Local(内置的 threading.local 不行):

from werkzeug.local import Local
g = Local()

# ...

参考资料


Comments