文章目录

Python面经总结

参考网址

1. 基础知识

1.1 Python的解释器种类和特点?

  • CPython:c语言开发的 使用最广的解释器
  • IPython基于cpython之上的一个交互式计时器 交互方式增强;功能和cpython一样
  • PyPy目标是执行效率 采用JIT技术对python代码进行动态编译,提高执行效率
  • JPython运行在Java上的解释器 直接把python代码编译成Java字节码执行
  • IronPython运行在微软 .NET 平台上的解释器,把python编译成. NET 的字节码

1.2 解释型语言和编译型语言区别

  • 解释型 在运行时才翻译;每个语句执行时候才翻译;效率比较低;跨平台性好 (python)
  • 编译型 程序执行之前有一个专门的编译过程 把程序编译成机器语言的文件 程序执行效率高 跨平台性差 (c c++)

1.3 Python的最大递归层数

  • Python的最大递归层数是可以设置的,默认的在window上的最大递归层数是 998。可以通过sys.setrecursionlimit()进行设置,但是一般默认不会超过3925-3929这个范围。

1.4 字节码和机器码

字节码是一种中间状态(中间码)的二进制代码(文件)。需要直译器转译后才能成为机器码。

1.5 列举布尔值为False的常见值?

0 , [ ] , " , ( ) , { }

1.6 *arg和**kwarg作用是什么?参数的收集和分配

  • *args用来将参数打包成tuple(元组)给函数体调用;
  • **kwargs 打包关键字参数成dict(字典)给函数体调用。当我们混合使用这三个参数时,必须遵循arg,*args,**kwargs这样的顺序,否则程序会报错。

1.7 is和==的区别?

python中对象包含的三个基本要素,分别是:id(身份标识)、type(数据类型)和value(值)。is也被叫做同一性运算符,这个运算符比较判断的是对象间的唯一身份标识,也就是id是否相同。==是python标准操作符中的比较操作符,用来比较判断两个对象的value(值)是否相等.

1.8 什么是Python的可变类型和不可变类型?

python中不可变数据类型有以下三种:Number(数字)String(字符串)Tuple(元组)
可变类型:List(列表)Set(集合)Dictionary(字典)。

1.9 Python list底层实现及原理

Python中的列表是由对其它对象的引用组成的连续数组。指向这个数组的指针及其长度被保存在一个列表头结构中。这意味着,每次添加或删除一个元素时,由引用组成的数组需要改变大小(重新分配)。幸运的是,Python在创建这些数组时采用了指数过分配,所以并不是每次操作都需要改变数组的大小。但是,也因为这个原因添加或取出元素的平摊复杂度较低。不幸的是,在普通链表上“代价很小”的其它一些操作在Python中计算复杂度相对过高。利用 list.insert方法在任意位置插入一个元素——复杂度O(N);利用 list.delete或del删除一个元素——复杂度O(N)。

1.10 虚拟内存与物理内存区别

