Jupyter(Python)中无法使用Cache原理分析

作者: 魏守峰 | 来源:发表于2017-09-25 09:25 被阅读31次

    前言

    最近需要在Jupyter中写一个类库,其中有一个文件实现从数据库中读取空间数据并加载为Feature对象,Feature对象是cartopy封装的geometry列表,能够方便的用于作图等。因为有很多数据是经常用到的,所以就写了很多常量将数据事先读好供用户直接调用,这样造成的一个问题是每次加载该页面的时候很慢,于是我就考虑可以写个Cache来缓存这些数据,这在其他情况下是再正常不过的需求,然而我却在这里折腾半天,踏了坑,坑里还有水,再也没有出来。。。在这里我简单分析一下失败的原因,如果有人有能解决的方案或者我有什么说的不对的地方,欢迎批评指导!

    折腾过程

    首先我考虑这个应该是写个Cache类,其中加入一个字典,于是找到了这样一块代码,初步看了一下代码没有问题,于是Copy上:

    #coding=utf-8
    from time import time
    class Cache:
        '''简单的缓存系统'''
        def __init__(self):
            '''初始化'''
            self.mem = {}
            self.time = {}
    
        def set(self, key, data, age=-1):
            '''保存键为key的值,时间位age'''
            self.mem[key] = data
            if age == -1:
                self.time[key] = -1
            else:
                self.time[key] = time() + age
            return True
    
        def get(self,key):
            '''获取键key对应的值'''
            if key in self.mem.keys():
                if self.time[key] == -1 or self.time[key] > time():
                    return self.mem[key]
                else:
                    self.delete(key)
                    return None
            else:
                return None
    
        def delete(self,key):
            '''删除键为key的条目'''
            del self.mem[key]
            del self.time[key]
            return True
    
        def clear(self):
            '''清空所有缓存'''
            self.mem.clear()
            self.time.clear()
    

    很清晰的一段代码,并且加入了缓存时间,应当能满足我的要求的,在此页面定义了一个变量,创建一个FEATURE_CACHE对象如下:

    FEATURE_CACHE = Cache()
    

    这样我在需要缓存的页面只要先判断是否在缓存内,是则直接读取,否则使用原来的逻辑读取数据库并存入缓存即可,改造如下:

    if FEATURE_CACHE.get(ds_id) != None:
        return FEATURE_CACHE.get(ds_id)
    else:
        ...
        geo_feature = ...
        FEATURE_CACHE.set(ds_id, geo_feature)
        return geo_feature
    

    逻辑上清晰易懂,然后尝试调用。新建一个jupyter页面,多次调用,很好,只有第一次比较慢,再次调用就非常快,本以为这就解决了问题,我也是灵光一闪,既然我是全局缓存那就再开一个页面试试吧,于是又新建了一个jupyter页面,大跌眼镜的事情出现了,居然也是第一次调用非常慢,这是什么逻辑,为什么这里面没有缓存。然后经历了无数次加输出信息调试、重启kernel调试、staticmethod方法、单例等均达不到效果,单例的代码如下:

    class Cache:
        __instance = None  
        
        __lock = threading.Lock()   # used to synchronize code  
        
        mem = {}
        time = {}
     
        def __init__(self):  
            "disable the __init__ method"  
            
        '''简单的缓存系统'''
    
        def set(self, key, data, age=-1):
            '''保存键为key的值,时间位age'''
            self.mem[key] = data
            if age == -1:
                self.time[key] = -1
            else:
                self.time[key] = time() + age
            return True
    
        def get(self,key):
            '''获取键key对应的值'''
            if key in self.mem.keys():
                if self.time[key] == -1 or self.time[key] > time():
                    return self.mem[key]
                else:
                    self.delete(key)
                    return None
            else:
                return None
    
        def delete(self,key):
            '''删除键为key的条目'''
            del self.mem[key]
            del self.time[key]
            return True
    
        def clear(self):
            '''清空所有缓存'''
            self.mem.clear()
            self.time.clear()
            
        @staticmethod  
        def getInstance():  
            if not Cache.__instance:  
                Cache.__lock.acquire()  
                if not Cache.__instance:  
                    Cache.__instance = object.__new__(Cache)  
                    object.__init__(Cache.__instance)  
                Cache.__lock.release()  
            return Cache.__instance
    

    这样就是不再创建Cache的实例,而是直接调用Cache.getInstance()。可想而知这样也是不行的。于是折腾一番后我得出这么一个结果。

    结果与原理

    当我们在一个jupyter页面中调用某个python库的时候,只要在这个jupyter页面中不重新启动内核,则已经加载过的模块会自动缓存(是python的缓存,并非我写的缓存),重启内核相当于打开一个新的jupyter页面,并且在重新打开一个jupyter页面时,即使其他jupyter页面已经加载过了相应的调用,也不会缓存,会再次去执行程序,这样我写的Cache类就没有用了。所以结论就是在jupyter中我的Cahce缓存类加不加效果是一样的。那么原理是什么呢?

    其实很简单,只是我刚开始对python的运行机理和生命周期等不太熟悉,才走了这个弯路,折腾一番大概明白了。首先普通的python程序使用python xx.py启动的时候这样写Cahce肯定是可行的,能够实现全局缓存,因为这是在一个application内部,加载过的python文件会编译成pyc,再次加载的时候会直接调用此pyc而不会重新执行,并且整体是共享内存的。而在jupyter中每一个jupyter页面都相当于启动了一个application,所以他们相互之间是隔离的,即无法共享pyc文件,也无法共享内存,于是重新打开一个jupyter页面就是一个新的Cache,这样写不写Cache得到的结果是一致的。

    总结

    当然可以考虑采用文件缓存的方式,即首次读取的时候将数据库内容加载到本地文件,再次调用的时候读取文件,然而并没有尝试这样会快多少,并且本身访问量就不大,数据库是完全能抗住的,于是不知道这样的缓存有多少意义。当然也可以使用redis、memcache等缓存件,但是这样就整大发了,没必要使用jupyter了吧。以上是我对此问题的个人见解,欢迎大家提出宝贵意见,不甚感激!

    相关文章

      网友评论

        本文标题:Jupyter(Python)中无法使用Cache原理分析

        本文链接:https://www.haomeiwen.com/subject/kksrextx.html