1. 程式人生 > >Flask上下文管理源碼分析

Flask上下文管理源碼分析

lee sta targe start func pre local ror elf

引出的問題

Flask如何使用上下文臨時把某些對象變為全局可訪問

首先我們做如下的幾種情況的假設

情況一:單進程單線程

這種情況可以基於全局變量存儲臨時的對象

情況二:單進程多線程

這種情況會出現多個線程共享全局的變量,為了每個線程中的數據不被其他線程修改,可以借助hreading.local對象,為每個線程做唯一的表示用來做鍵,請求的對象作為值來實現

多線程共享數據的問題

import threading
class Foo(object):
    def __init__(self):
        self.name = 0

local_values = Foo()

def func(num):
    local_values.name 
= num import time time.sleep(1) print(local_values.name, threading.current_thread().name) for i in range(20): th = threading.Thread(target=func, args=(i,), name=線程%s % i) th.start()

我們可以看到最後把每個線程中對象中name值都變為了19,不能保證每個線程中對象中的值唯一

技術分享圖片

使用hreading.local對象可以對每個線程做唯一的表示可以解決上述的問題

import
threading local_values = threading.local() def func(num): local_values.name = num import time time.sleep(1) print(local_values.name, threading.current_thread().name) for i in range(20): th = threading.Thread(target=func, args=(i,), name=線程%s % i) th.start()

可以看到每個線程中的值唯一

技術分享圖片

- 情況三:單進程單線程(多個協程)Flask 的上下文管理就是基於這種情況做的

在這種情況下使用上面的方法可以保證線程中的數據唯一,但是使用其內部創建多個協程後,hreading.local只能對線程作唯一的標示,協程是在單線程下切換的,所以多個協程還會出現共享數據的問題

解決的思路:為每個程做唯一的標示,我們可以通過python自帶的greenlet模塊中的getcurrent來實現

只需對上面的代碼做簡單的修改即可

import threading
try:
    from greenlet import getcurrent as get_ident # 協程
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
        from _thread import get_ident # 線程


class Local(object):
    def __init__(self):
        self.storage = {}
        self.get_ident = get_ident

    def set(self,k,v):
        ident = self.get_ident()
        origin = self.storage.get(ident)
        if not origin:
            origin = {k:v}
        else:
            origin[k] = v
        self.storage[ident] = origin

    def get(self,k):
        ident = self.get_ident()
        origin = self.storage.get(ident)
        if not origin:
            return None
        return origin.get(k,None)

local_values = Local()


def task(num):
    local_values.set(name,num)
    import time
    time.sleep(1)
    print(local_values.get(name), threading.current_thread().name)


for i in range(20):
    th = threading.Thread(target=task, args=(i,),name=線程%s % i)
    th.start()

測試的結果如下

技術分享圖片

使用面向對象中方法對其進行簡單的優化

在初始化的時候設置屬性的時候,為了避免循環引用,我們可以這樣做 object.__setattr__(self, ‘storage‘, {})

class Foo(object):

    def __init__(self):
        object.__setattr__(self, storage, {})
        # self.storage = {}

    def __setattr__(self, key, value):
        self.storage = {k1:v1}
        print(key,value)

    def __getattr__(self, item):
        print(item)
        return df


obj = Foo()

# obj.x = 123
# 對象.xx

修改後的代碼如下所示

import threading
try:
    from greenlet import getcurrent as get_ident # 協程
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
        from _thread import get_ident # 線程


class Local(object):

    def __init__(self):
        object.__setattr__(self, __storage__, {})
        object.__setattr__(self, __ident_func__, get_ident)


    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__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}

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


local_values = Local()


def task(num):
    local_values.name = num
    import time
    time.sleep(1)
    print(local_values.name, threading.current_thread().name)


for i in range(20):
    th = threading.Thread(target=task, args=(i,),name=線程%s % i)
    th.start()

Flask上下文管理源碼分析