参考网址

  • 物理内存:没有虚拟内存概念的时候,程序寻址用的都是物理地址。程序能寻址的范围是有限的,这取决于CPU的地址线条数。比如在32位平台下,寻址的范围是2^32也就是4G。并且这是固定的,如果没有虚拟内存,且每次开启一个进程都给4G的物理内存,就可能会出现很多问题:
    • 因为我的物理内存时有限的,当有多个进程要执行的时候,都要给4G内存,很显然你内存小一点,这很快就分配完了,于是没有得到分配资源的进程就只能等待。当一个进程执行完了以后,再将等待的进程装入内存。这种频繁的装入内存的操作是很没效率的。
    • 由于指令都是直接访问物理内存的,那么我这个进程就可以修改其他进程的数据,甚至会修改内核地址空间的数据,这是我们不想看到的
    • 因为内存时随机分配的,所以程序运行的地址也是不正确的。
  • 虚拟内存:当每个进程创建的时候,内核会为进程分配4G的虚拟内存,当进程还没有开始运行时,这只是一个内存布局。实际上并不立即就把虚拟内存对应位置的程序数据和代码拷贝到物理内存中,只是建立好虚拟内存和磁盘文件之间的映射就好(叫做存储器映射)。这个时候数据和代码还是在磁盘上的。当运行到对应的程序时,进程去寻找页表,发现页表中地址没有存放在物理内存上,而是在磁盘上,于是发生缺页异常,于是将磁盘上的数据拷贝到物理内存中。另外在进程运行过程中,要通过malloc来动态分配内存时,也只是分配了虚拟内存,即为这块虚拟内存对应的页表项做相应设置,当进程真正访问到此数据时,才引发缺页异常。可以认为虚拟空间都被映射到了磁盘空间中。
  • 利用虚拟内存机制的优点
    • 无需关心物理内存地址:既然每个进程的内存空间都是一致而且固定的(32位平台下都是4G),所以链接器在链接可执行文件时,可以设定内存地址,而不用去管这些数据最终实际内存地址,这交给内核来完成映射关系。
    • 节省物理内存:当不同的进程使用同一段代码时,比如库文件的代码,在物理内存中可以只存储一份这样的代码,不同进程只要将自己的虚拟内存映射过去就好了,这样可以节省物理内存。
    • 有效利用碎片化的物理内存:在程序需要分配连续空间的时候,只需要在虚拟内存分配连续空间,而不需要物理内存时连续的,实际上,往往物理内存都是断断续续的内存碎片。这样就可以有效地利用我们的物理内存。

1.11 哪些操作会导致Python内存泄露?

  • 内存中加载的数据量过于庞大,如一次从数据库取出过多数据; 一般比如数据查询未做分页处理。
  • 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收。
  • 代码中存在死循环或循环产生过多重复的对象实体。
  • 使用的第三方软件中的BUG; 一般引用第三方jar包过多会出现此类问题。
  • 启动参数内存值设定的过小 。

Python语法相关

1.12 文件操作时:xreadlines和readlines的区别是?

二者使用时相同,但返回类型不同,xreadlines返回的是一个生成器,readlines返回的是list。

1.13 lambda的表达式?

g = lambda x ,y: x * y
g ( 2,3 )

1.14 什么是metaclass

https://www.cnblogs.com/JIM-FAN/p/13358488.html

1.15 什么是反射

在做程序开发中,我们常常会遇到这样的需求:需要执行对象里的某个方法,或需要调用对象中的某个变量,但是由于种种原因我们无法确定这个方法或变量是否存在,这是我们需要用一个特殊的方法或机制要访问和操作这个未知的方法或变量,这中机制就称之为反射。
接下记录下反射几个重要方法:getattr、hasattr、delattr和setattr较为全面的实现了基于字符串的反射机制。他们都是对内存内的模块进行操作,并不会对源文件进行修改。动态导入模块
https://www.cnblogs.com/kongk/p/8645202.html

1.16 Python logging模块的作用是什么?以及应用场景?

程序调试;了解软件程序运行情况,是否正常;软件程序运行故障分析与问题定位;用户行为分析

1.17 os与sys模块的作用与常用方法

  • os:这个模块提供了一种方便的使用操作系统函数的方法。
  • sys:这个模块可供访问由解释器使用或维护的变量和与解释器进行交互的函数。

1.18 import一个包时过程是怎么样的?

https://blog.csdn.net/gaifuxi9518/article/details/81038818

1.19 def func(a,b=[]) 这种写法有什么坑?

函数的第二个默认参数是一个list,当第一次执行的时候实例化了一个list,第二次执行还是用第一次执行的时候实例化的地址存储

1.20 如何在函数中设置一个全局变量 ?

在函数的内部,通过global声明,使在函数内部中设置一个全局变量,这个全局变量可以在任意的函数中进行调用!

1.21 进制转换

x = 0b1010 print(x) int(‘1010’,base=2) int(‘0b1010’,2) x = eval(‘0b1010’)
ret = bin(18) print(ret)
ret = oct(18) print(ret) ret = int(‘0o12’,16)
ret = hex(87) print(ret) ret = int(‘0x12’,16)

