文章目录
- 一、Python-GIL(全局解释器锁)
- 二、高级编程技巧总结
- 1.如何区别可变数据类型和不可变数据类型
- 2.Python垃圾回收机制
- 3.Python中函数或成员变量包含单下划线前缀结尾和双下划线前缀结尾的区别
- 4.判断一个对象是函数还是方法
- 5.super函数的用法
- 6.使用isinstance和type的区别
- 7.创建大量实例节省内存
- 8.上下文管理器
- 9.判断一个对象中是否具有某个属性
- 10.property动态属性的使用
- 11.如何使用type创建自定义类
- 12.生成器的创建
- 13.TCP和UDP的区别
- 14.TCP服务端通信流程
- 15.创建线程的两种方式
- 16.解释线程资源竞争,以及解决方案
- 17.死锁出现的原因
- 18.进程之间的通信,以及进程池中的进程通信
- 19.同步、异步、阻塞、非阻塞
- 20.进程、线程、协程对比
- 21.Python GIL的概念,以及它对Python多线程的影响
一、Python-GIL(全局解释器锁)
面试题:
描述Python GIL的概念,以及它对Python多线程的影响
在Linux中执行
(1)主线程死循环
while True:
pass
通过htop查看CPU使用情况,相当于Windows中的任务管理器。
显示:
运行之后CPU占满,利用率100%。
(2)2个线程死循环
import threading
# 子线程死循环
def test():
while True:
pass
t1 = threading.Thread(target=test)
t1.start()
# 主线程死循环
while True:
pass
显示:
运行之后,如果是双核,两个CPU各占一半,利用率50%。
解释:
两个线程是交叉运行的,任意时刻只有一个线程处于运行状态。
(3)2个进程死循环
import multiprocessing
def deapLoop():
while True:
pass
# 子进程死循环
p1 = multiprocessing.Process(target=deapLoop)
p1.start()
# 主进程死循环
while True:
pass
显示:
运行之后,如果是双核,CPU都占满,利用率100%。
解释:
进程是真正利用多核的并发优势,多个CPU同时利用。
Python代码需要通过Python解释器翻译成计算机语言,才能在CPU中处理运行。
解释器有CPython(默认)、JPython(Java语言编写)、IPython等。
GIL只出现在CPython中。
参考答案:
- Python语言和GIL没有关系,仅仅是由于历史原因在CPython虚拟机,难以移除GIL。
- GIL即全局解释器锁,每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行代码。
- 线程释放GIL锁的情况:
在IO操作等可能会引起阻塞的system call之前,可以暂时释放GIL,但在执行完毕后,必须重新获取GIL,Python3使用计时器。 - Python使用多进程是可以利用多核的CPU资源的。
- 多线程爬取比单线程性能有提升,因为遇到IO阻塞会自动释放GIL锁。
二、高级编程技巧总结
1.如何区别可变数据类型和不可变数据类型
从对象的内存地址方向:
- 可变数据类型:
内存不变,值可以改变,如列表、字典、集合等。 - 不可变数据类型:
值变化时,内存地址也跟着变化,如整型、字符串、元组、布尔型等。
2.Python垃圾回收机制
Python中可以不事先声明变量而直接赋值。
垃圾回收有3种方式:
- 引用计数机制:
当引用计数为0时,自动将变量回收。
可能会存在循环引用 - 标记清除
- 分代回收
引用计数回顾:
import sys
a = []
print(sys.getrefcount(a))
def func(a):
print(sys.getrefcount(a))
func(a)
打印
2
4
3.Python中函数或成员变量包含单下划线前缀结尾和双下划线前缀结尾的区别
- 单下划线前缀结尾:
_代表保护变量,不希望更改; - 双下划线前缀结尾:
__代表私有成员(私有属性和私有方法),类外不能访问。
测试:
class Person(object):
def __init__(self):
self.__age = 12
self._age = 13
def _set_age(self, age):
self.__age = age
if __name__ == '__main__':
p = Person()
try:
# 不能直接访问私有属性
print(p.__age)
except:
# 通过该方式访问私有属性,不建议
print(p._Person__age)
print(p._age)
打印:
12
13
4.判断一个对象是函数还是方法
from types import MethodType, FunctionType
class Demo(object):
def __init__(self):
pass
def foo(self):
pass
def foo2():
pass
print(isinstance(Demo().foo, FunctionType))
print(isinstance(foo2, FunctionType))
print(isinstance(Demo().foo, MethodType))
print(isinstance(foo2, MethodType))
打印
False
True
True
False
5.super函数的用法
super:
调用父类的方法,是按照MRO算法调用的。
class A(object):
def __init__(self):
print('A')
class B(A):
def __init__(self):
print('B')
super(B, self).__init__()
class C(A):
def __init__(self):
print('C')
super().__init__()
class D(B, C):
def __init__(self):
print('D')
super().__init__()
if __name__ == '__main__':
d = D()
print(D.__mro__)
打印
D
B
C
A
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
6.使用isinstance和type的区别
isinstance和type是用来查看对象类型的。
- isinstance考虑类的继承关系
- type不会考虑类的继承关系
7.创建大量实例节省内存
用__slots__
;
单例模式:
只实例化一次。
8.上下文管理器
类中实现__enter__
和__exit__
方法。
class Sample(object):
def __enter__(self):
# 获取资源
print('Start')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# 释放资源
print('End')
def demo(self):
print('demo')
with Sample() as s:
s.demo()
打印
Start
demo
End
9.判断一个对象中是否具有某个属性
用hasattr(object, attr)
。
class Demo(object):
name = 'Corley'
def run(self):
return 'hello'
d = Demo()
print(hasattr(d, 'name'))
print(hasattr(d, 'run'))
print(hasattr(d, 'age'))
打印
True
True
False
10.property动态属性的使用
- 装饰器
property(get, set, ...)
11.如何使用type创建自定义类
type(name, bases, attr)
name:类名
bases:继承对象的元组
attr:类属性、类方法、静态方法的字典
12.生成器的创建
yield
创建- 生成器表达式()
启动生成器:next()
send()
方法:
如果前面没有调用next()时,第一次调用send()
必须传None。
13.TCP和UDP的区别
- TCP面向连接,UDP无连接
- TCP安全可靠,UDP不保证可靠交付
- UDP实时性更好,适用于高速传输
- TCP点到点,UDP支持一对一、一对多、多对多
-TCP对系统资源要求较高,UDP相对要求较低
14.TCP服务端通信流程
- 创建套接字
- 绑定信息
- listen主动变被动
- accept等待客户端连接
- 收发数据
- 关闭套接字
15.创建线程的两种方式
- 普通创建–
threading.Thread()
- 类的继承–继承
threading.Thread
例如:
import threading
import time
class A(threading.Thread):
def __init__(self, name):
super().__init__(name=name)
def run(self) -> None:
for i in range(5):
print(i)
if __name__ == '__main__':
a = A('a')
a.start()
打印
0
1
2
3
4
16.解释线程资源竞争,以及解决方案
线程之间共享资源,肯在一个线程运行的时候,暂停,另一个线程执行并占用资源,导致竞争资源。
解决:
加互斥锁。
# 创建锁
mutex = threading.Lock()
rmutex = threading.RLock() # 可重入锁
# 上锁
mutex.acquire()
# 解锁
mutex.release()
可能会出现死锁。
17.死锁出现的原因
两个对象之间互相等待对方释放锁。
18.进程之间的通信,以及进程池中的进程通信
- 进程之间的通信:
multiprossing.Queue
- 进程池中的进程通信:
multiprocessing.Manager().Queue
19.同步、异步、阻塞、非阻塞
- 同步:
多个任务之间有先后顺序 - 异步:
多个任务之间无先后顺序,可同时执行 - 阻塞:
停止,不继续向前执行 - 非阻塞:
不停止,继续向前执行
20.进程、线程、协程对比
- 进程:
资源分配的单位;
需要的资源较大;
能真正发挥多核的优势;
只有进程才可能是并行。 - 线程:
操作系统调度的单位 - 协程:
占有资源最少
21.Python GIL的概念,以及它对Python多线程的影响
GIL:
全局解释器锁,每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行代码。
在IO操作等可能会引起阻塞的system call之前,可以暂时释放GIL,但在执行完毕后,必须重新获取GIL。