zoukankan      html  css  js  c++  java
  • Python 高级

     GIL面试题如下

    描述Python GIL的概念, 以及它对python多线程的影响?编写一个多线程抓取网页的程序,并阐明多线程抓取程序是否可比单线程性能有提升,并解释原因。

    Guido的声明:http://www.artima.com/forums/flat.jsp?forum=106&thread=214235

    he language doesn't require the GIL -- it's only the CPython virtual machine that has historically been unable to shed it.

    参考答案:

    1. Python语言和GIL没有半毛钱关系。仅仅是由于历史原因在Cpython虚拟机(解释器),难以移除GIL。
    2. GIL:全局解释器锁。每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行代码。
    3. 线程释放GIL锁的情况: 在IO操作等可能会引起阻塞的system call之前,可以暂时释放GIL,但在执行完毕后,必须重新获取GIL Python 3.x使用计时器(执行时间达到阈值后,当前线程释放GIL)或Python 2.x,tickets计数达到100
    4. Python使用多进程是可以利用多核的CPU资源的。
    5. 多线程爬取比单线程性能有提升,因为遇到IO阻塞会自动释放GIL锁

    因为GIL的原因,导致python的多线程并不是真正的多线程,一次只能执行一个线程。但是开启多线程也比单线程要好。因此如果运行的是计算密集型,也就是中间没有延时的,就使用进程。如果是io密集型,也就是读写的话,就可以考虑线程和协程。解决python的GIL问题有两个,一个换解释器,这个问题只有在CPython解释器中存在。第二个方法就是使用C语言编写子线程,然后导入进Python程序中。

    深拷贝、浅拷贝

    1. 浅拷贝

    • 浅拷贝是对于一个对象的顶层拷贝

    通俗的理解是:拷贝了引用,并没有拷贝内容

    2. 深拷贝

    • 深拷贝是对于一个对象所有层次的拷贝(递归)

    进一步理解深拷贝

    如果copy.copy拷贝的是元组,那么它不会进行浅拷贝,而仅仅是指向。

    原因:元组是不可变类型,那么意味着数据一定不能进行修改,因此用copy.copy的时候它会进行自动判断,如果是元组它就是指向了它。

    如果用copy.copy、copy.deepcopy对一个全部都是不可变类型的数据进行拷贝,那么他们结果相同,都是引用指向.。

    如果拷贝的是一个拥有可变类型的数据,即使元组是最顶层,那么deepcopy依然是深拷贝,而copy.copy还是指向。

     拷贝的其他方式

    • 分片表达式可以赋值一个序列

    • 字典的copy方法可以拷贝一个字典

    注意点

    浅拷贝对不可变类型和可变类型的copy不同

    1. copy.copy对于可变类型,会进行浅拷贝
    2. copy.copy对于不可变类型,不会拷贝,仅仅是指向

    import导入模块

    import aa 的过程是首先导入 aa这个模块,然后在当前程序中创建aa这个变量指向这个模块。

    1. import 搜索路径

    路径搜索

    • 从上面列出的目录里依次查找要导入的模块文件
    • '' 表示当前路径
    • 列表中的路径的先后顺序代表了python解释器在搜索模块时的先后顺序

    程序执行时添加新的模块路径

    sys.path.append('/home/itcast/xxx')
    sys.path.insert(0, '/home/itcast/xxx')  # 可以确保先搜索这个路径
    
    In [37]: sys.path.insert(0,"/home/python/xxxx")
    In [38]: sys.path
    Out[38]: 
    ['/home/python/xxxx',
     '',
     '/usr/bin',
     '/usr/lib/python35.zip',
     '/usr/lib/python3.5',
     '/usr/lib/python3.5/plat-x86_64-linux-gnu',
     '/usr/lib/python3.5/lib-dynload',
     '/usr/local/lib/python3.5/dist-packages',
     '/usr/lib/python3/dist-packages',
     '/usr/lib/python3/dist-packages/IPython/extensions',
     '/home/python/.ipython']
    

    2. 重新导入模块

    模块被导入后,import module不能重新导入模块,重新导入需用reload

    3. 多模块开发时的注意点

    # recv_msg.py模块
    from common import RECV_DATA_LIST
    # from common import HANDLE_FLAG
    import common
    
    
    def recv_msg():
        """模拟接收到数据,然后添加到common模块中的列表中"""
        print("--->recv_msg")
        for i in range(5):
            RECV_DATA_LIST.append(i)
    
    
    def test_recv_data():
        """测试接收到的数据"""
        print("--->test_recv_data")
        print(RECV_DATA_LIST)
    
    
    def recv_msg_next():
        """已经处理完成后,再接收另外的其他数据"""
        print("--->recv_msg_next")
        # if HANDLE_FLAG:
        if common.HANDLE_FLAG:
            print("------发现之前的数据已经处理完成,这里进行接收其他的数据(模拟过程...)----")
        else:
            print("------发现之前的数据未处理完,等待中....------")
    # handle_msg.py模块
    from common import RECV_DATA_LIST
    # from common import HANDLE_FLAG
    import common
    
    def handle_data():
        """模拟处理recv_msg模块接收的数据"""
        print("--->handle_data")
        for i in RECV_DATA_LIST:
            print(i)
    
        # 既然处理完成了,那么将变量HANDLE_FLAG设置为True,意味着处理完成
        # global HANDLE_FLAG
        # HANDLE_FLAG = True
        common.HANDLE_FLAG = True
    
    def test_handle_data():
        """测试处理是否完成,变量是否设置为True"""
        print("--->test_handle_data")
        # if HANDLE_FLAG:
        if common.HANDLE_FLAG:
            print("=====已经处理完成====")
        else:
            print("=====未处理完成====")
    # main.py模块
    from recv_msg import *
    from handle_msg import *
    
    
    def main():
        # 1. 接收数据
        recv_msg()
        # 2. 测试是否接收完毕
        test_recv_data()
        # 3. 判断如果处理完成,则接收其它数据
        recv_msg_next()
        # 4. 处理数据
        handle_data()
        # 5. 测试是否处理完毕
        test_handle_data()
        # 6. 判断如果处理完成,则接收其它数据
        recv_msg_next()
    
    
    if __name__ == "__main__":
        main()

    以上面的图,如果其他两个模块使用“import  common”,那就是都指向同一个模块common;如果使用“from common import HANDLE_FLAG”,那么一开始就是HANDLE_FLAG直接指向common里面的HANDLE_FLAG而已,没有指向common,在后面的“HANDLE_FLAG = True”属于赋值语句,不会改变common里面的HANDLE_FLAG,而是重新创建一个值True,并指向它。(当然如果指向的值是列表list,那么采用".append"时,就可以修改common模块里面的值,但如果还是采用赋值语句就不行,相当于重新开辟一块内存空间了。)

    为啥要封装

    好处

    1. 在使用面向过程编程时,当需要对数据处理时,需要考虑用哪个模板中哪个函数来进行操作,但是当用面向对象编程时,因为已经将数据存储到了这个独立的空间中,这个独立的空间(即对象)中通过一个特殊的变量(__class__)能够获取到类(模板),而且这个类中的方法是有一定数量的,与此类无关的将不会出现在本类中,因此需要对数据处理时,可以很快速的定位到需要的方法是谁 这样更方便
    2. 全局变量是只能有1份的,多很多个函数需要多个备份时,往往需要利用其它的变量来进行储存;而通过封装 会将用来存储数据的这个变量 变为了对象中的一个“全局”变量,只要对象不一样那么这个变量就可以再有1份,所以这样更方便
    3. 代码划分更清晰

      

  • 相关阅读:
    npm, node, pm2 使用笔记
    没加证书的域名通过https访问,错误的访问到有证书的域名项目--已解决
    mysql数据库大表加索引
    上传大文件失败
    ifame 与父页面进行数据交互(跨域)
    windows平台编译PHP及扩展 和 踩过的坑
    vim 使用笔记
    git 在pull/push指定密钥文件
    记一次使用Xshell登陆提示所选用户密钥未在远程主机上注册
    学习网站与参考文档
  • 原文地址:https://www.cnblogs.com/linyuhong/p/10046441.html
Copyright © 2011-2022 走看看