1.22 python中内置的数据结构有几种?

a. 整型 int、 长整型 long、浮点型 float、 复数 complex b. 字符串 str、 列表list、 元祖tuple c. 字典 dict 、 集合 set

1.23 什么是闭包?

在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包。

1.24 with关键字

with表达式其实是try-finally的简写形式。但是又不是全相同。
with 语句实质是上下文管理。
1、上下文管理协议。包含方法__enter__() 和 exit(),支持该协议对象要实现这两个方法。
2、上下文管理器,定义执行with语句时要建立的运行时上下文,负责执行with语句块上下文中的进入与退出操作。
3、进入上下文的时候执行__enter__方法,如果设置as var语句,var变量接受__enter__()方法返回值。
4、如果运行时发生了异常,就退出上下文管理器。调用管理器__exit__方法。
链接:https://www.jianshu.com/p/5b01fb36fd4c

1.25 Python 实例方法、类方法、静态方法的区别与作用

https://www.cnblogs.com/wcwnina/p/8644892.html

  • 实例方法:第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法);只能由实例对象调用。
  • 类方法:使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法);类和实例对象都可以调用。
  • 静态方法:使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法;类和实例对象都可以调用。

1.26 __new__方法

在这里插入图片描述

1.27 Python自省

自省就是面向对象的语言所写的程序在运行时,所能知道对象的类型.简单一句就是运行时能够获得对象的类型.比如type(),dir(),getattr(),hasattr(),isinstance().

1.28Python中单下划线和双下划线

  • _foo:一种约定,用来指定变量私有.程序员用来指定私有变量的一种方式.不能用from module import * 导入,其他方面和公有一样访问;
  • __foo:这个有真正的意义:解析器用_classname__foo来代替这个名字,以区别和其他类相同的命名,它无法直接像公有成员一样随便访问,通过对象名._类名__xxx这样的方式可以访问.
  • __foo__:一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突,就是例__init__(), __del__(),__call__()这些特殊方法

2. Python内存回收

参考网址

2.1 Python的垃圾回收机制是怎样的?

python里也同java一样采用了垃圾收集机制,不过不一样的是:python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略。python里每一个东西都是对象,它们的核心就是一个结构体:PyObject。PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少。使用sys.getrefcount(a)可以查看一个对象的引用计数。

  • 引用计数机制的优点:简单;实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
  • 引用计数机制的缺点:循环应用时,无法回收。也正是因为这个原因,才需要通过标记-清理和分代收集机制来辅助引用计数机制。

2.2 内存垃圾回收:标记-清理

c=[5,6]#假设此时c的引用为1
d=[7,8]#假设此时d的引用为1
c.append(d)#c的引用+1=2
d.append(c)#d的引用+1=2

假如现在需要同时删除c、d,应该如何回收呢?
python标记删除时通过两个容器来完成的:死亡容器、存活容器。

  • 首先,我们先来分析情况2,删除c、d后,c的引用为1,d的引用为1,根据引用计数,还无法删除
  • 标记删除第一步:对执行删除操作后的每个引用-1,此时c的引用为0,d的引用为0,把他们都放到死亡容器内。把那些引用仍然大于0的放到存活容器内。
  • 标记删除第二步:遍历存活容器,查看是否有的存活容器引用了死亡容器内的对象,如果有就把该对象(注意是对象,比如0x7f94bb602f80,不是对象的引用)从死亡容器内取出,放到存活容器内。由于c、d都没有对象引用他们了,所以经过这一步骤,他们还是在死亡组。
  • 标记删除第三步:将死亡组所有对象删除。这样就完成了对从c、d的删除。
a=[1,2]#假设此时a的引用为1
b=[3,4]#假设此时b的引用为1
#循环引用
a.append(b)#b的引用+1=2
b.append(a)//a的引用+1=2

