csrgxtu / Cocoa

A flask like framework implemented from inside out
1 stars 0 forks source link

Flask Werkzeug request context stack #2

Open csrgxtu opened 7 years ago

csrgxtu commented 7 years ago

how Flask handle concurrency?

csrgxtu commented 7 years ago

在操作系统的概念中,有进程和线程,这是都是为了并发的执行任务而引入的。我们知道当你在Linux系统中fork出一个新的进程或者线程,那么这个子进程会继承使用父进程的全局变量,并且会有自己的私有变量。那么当有多个子进程的时候,读写全局变量就需要用读写锁,互斥锁,条件变量同步起来,否则最后全局变量就是脏数据了。而进程的私有变量因为只是在每个进程的内部全局可见,所以可以在进程内随便使用。

csrgxtu commented 7 years ago

在Python中,你可以通过如下的方式access全局变量:

import threading

global_num = 0

def thread_cal():
    global global_num
    for i in xrange(1000):
        global_num += 1

# Get 10 threads, run them and wait them all finished.
threads = []
for i in range(10):
    threads.append(threading.Thread(target=thread_cal))
    threads[i].start()

# wait until finished
for i in range(10):
    threads[i].join()

# Value of global variable can be confused.
print global_num

如上代码,最终global_num是多少,我们并不能准确的知道。

csrgxtu commented 7 years ago

当然我们也可以在thread内部生命变量,然后这个变量就是当前线程全局有效的:

def show(num):
    print threading.current_thread().getName(), num

def thread_cal():
    local_num = 0
    for _ in xrange(1000):
        local_num += 1
    show(local_num)

threads = []
for i in range(10):
    threads.append(threading.Thread(target=thread_cal))
    threads[i].start()
csrgxtu commented 7 years ago

使用线程局部变量确实可以解决一些问题,但是在一个线程内部,往往会做很多逻辑,调用很多相关的函数,如果这些函数需要访问线程的局部变量,就需要将这些变量作为函数参数传入。

或者我们也可以将这些局部变量放入全局变量,以线程id作为key来标识,这样每个线程就可以根据自己的线程id获取到属于他的变量。

global_data = {}
def show():
    cur_thread = threading.current_thread()
    print cur_thread.getName(), global_data[cur_thread]

def thread_cal():
    cur_thread = threading.current_thread()
    global_data[cur_thread] = 0
    for _ in xrange(1000):
        global_data[cur_thread] += 1
    show()  # Need no local variable.  Looks good.
...

这就是Python的lib ThreadLocal所实现的。

csrgxtu commented 7 years ago

所以,至此,我们理解了ThreadLocal可以用于多线程并发。Werkzeug的Local也是如此。因为ThreadLocal 只是在多线程下的,在coroutine并发中就不适用了。其次,WSGI并没有保证处理每个请求都需要一个新的线程,而是可能会使用线程池机制,所以上一次线程执行后在ThreadLocal中会存有脏的数据。所以Werkzeug实现了自己的Local.

csrgxtu commented 7 years ago

LocalStack是针对Local对象实现的一种栈结构,我们可以对这个栈pop, push, top等。因为在Werkzeug中,一个请求可能重定向,跳转,所以这里使用了Stack的结构来存储一个请求相关的数据。

csrgxtu commented 7 years ago
# flask.py
_request_ctx_stack = LocalStack()
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
request = LocalProxy(lambda: _request_ctx_stack.top.request)
session = LocalProxy(lambda: _request_ctx_stack.top.session)
g = LocalProxy(lambda: _request_ctx_stack.top.g)

在flask.py文件中声明了全局变量_request_ctx_stack,这个全局变量是一个Werkzeug Local类型了,所以可以在多个线程中使用而不会造成脏数据。如此,每个线程都可以全局访问属于自己线程的数据,而不会意外的修改了属于其它线程的数据,同时也不需要将request, session对象作为函数参数不停的在函数间传递了。

csrgxtu commented 7 years ago

hummm, 以上就是Flask中currency的实现原理了,当然是借助多线程的概念。