李迎辉 2009年08月06日 星期四 14:11 | 1231次浏览 | 2条评论
今天在uliweb的邮件列表,shuhanwu提了一个问题:在多线程下,request对象不是线程安全的。并且给出了试例及可能出错代码的位置。于是我研究了一下,发现的确有这个问题。
问题原因
这个问题的产生与uliweb对view函数的设计造成的。uliweb在处理view函数时,使用了一种hack的机制。每个view方法都有一个func_globals的属性,它用来记录可以在函数中直接访问的全局对象的信息。因此uliweb在调用每个view方法之前,会将一些变量,如:request, response等信息添加到这个属性中,这样你就可以在view中不需要定义这些变量而直接使用了。但是这个func_globals属性对于每个函数来说是一份实例,因此对于不同的view函数,func_globals仍然是同一个对象。所以尽管我希望每个view函数的func_globals在不同线程下是不同的,但实际上是做不到的。于是造成后执行的view方法会将其它函数中的request,response等对象覆盖。
解决方案
知道问题产生的原因。我使用了以下的方案。
1. 使用threading.local来保存线程安全的request, response对象。在werkzeug中已经提供了Local类。而且在SimpleFrame.py中已经有一个全局的local对象可以直接使用。因此在__call__()中会将创建的request, response对象首先保存到local中去。__call__()是处理每个view请求的一个调度程序。
2. 在__call__()中最终会调用一个_call_function()的函数,它将完成向func_globals中添加新的属性的功能。因为我仍然希望用户不需要定义request, response的方式来处理view方法,因此向func_globals注入对象的方式仍然会使用。那么就要在request, response本身进行改造。这里我创建了两个新的RequestProxy和ResponseProxy的类,所以对request和response的属性操作,如request.GET这种操作,都将转换为对local.request或local.response的操作,包括给属性赋值。示例代码如下:
class RequestProxy(object):
def __init__(self, req):
local.request = req
def instance(self):
return local.request
def __getattr__(self, name):
return getattr(local.request, name)
def __setattr__(self, name, value):
setattr(local.request, name, value)
Zeuux © 2024
京ICP备05028076号
回复 李迎辉 2009年08月06日 星期四 22:39