假如现在需要删除a,应该如何回收呢?(注意删除a可以使用del a,这样a这个引用就不存在了,但是它指向的对象,在标记删除后还存在,因为还被b使用者

  • 标记删除第一步:对执行删除(-1)后的每个引用-1,那么a的引用就是0,b的引用为1,将a放到死亡容器,将b放到存活容器。
  • 标记删除第二步:循环存活容器,发现b引用a,复活a:将a放到存活容器内。
  • 标记删除第三步:删除死亡容器内的所有对象。

2.3 内存垃圾回收:分代回收细节

为了更合理的进行【标记-删除】,就需要对对象进行分代处理

  • 新创建的对象做为0代
  • 每执行一个【标记-删除】,存活的对象代数就+1
  • 代数越高的对象(存活越持久的对象),进行【标记-删除】的时间间隔就越长。这个间隔,江湖人称阀值。

2.4 三种情况触发垃圾回收

  • 调用gc.collect()
  • GC达到阀值时
  • 程序退出时

2.5 小整数对象池与intern机制

  • 小整数[-5,257):共用对象,常驻内存
  • 单个字符:共用对象,常驻内存
  • 单个单词等不可变类型,默认开启intern机制,共用对象,引用计数为0时销毁。

2.6 调优手段

  • 手动垃圾回收
  • 调高垃圾回收阈值
  • 避免循环引用

3. Python生成器和迭代器

3.1 生成器,迭代器的区别?

迭代器是遵循迭代协议的对象。用户可以使用 iter() 以从任何序列得到迭代器(如 list, tuple, dictionary, set 等)。另一个方法则是创建一个另一种形式的迭代器 —— generator 。要获取下一个元素,则使用成员函数 next()(Python 2)或函数 next() function (Python 3) 。当没有元素时,则引发 StopIteration 此例外。若要实现自己的迭代器,则只要实现 next()(Python 2)或 next()( Python 3)

生成器(Generator),只是在需要返回数据的时候使用yield语句。每次next()被调用时,生成器会返回它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值)

区别: 生成器能做到迭代器能做的所有事,而且因为自动创建iter()和next()方法,生成器显得特别简洁,而且生成器也是高效的,使用生成器表达式取代列表解析可以同时节省内存。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出StopIteration异常。

3.2 迭代器

把一个类作为一个迭代器使用需要在类中实现两个方法 、__iter__() 与 __next__() 。__iter__() 方法返回一个特殊的迭代器对象, 这个迭代器对象实现了\ __next__() 方法并通过 StopIteration 异常标识迭代的完成。__next__() 方法(Python 2 里是 next())会返回下一个迭代器对象。

class MyNumbers:
  def __iter__(self):
    self.a = 1
    return self
 
  def __next__(self):
    x = self.a
    self.a += 1
    return x
 
myclass = MyNumbers()
myiter = iter(myclass)

3.3 生成器

在 Python 中,使用了 yield 的函数被称为生成器(generator)。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

import sys
 
def fibonacci(n): # 生成器函数 - 斐波那契
    a, b, counter = 0, 1, 0
    while True:
        if (counter > n): 
            return
        yield a
        a, b = b, a + b
        counter += 1
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
 
while True:
    try:
        print (next(f), end=" ")
    except StopIteration:
        sys.exit()

4. Python设计模式

4.1 对设计模式的理解,简述你了解的设计模式?

设计模式是经过总结,优化的,对我们经常会碰到的一些编程问题的可重用解决方案。一个设计模式并不像一个类或一个库那样能够直接作用于我们的代码,反之,设计模式更为高级,它是一种必须在特定情形下实现的一种方法模板。 常见的是工厂模式和单例模式

4.2 什么是装饰器?

装饰器本质上是一个python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。

import time
from functools import wraps

