ThreadLocal是线程级别的local,如果在greenlet或者协程这种微线程环境下,或者在多个请求共用一个线程的情况下,线程级别是不够的。ThreadLocal是thread-safe和thread-specific的, 而有些情况需要greenlet-safe和greenlet-specific或者request-safe和request-specific。

werkzeug 0.1版中Local的实现是这样的:

try:
    from py.magic import greenlet
    get_current_greenlet = greenlet.getcurrent
    del greenlet
except (RuntimeError, ImportError):
    get_current_greenlet = lambda: None
try:
    from thread import get_ident as get_current_thread
    from threading import Lock
except ImportError:
    from dummy_thread import get_ident as get_current_thread
    from dummy_threading import Lock
from werkzeug.utils import ClosingIterator


def get_ident():
    """
    Return a unique number for the current greenlet in the current thread.
    """
    return hash((get_current_thread(), get_current_greenlet()))


class Local(object):

    def __init__(self):
        self.__dict__.update(
            __storage={},
            __lock=Lock()
        )

    def __iter__(self):
        return self.__dict__['__storage'].iteritems()

    def __call__(self, proxy):
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)

    def __getattr__(self, name):
        self.__dict__['__lock'].acquire()
        try:
            ident = get_ident()
            if ident not in self.__dict__['__storage']:
                raise AttributeError(name)
            try:
                return self.__dict__['__storage'][ident][name]
            except KeyError:
                raise AttributeError(name)
        finally:
            self.__dict__['__lock'].release()

    def __setattr__(self, name, value):
        self.__dict__['__lock'].acquire()
        try:
            ident = get_ident()
            storage = self.__dict__['__storage']
            if ident in storage:
                storage[ident][name] = value
            else:
                storage[ident] = {name: value}
        finally:
            self.__dict__['__lock'].release()

    def __delattr__(self, name):
        self.__dict__['__lock'].acquire()
        try:
            ident = get_ident()
            if ident not in self.__dict__['__storage']:
                raise AttributeError(name)
            try:
                del self.__dict__['__storage'][ident][name]
            except KeyError:
                raise AttributeError(name)
        finally:
            self.__dict__['__lock'].release()

可以看到,如果当前环境支持greenlet, 则get_ident是greenlet级别的ident(greentlet中有类似thread.current_thread的greenlet.getcurrent), 否则是thread级别。

测试过程:将上面的代码开头的py.magic改为greenlet, __setattr__中加入print, 保存为local.py。测试代码如下:

import local
from greenlet import greenlet

lc = local.Local()

def test1():
	lc.test = 'test1'
	print("lc.test in test1: {}".format(lc.test))
	gr2.switch()
	print("lc.test in test1: {}".format(lc.test))
	print("test1 done.")

def test2():
	lc.test = 'test2'
	print("lc.test in test2: {}".format(lc.test))
	gr1.switch()
	print("test2 done.")

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

运行结果:

(werkzeug) localhost:iwerkzeug didi$ python test_local.py 
('ident: ', -3206197664300787518)
('storage: ', {-3206197664300787518: {'test': 'test1'}})
lc.test in test1: test1
('ident: ', -3206197296357035168)
('storage: ', {-3206197296357035168: {'test': 'test2'}, -3206197664300787518: {'test': 'test1'}})
lc.test in test2: test2
lc.test in test1: test1
test1 done.

可以看到,gr1和gr2中使用同样的参数lc.test,但却是greenlet specific的, 它得到的是自己的那份。原理其实也简单:将ident作为key,value中(也是一个dict)存放这个环境的一些变量。