python后端学习(九)GIL、深/浅拷贝、私有化、import、封装继承多态

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.

单线程死循环
#主线程死循环,占满cpu
while True:
    pass

htop 查看CPU状况,单线程占满一个cpu核
在这里插入图片描述

多线程死循环
import threading

#子线程死循环
def test():
    while True:
        pass


t1 = threading.Thread(target=test)
t1.start()

#主线程死循环
while True:
    pass

在这里插入图片描述

因为存在GIL的关系,python多线程是假的多线程,多个线程同一时刻只有一个在进行(全局解释器锁)。GIL是cpython解释器造成的,Cpython解释器是由C语言编写的解释器,官方推荐
在这里插入图片描述

多进程死循环
import multiprocessing

def deadLoop():
    while True:
        pass

#子进程死循环
p1 = multiprocessing.Process(target=deadLoop)
p1.start()

#主进程死循环
deadLoop()

真正使用多核 进行并行的是多进程
在这里插入图片描述

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

什么时候适合使用?

  • 计算密集型:进程
  • IO密集型:线程、协程
如何避免GIL锁?

1.使用其他语言的解释器 如jpython
2.使用其他语言实现 例如C、C++、java、javascritp
下面使用python运行C语言举例实现真正多线程

C语言hello word
C编译型语言 python解释型语言
在这里插入图片描述

gcc test.c C语言需要先编译成可执行的二进制文件 python不需要
./a.out  执行

在这里插入图片描述

from ctypes import *
from threading import Thread

#加载动态库
lib = cdll.LoadLibrary("./libdead_loop.so")

#创建一个子线程,让其执行c语言编写的函数,此函数是一个死循环
t = Thread(target=lib.DeadLoop)
t.start()

#主线程
while True:
    pass

在这里插入图片描述

深、浅拷贝

浅拷贝是对于一个对象的顶层拷贝
通俗的理解是:拷贝了引用,并没有拷贝内容
在这里插入图片描述
在这里插入图片描述
深拷贝是对于一个对象所有层次的拷贝(递归)
在这里插入图片描述
进一步理解深拷贝
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

拷贝的其他方式

分片表达式可以赋值一个序列
在这里插入图片描述
字典的copy方法可以拷贝一个字典
在这里插入图片描述
函数中调用的是指向,也可以使用深拷贝对参数的备份进行操作
在这里插入图片描述

私有化

xx: 公有变量
_x: 单前置下划线,私有化属性或方法,from somemodule import *禁止导入,类对象和子类可以访问
_xx:双前置下划线,避免与子类中的属性命名冲突,无法在外部直接访问(名字重整所以访问不到)
xx:双前后下划线,用户名字空间的魔法对象或属性。例如:init , __ 不要自己发明这样的名字
xx
:单后置下划线,用于避免与Python关键词的冲突
通过name mangling(名字重整(目的就是以防子类意外重写基类的方法或者属性)如:_Class__object)机制就可以访问private了。

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']
重新导入模块

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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

多模块开发时的注意点

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()

common.py

RECV_DATA_LIST = list()  # 用来存储数据
HANDLE_FLAG = False  # 用来标记是否处理完成

在这里插入图片描述
使用from common import HANDLE_FLAG的弊端 修改 HANDLE_FLAG并不是修改公共模块中的变量,而是修改HANDLE_FLAG的变量。
在这里插入图片描述

封装继承多态

为啥要封装

在这里插入图片描述
在这里插入图片描述
好处

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

def 函数1():
    pass


def 函数2():
    pass


def 函数3():
    pass


def 函数4():
    pass


def 函数5():
    pass

面向对象

class(object):
    属性1
    属性2

    def 方法1(self):
        pass

    def 方法2(self):
        pass

class2(object):
    属性3
    def 方法3(self):
        pass

    def 方法4(self):
        pass

    def 方法5(self):
        pass
为啥要继承

在这里插入图片描述
说明

  1. 能够提升代码的重用率,即开发一个类,可以在多个子功能中直接使用
  2. 继承能够有效的进行代码的管理,当某个类有问题只要修改这个类就行,而其继承这个类的子类往往不需要就修改
怎样理解多态
class MiniOS(object):
    """MiniOS 操作系统类 """
    def __init__(self, name):
        self.name = name
        self.apps = []  # 安装的应用程序名称列表

    def __str__(self):
        return "%s 安装的软件列表为 %s" % (self.name, str(self.apps))

    def install_app(self, app):
        # 判断是否已经安装了软件
        if app.name in self.apps:
            print("已经安装了 %s,无需再次安装" % app.name)
        else:
            app.install()
            self.apps.append(app.name)


class App(object):
    def __init__(self, name, version, desc):
        self.name = name
        self.version = version
        self.desc = desc

    def __str__(self):
        return "%s 的当前版本是 %s - %s" % (self.name, self.version, self.desc)

    def install(self):
        print("将 %s [%s] 的执行程序复制到程序目录..." % (self.name, self.version))


class PyCharm(App):
    pass


class Chrome(App):
    def install(self):
        print("正在解压缩安装程序...")
        super().install()


linux = MiniOS("Linux")
print(linux)

pycharm = PyCharm("PyCharm", "1.0", "python 开发的 IDE 环境")
chrome = Chrome("Chrome", "2.0", "谷歌浏览器")

linux.install_app(pycharm)
linux.install_app(chrome)
linux.install_app(chrome)

print(linux)

运行结果

Linux 安装的软件列表为 []
将 PyCharm [1.0] 的执行程序复制到程序目录...
正在解压缩安装程序...
将 Chrome [2.0] 的执行程序复制到程序目录...
已经安装了 Chrome,无需再次安装
Linux 安装的软件列表为 ['PyCharm', 'Chrome']
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值