def timeit(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        ret = func(*args, **kwargs)
        end = time.time()
        print('used:',end-start)
        return ret
    return wrapper
@timeit
def foo():
    print('in foo()')

## 执行foo(),输出结果
in foo()
used: 0.0

4.3 什么是单例模式?

参考链接
单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。

4.4 如何实现单例模式?

参考链接

  • 第一种方法:使用装饰器
def singleton(cls):
    instances = {}
    def wrapper(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return wrapper
@singleton
class Foo(object):
    pass
foo1 = Foo()
foo2 = Foo()
print foo1 is foo2 #True
  • 第二种方法:使用基类 New 是真正创建实例对象的方法,所以重写基类的new 方法,以此保证创建对象的时候只生成一个实例
class Singleton(object):
    def __new__(cls,*args,**kwargs):
        if not hasattr(cls,'_instance'):
            cls._instance = super(Singleton,cls).__new__(cls,*args,**kwargs)
        return cls._instance
class Foo(Singleton):
    pass
foo1 = Foo()
foo2 = Foo()
print foo1 is foo2 #True

或者

class Singleton(object):
	def __init__(self):
		pass

	def __new__(cls, *args, **kwargs):
		if not hasattr(Singleton, "_instance"): # 反射
			Singleton._instance = object.__new__(cls)
		return Singleton._instance
obj1 = Singleton()
obj2 = Singleton()
print(obj1, obj2)
  • 第三种方法:元类,元类是用于创建类对象的类,类对象创建实例对象时一定要调用call方法,因此在调用call时候保证始终只创建一个实例即可,type是python的元类
class Singleton(type):
    def __call__(cls,*args,**kwargs):
        if not hasattr(cls,'_instance'):
            cls._instance = super(Singleton,cls).__call__(*args,**kwargs)
        return cls._instance
class Foo(object):
    __metaclass__ = Singleton
foo1 = Foo()
foo2 = Foo()
print foo1 is foo2 #True

4.5 单例模式的应用场景有那些?

单例模式应用的场景一般发现在以下条件下: 资源共享的情况下,避免由于资源操作时导致的性能或损耗等,如日志文件,应用配置。 控制资源的情况下,方便资源之间的互相通信。如线程池等,1,网站的计数器 2,应用配置 3.多线程池 4数据库配置 数据库连接池 5.应用程序的日志应用

5. Python多线程

参考https://www.cnblogs.com/fengf233/p/11468541.html

5.1 进程与线程

  • 进程:进程是资源(CPU、内存等)分配的最小单位,进程有独立的地址空间与系统资源,一个进程可以包含一个或多个线程
  • 线程:线程是CPU调度的最小单位,是进程的一个执行流,线程依赖于进程而存在,线程共享所在进程的地址空间和系统资源,每个线程有自己的堆栈和局部变量

5.2 并发与并行

  • 并发:当系统只有一个CPU时,想执行多个线程,CPU就会轮流切换多个线程执行,当有一个线程被执行时,其他线程就会等待,但由于CPU调度很快,所以看起来像多个线程同时执行
  • 并行:当系统有多个CPU时,执行多个线程,就可以分配到多个CPU上同时执行

5.3 同步与异步

  • 同步:调用者调用一个功能时,必须要等到这个功能执行完返回结果后,才能再调用其他功能
  • 异步:调用者调用一个功能时,不会立即得到结果,而是在调用发出后,被调用功能通过状态、通知来通告调用者,或通过回调函数处理这个调用

5.4 线程锁

每当一个线程a要访问共享数据时,必须先获得锁定;如果已经有别的线程b获得锁定了,那么就让线程a暂停,也就是同步阻塞;等到线程b访问完毕,释放锁以后,再让线程a继续
锁有两种状态:被锁(locked)和没有被锁(unlocked)。拥有acquire()和release()两种方法,并且遵循一下的规则:

  • 如果一个锁的状态是unlocked,调用acquire()方法改变它的状态为locked;
    如果一个锁的状态是locked,acquire()方法将会阻塞,直到另一个线程调用release()方法释放了锁;
    如果一个锁的状态是unlocked调用release()会抛出RuntimeError异常;
    如果一个锁的状态是locked,调用release()方法改变它的状态为unlocked。

5.5 线程锁的缺点

  • 首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。
  • 其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。

5.6 条件同步

条件同步机制是指:一个线程等待特定条件,而另一个线程发出特定条件满足的信号。

5.7 事件同步(Event)

基于事件的同步是指:一个线程发送/传递事件,另外的线程等待事件的触发。与条件同步类似,只是少了我们自己添加锁的步骤。

5.8 谈谈你对多进程,多线程,以及协程的理解,项目是否用?

进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最小单位,进程拥有自己独立的内存空间,所有进程间数据不共享,开销大。 线程: cpu调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在,一个进程至少有一个线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。 协程: 是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操中栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

5.9 Python异步使用场景有那些?

异步的使用场景: 1、 不涉及共享资源,获对共享资源只读,即非互斥操作 2、 没有时序上的严格关系 3、 不需要原子操作,或可以通过其他方式控制原子性 4、 常用于IO操作等耗时操作,因为比较影响客户体验和使用性能 5、 不影响主线程逻辑

5.10 什么是多线程竞争?

线程是非独立的,同一个进程里线程是数据共享的,当各个线程访问数据资源时会出现竞争状态即:数据几乎同步会被多个线程占用,造成数据混乱,即所谓的线程不安全 那么怎么解决多线程竞争问题?—锁 锁的好处: 确保了某段关键代码(共享数据资源)只能由一个线程从头到尾完整地执行能解决多线程资源竞争下的原子操作问题。 锁的坏处: 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了 锁的致命问题: 死锁

5.11 什么是僵尸进程和孤儿进程?怎么避免僵尸进程?

孤儿进程: 父进程退出,子进程还在运行的这些子进程都是孤儿进程,孤儿进程将被init 进程(进程号为1)所收养,并由init 进程对他们完成状态收集工作。 僵尸进程: 进程使用fork 创建子进程,如果子进程退出,而父进程并没有调用wait 获waitpid 获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中的这些进程是僵尸进程。 避免僵尸进程的方法: 1.fork 两次用孙子进程去完成子进程的任务 2.用wait()函数使父进程阻塞 3.使用信号量,在signal handler 中调用waitpid,这样父进程不用阻塞

5.12 说说下面几个概念:同步,异步,阻塞,非阻塞?

同步: 多个任务之间有先后顺序执行,一个执行完下个才能执行。 异步: 多个任务之间没有先后顺序,可以同时执行,有时候一个任务可能要在必要的时候获取另一个同时执行的任务的结果,这个就叫回调! 阻塞: 如果卡住了调用者,调用者不能继续往下执行,就是说调用者阻塞了。 非阻塞: 如果不会卡住,可以继续执行,就是说非阻塞的。 同步异步相对于多任务而言,阻塞非阻塞相对于代码执行而言。

5.13 什么是线程安全,什么是互斥锁?

每个对象都对应于一个可称为’互斥锁‘的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。 同一进程中的多线程之间是共享系统资源的,多个线程同时对一个对象进行操作,一个线程操作尚未结束,另一线程已经对其进行操作,导致最终结果出现错误,此时需要对被操作对象添加互斥锁,保证每个线程对该对象的操作都得到正确的结果。

5.14 多线程交互访问数据,如果访问到了就不访问了?

怎么避免重读? 创建一个已访问数据列表,用于存储已经访问过的数据,并加上互斥锁,在多线程访问数据的时候先查看数据是否在已访问的列表中,若已存在就直接跳过。

5.15 什么是死锁?怎么解决

https://www.cnblogs.com/JimmyFanHome/p/9914562.html
若干子线程在系统资源竞争时,都在等待对方对某部分资源解除占用状态,结果是谁也不愿先解锁,互相干等着,程序无法执行下去,这就是死锁。

  • 产生死锁的四个必要条件
    1.互斥性:线程对资源的占有是排他性的,一个资源只能被一个线程占有,直到释放。
    2.请求和保持条件:一个线程对请求被占有资源发生阻塞时,对已经获得的资源不释放。
    3.不剥夺:一个线程在释放资源之前,其他的线程无法剥夺占用。
    4.循环等待:发生死锁时,线程进入死循环,永久阻塞。

  • 避免死锁的方法
      1.破坏“请求和保持”条件
      想办法,让进程不要那么贪心,自己已经有了资源就不要去竞争那些不可抢占的资源。比如,让进程在申请资源时,一次性申请所有需要用到的资源,不要一次一次来申请,当申请的资源有一些没空,那就让线程等待。不过这个方法比较浪费资源,进程可能经常处于饥饿状态。还有一种方法是,要求进程在申请资源前,要释放自己拥有的资源。
      2.破坏“不可抢占”条件
      允许进程进行抢占,方法一:如果去抢资源,被拒绝,就释放自己的资源。方法二:操作系统允许抢,只要你优先级大,可以抢到。
      3.破坏“循环等待”条件
      将系统中的所有资源统一编号,进程可在任何时刻提出资源申请,但所有申请必须按照资源的编号顺序(升序)提出

  • 死锁的解除
      1.抢占资源,从一个或多个进程中抢占足够数量的资源,分配给死锁进程,以解除死锁状态。
      2.终止(或撤销)进程,终止(或撤销)系统中的一个或多个死锁进程,直至打破循环环路,使系统从死锁状态解脱出来.

  • 死锁的检测
      1.每个进程、每个资源制定唯一编号
      2.设定一张资源分配表,记录各进程与占用资源之间的关系
      3.设置一张进程等待表,记录各进程与要申请资源之间的关系

5.16 为什么说python的多线程是伪多线程?

GIL锁 全局解释器锁(只在cython里才有) 作用: 限制多线程同时执行,保证同一时间只有一个线程执行,所以cython里的多线程其实是伪多线程! 所以python里常常使用协程技术来代替多线程,协程是一种更轻量级的线程。 进程和线程的切换时由系统决定,而协程由我们程序员自己决定,而模块gevent下切换是遇到了耗时操作时才会切换 三者的关系:进程里有线程,线程里有协程。

5.17 GIL锁

python GIL锁 也称为:全局解释器所(global interpreter lock),当有多个线程同时执行时,每个线程在执行时候都需要先获取GIL,保证同一时刻只有一个线程可以执行代码,即同一时刻只有一个线程使用CPU,也就是说多线程并不是真正意义上的同时执行!
任何Python 线程threading 执行前,必须先获得GIL锁才能执行,当线程获取到GIL锁之后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。

5.18 什么是递归锁

在Python中为了支持同一个线程中多次请求同一资源,Python提供了可重入锁。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获取资源

5.19 什么是信号量?

同进程的一样,semaphore管理一个内置的计数器,每当调用acquire()时内置函数-1,每当调用release()时内置函数+1。
计数器不能为0,当计数器为0时acquire()将阻塞线程,直到其他线程调用release()。

如果使用RLock代替Lock,则不会发生死锁,二者的区别是:递归锁可以连续acquire多次,而互斥锁只能acquire一次

6. Python的一些技巧知识

6.1 对字典的键值对进行排序

  • 字典按键排序::sorted (key_value)
  • 字典按值排序::sorted(key_value.items(), key = lambda kv:(kv[1], kv[0]))

6.2 sotred()函数

参考https://www.runoob.com/python3/python3-func-sorted.html
sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。

example_list = [5, 0, 6, 1, 2, 7, 3, 4]
result_list = sorted(example_list, key=lambda x: x*-1)
print(result_list)
[7, 6, 5, 4, 3, 2, 1, 0]

注意参数key,该参数传入一个lambda表达式,实现对可迭代对象中的每个元素的加工。其中x表示可迭代对象中的每个元素。

6.3 加载超大文件

def get_lines():
    with open('file.txt','rb') as f:
        for i in f:
            yield i

6.4 打乱一个排好序的list对象alist

import random
alist = [1,2,3,4,5]
random.shuffle(alist)
print(alist)
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