python的多线程有两种锁:
1. 细粒度的锁 程序员在程序中主动加的 lock
2. 粗粒度的锁GIL 保证多cpu的情况下,同一时刻也只执行一个线程 一定程度上保证线程安全
因为gil的存在,cpython不能真正利用多线程。如果非要实现真正的多线程, 可以使用多进程模式。 但是多进程又会带来新的问题,比如 进程间切换的开销远大于多线程, 多进程间的共享资源也更麻烦。
很多人都在讨论python多线程到底是不是鸡肋?答案是 得分情况看。
1. python多线程是鸡肋: 计算圆周率、视频解码 等cpu密集型程序
2. python多线程不是鸡肋: 查询数据库、请求网络资源、读写文件 等IO密集型程序
我们现在多数的代码都是io密集型的
现有全局变量request=None
在线程1中 request=Request()
在线程2中 request=Request()
在线程3中 request=Request()
如果我想取线程1的request, 现在的情况下肯定无法做到。
全局变量改为dict, 每一个线程都有对应的自己的key, 并将request作为value存放。
falsk的线程隔离技术也是基于类似的原理实现的。
查看源码: 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
Local: 使用字典的方式实现的线程隔离
LocalStack: 封装Local实现线程隔离的栈结构
代码举例:
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
如果我们在处理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,每次请求都有一个, 它做线程隔离就是有意义的。
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)