5.flask中多线程和线程隔离技术

一. GIL

python的多线程有两种锁:

1. 细粒度的锁    程序员在程序中主动加的  lock
2. 粗粒度的锁GIL  保证多cpu的情况下,同一时刻也只执行一个线程     一定程度上保证线程安全

因为gil的存在,cpython不能真正利用多线程。如果非要实现真正的多线程, 可以使用多进程模式。 但是多进程又会带来新的问题,比如 进程间切换的开销远大于多线程, 多进程间的共享资源也更麻烦。


二. 对于IO密集型程序,多线程是有意义的

很多人都在讨论python多线程到底是不是鸡肋?答案是 得分情况看。

1. python多线程是鸡肋:  计算圆周率、视频解码 等cpu密集型程序
2. python多线程不是鸡肋: 查询数据库、请求网络资源、读写文件 等IO密集型程序

我们现在多数的代码都是io密集型的


三. flask的线程隔离

1. 需求

现有全局变量request=None
在线程1中 request=Request()
在线程2中 request=Request()
在线程3中 request=Request()
如果我想取线程1的request, 现在的情况下肯定无法做到。

解决办法:

全局变量改为dict, 每一个线程都有对应的自己的key, 并将request作为value存放。

falsk的线程隔离技术也是基于类似的原理实现的。

2. flask的线程隔离原理: Local对象

查看源码: siet-package/werkzeug/local.py 中的Local类:

class Local(object):
    __slots__ = ('__storage__', '__ident_func__')

    def __init__(self):
        object.__setattr__(self, '__storage__', {})   # __storge__ 实现空的字典
        object.__setattr__(self, '__ident_func__', get_ident) # get_ident是获取当前线程id号的 函数

    def __iter__(self):
        return iter(self.__storage__.items())

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

    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None)

    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        ident = self.__ident_func__()     # 取当前线程的 线程id号, 
        storage = self.__storage__        # 类本身的字典
        try:
            storage[ident][name] = value   # 把当前线程id号存起来
        except KeyError:
            storage[ident] = {name: value}

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)
线程隔离的对象: 不同线程中操作该对象, 是互不影响的。
Local类就是线程隔离的对象

应用Local对象举例:

from werkzeug.local import Local
import threading, time

my_obj = Local()
my_obj.b = 1


def worker():
    my_obj.b = 2
    print('in sub thread b is:' + str(my_obj.b))


subthread1 = threading.Thread(target=worker, name='subthread1')
subthread1.start()

time.sleep(1)

print('my_obj.b is : {}'.format(my_obj.b))

运行结果, 可以看到主线程和子线程 互不影响:

in sub thread b is:2
my_obj.b is : 1

3. flask的线程隔离栈: LocalStack

LocalStack与Local的关系:

Local:          使用字典的方式实现的线程隔离
LocalStack:     封装Local实现线程隔离的栈结构

LocalStack的使用

代码举例:

from werkzeug.local import Local, LocalStack
import threading, time

my_stack = LocalStack()
my_stack.push(1)
print('in main thread after push, value is:' + str(my_stack.top))


def worker():
    print('in new thread before push , value is:'+str(my_stack.top))   # 因为线程隔离,所以为None
    my_stack.push(2)
    print('in new thread after push , value is:'+str(my_stack.top))


new_t = threading.Thread(target=worker, name='localstack_thread')
new_t.start()

time.sleep(1)

print('finally, in main thread , value is : {}'.format(my_stack.top))

运行结果, 是线程隔离的

in main thread after push, value is:1
in new thread before push , value is:None   # 新线程取不到主线程的栈的值,是隔离的
in new thread after push , value is:2
finally, in main thread , value is : 1

4. flask中被线程隔离的对象

如果我们在处理request的过程中使用非线程隔离的对象,会怎么样? 在app/libs下增加none_local.py:

class NoneLocal:
    def __init__(self, v):
        self.v = v


n = NoneLocal(1)

在app/web/book.py添加代码:

@web.route('/test')
def test():
    from flask import request
    from app.libs.none_local import n
    print(n.v)
    n.v = 2
    print('-----------------------')
    print(getattr(request, 'v', None))
    setattr(request, 'v', 2)
    print('-----------------------')
    return ''

fisher.py中app.run(threaded=True) 开启多线程,并运行:
多次访问http://127.0.0.1:5000/test, 得到结果

1                            # 多线程模式第一次访问'test', n.v为初始值1
-----------------------
None
-----------------------
127.0.0.1 - - [18/Jun/2018 16:08:09] "GET /test HTTP/1.1" 200 -
2                           # 以后每次访问`test`的线程, 都会被影响, 变为2
-----------------------
None                        # flask自带的线程隔离的request 并没有受到影响
-----------------------
127.0.0.1 - - [18/Jun/2018 16:08:22] "GET /test HTTP/1.1" 200 -
2
-----------------------
None
-----------------------
127.0.0.1 - - [18/Jun/2018 16:08:33] "GET /test HTTP/1.1" 200 -
127.0.0.1 - - [18/Jun/2018 16:08:43] "GET /test HTTP/1.1" 200 -
2
-----------------------
None
-----------------------

flask中有哪些线程隔离的对象? 我们查看request的源码, 进入globals.py:

# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))

这里的变量都是线程隔离的, 除了current_app, current_app全局只有一个(即id号永远只有一个), 做线程隔离没有意义。 像request,每次请求都有一个, 它做线程隔离就是有意义的。


四. 梳理串接flask的一些名词

1.以线程ID号作为key的字典 ->Local -> LocalStack
2.AppContext、RequestContext -> LocalStack
3.Flask -> AppContext          Request ->RequestContext
4.current_app -> (LocalStack.top = AppContext top.app=Flask)
5.request -> (LocalStack.top = RequestContext top.request = Request)

5.flask中多线程和线程隔离技术_第1张图片

你可能感兴趣的:(flask)