文章目录
- 说说什么是解释性语言,什么是编译性语言?
- 说说Python中可变与不可变类型?
- 说说进程与线程?
- 说说多进程和多线程之间的通信方式?
- 进程的实现方式:
- 说说Python中的多线程?
- 为什么要有GIL锁?
- 说说Python中的多进程?
- 说说Python互斥锁与死锁?
- 怎么避免僵尸进程?
- 说说Python的深拷贝与浅拷贝?
- 说说Python中yeild和return的区别?
- 说说Python中set的底层实现?
- 说说Python中字典与set区别?
- 说说Python中__init__和__new__和__call__的区别?
- 说说Python 中的is和==区别?
- 说说函数调用参数的传递方式是值传递还是引用传递?
- 说说对缺省参数的理解?
- 说说Python中什么元素为假?
- 说说lambda函数?
- 说说垃圾回收机制
- 迭代器和生成器
- 带参装饰器和非带参装饰器:
- docker怎么实现隔离?
说说什么是解释性语言,什么是编译性语言?
计算机不能直接理解高级语言,只能直接理解机器语言,所以必须要把高级语言翻译成机器语言,计算机才能执行高级语言编写的程序。
解释性语言在运行程序的时候才会进行翻译。
编译型语言写的程序在执行之前,需要一个专门的编译过程,把程序编译成机器语言(可执行文件)。
说说Python中可变与不可变类型?
Python可变类型是列表、集合、字典,不可变有字符串(string)、元组(tuple)、数字(int,float)。
说说进程与线程?
进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最小单位,进程拥有自己独立的内存空间,所有进程间数据不共享,开销大。
线程: cpu调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在,一个进程至少有一个线程,叫主线程,而**多个线程共享内存(数据共享,共享全局变量),**从而极大地提高了程序的运行效率。
说说多进程和多线程之间的通信方式?
多进程间的通信方式:
- 管道(Pipe):这是最简单的通信方式,数据只能在一个方向上流动,而且只能在有共同祖先的两个进程之间使用。
- 命名管道(named pipe): 它与匿名管道相似,不同之处在于它可以用于没有相关性的进程之间的通信。
- 信号(Signal):这是一种比较复杂的通信方法,用于通知接收进程某个事件已经发生。
- 消息队列(Message Queue):它是由消息的链表,存放在内核中并由进程id来引用。
- 共享内存(Shared Memory):它允许多个进程访问同一块内存空间。
- 套接字(Socket):它是在网络中不同主机的进程进行双向通信的主要方式。
- 信号量(Semaphore):主要作为进程间以及同一进程内不同线程之间的同步手段。
- 文件锁(File-locking):可以用来解决多进程同时读写文件的问题。
多线程间的通信方式:
- 锁机制(Locks):通过互斥锁(mutex)、读-写锁(rwlock)等锁机制来实现线程间的同步。
- 条件变量(Condition Variables):它可以允许一个线程就某个条件(通常是共享资源的状态)进行等待,当条件发生(或者说满足)时,另外的线程可以唤醒一个或多个正在等待的线程。
- 信号量(Semaphore):信号量也可以用在多线程之间进行同步,尤其适用于控制对有限数量的资源的访问问题。
- 屏障(Barrier):屏障是一种简单的同步方式,它允许多个线程在某个点上进行同步。
- 消息传递(Message Passing):线程之间可以通过队列,链表等数据结构进行数据交换和通信。
进程的实现方式:
进程是操作系统进行资源分配和调度的基本单位,不同的操作系统对进程的实现方式有各自的特点。在大多数现代操作系统中,如Linux,Windows等,进程的实现主要涉及以下几个方面:
- 进程控制块(Process Control Block, PCB):每个进程都有一个进程控制块来存储与该进程相关的信息,如进程状态、程序计数器、CPU寄存器值、优先级、内存指针(指向该进程的代码段和数据段)等。
- 进程状态管理:操作系统需要跟踪并控制每个进程的状态。常见的进程状态包括新建(new)、运行(running)、等待(waiting)、就绪(ready)和终止(terminated)。
- 调度:操作系统决定何时以及如何将CPU等资源分配给进程,这通常由一些调度算法完成,如先来先服务(FCFS)、短作业优先(SJF)、轮转(RoundRobin)等。
- 上下文切换:当CPU从一个进程切换到另一个进程时,操作系统会保存当前进程的状态,并加载即将运行的进程的状态,这就是上下文切换。
- 创建和终止:操作系统提供了创建和终止进程的机制。例如,在Unix或Linux系统中,可以使用fork()系统调用来创建新的进程,使用exit()或者kill()来终止进程。
- 同步和通信:操作系统提供了各种机制让进程能够进行同步和通信,如信号量、管道、消息队列、共享内存等。
- 内存管理:每个进程都有自己的独立地址空间,操作系统需要对其进行管理,包括分配、回收以及保护不被其他进程侵入。
说说Python中的多线程?
Python在任意时刻,只有一个线程在解释器中运行。
对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。 多线程共享主进程的资源,所以可能还会改变其中的变量,这个需加上线程锁,每次执行完一个线程再执行下一个线程。一个CPU在同一个时刻只能执行一个线程,但是当遇到IO操作或者运行一定的代码量的时候就会释放全局解释器锁,执行另外一个线程。
为什么要有GIL锁?
为了保证内存管理中引用计数的一致性。
GIL 的设计是为了解决 Python 的多线程环境中的同步问题。在 Python中,所有的数据都是全局的,如果多个线程同时访问和修改共享数据,可能导致不一致性和数据损坏。
GIL 的设计是为了避免这种情况,使得所有的线程在任意时刻只有一个线程在执行 Python 代码。这保证了在多线程环境中,所有数据的一致性。
说说Python中的多进程?
Python中的多进程是通过multiprocessing包来实现,每个进程中所有数据(包括全局变量)都各自拥有一份,互不影响。它可以利用Process类来创建一个进程对象。
进程对象的方法和线程对象的方法差不多,也有start(), run(),join()等,但是,Thread线程对象中的守护线程方法是setDeamon,而Process进程对象的守护进程是通过设置daemon属性来完成。
说说Python互斥锁与死锁?
互斥锁:即确保某段关键代码的数据只能又一个线程从头到尾完整执行,保证了这段代码数据的安全性,但是这样就会导致死锁。
死锁:多个子线程在等待对方解除占用状态,但是都不先解锁,互相等待,这就是死锁。
怎么避免僵尸进程?
僵尸进程是指已经完成执行并终止,但父进程还未对其进行清理的进程。这些进程在进程表中仍然占用条目,因为它们还有一个退出状态需要父进程读取。
使用wait()函数:当子进程结束后,父进程可以通过调用wait()或waitpid()函数来获取子进程的退出状态,从而使子进程不再为僵尸进程。这个函数会导致父进程阻塞,直到一个子进程结束。
python中使用join()函数
说说Python的深拷贝与浅拷贝?
不可变对象:一旦创建就不可修改的对象,包括字符串、元组、数值类型。
可变对象:可以修改的对象,包括列表、字典、集合(该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的地址,通俗点说就是原地改变。)
当对象的值发生变化,但内存地址没有改变时,则说明是可变类型
当对象的值发生变化,内存地址也发生改变时,则说明是不可变类型
浅拷贝:创建新对象,其内容是原对象的引用。
深拷贝:和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。
浅拷贝之所以称为浅拷贝,是它仅仅只拷贝了一层,拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已,在lst中有一个嵌套的list[3,4],如果修改了它,情况就不一样了。
当浅拷贝的值是可变对象(列表、字典、集合)时会产生一个“不是那么独立的对象”存在。有两种情况:
第一种情况:复制的对象中无复杂子对象[1,2,3,4,5,6],原来值的改变并不会影响浅复制的值,同时浅复制的值改变也并不会影响原来的值。原来值的id值与浅复制原来的值不同。
第二种情况:复制的对象中有复杂子对象(例如列表中的一个子元素是一个列表[1,2,3,[4,5],6]),如果不改变其中复杂子对象,浅复制的值改变并不会影响原来的值。但是改变原来的值中的复杂子对象的值会影响浅复制的值。
说说Python中yeild和return的区别?
共同点:return和yield都用来返回值;在一次性地返回所有值场景中return和yield的作用是一样的。
不同点:如果要返回的数据是通过for等循环生成的迭代器类型数据(如列表、元组),return只能在循环外部一次性地返回,yeild则可以在循环内部逐个元素返回(yield函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行)
说说Python中set的底层实现?
散列表/哈希表。set只是默认键和值是相同的
注:散列表是根据关键字而直接进行访问值的数据结构。也就是说散列表建立了关键字和存储地址之间的一种直接映射关系。
# 使用set去重
my_list = [1, 2, 2, 3, 3, 4, 5, 5]
my_list = list(set(my_list))
print(my_list)
说说Python中字典与set区别?
1.字典是一系列无序的键值对的组合;集合set()里的元素默认键值是一样的,是单一的一个元素。
2.从python3.6后,字典有序;集合无序
3.字典键不能重复;集合set()元素不能重复
说说Python中__init__和__new__和__call__的区别?
__init__是初始化方法
__new__实例化对象
__call__允许一个类的实例像函数一样被调用。实质上说,这意味着 x() 与 x.call() 是相同
构造方法=创建对象+初始化对象=new+init
__new__方法是在实例创建之前被调用,是一个静态方法,主要的功能就是创建一个类的实例并返回
__init__方法是在实例创建之后被调用,主要的功能是设置实例的一些属性初始值
实际测试:__new__在__init__之前被调用,__new__的返回值(实例)将传递给__init__方法的第一个参数(self),然后__init__给这个实例(self)设置一些参数
说说Python 中的is和==区别?
is比较的是两个对象的id值是否相等,也就是比较两个对象是否为同一个实例对象,是否指向同一个内存地址。
==比较的是两个对象的内容是否相等,默认会调用对象的eq()方法。
说说函数调用参数的传递方式是值传递还是引用传递?
Python的参数传递有:位置参数、默认参数、可变参数、关键字参数。 函数的传值到底是值传递还是引用传递、要分情况:
不可变参数用值传递:像整数和字符串这样的不可变对象,是通过拷贝进行传递的,因为你无论如何都不可能在原处改变不可变对象。
可变参数是引用传递:比如像列表,字典这样的对象是通过引用传递、和C语言里面的用指针传递数组很相似,可变对象能在函数内部改变。
说说对缺省参数的理解?
缺省参数指在调用函数的时候没有传入参数的情况下,调用默认的参数,在调用函数的同时赋值时,所传入的参数会替代默认参数。
*args是不定长参数,它可以表示输入参数是不确定的,可以是任意多个。它将所有传入的位置参数封装为一个元组
**kwargs是关键字参数,赋值的时候是以键值对的方式,参数可以是任意多对在定义函数的时候
def foo(*args):
print(args)
foo(1, 2, 3) # (1, 2, 3)
foo('a', 'b', 'c')
def bar(**kwargs):
print(kwargs)
bar(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3}
bar(name='Alice', age=18) # {'name': 'Alice', 'age': 18}
说说Python中什么元素为假?
0,空字符串、空列表、空字典、空元组、None、False
说说lambda函数?
在Python中,lambda函数是一种匿名函数,它可以在需要时动态创建,通常用于一次性的、简单的函数调用。
# 使用lambda函数进行列表排序
lst = [(1, 3), (2, 2), (3, 1)]
lst.sort(key=lambda x: x[1])
print(lst) # [(3, 1), (2, 2), (1, 3)]
# 使用lambda函数过滤列表
lst = [1, 2, 3, 4, 5, 6]
result = list(filter(lambda x: x % 2 == 0, lst))
print(result) # [2, 4, 6]
# 使用lambda函数进行列表映射
lst = [1, 2, 3, 4, 5]
result = list(map(lambda x: x * 2, lst))
print(result) # [2, 4, 6, 8, 10]
add = lambda x, y: x + y
result = add(3, 5)
print(result) # 8
说说垃圾回收机制
Python采用了自动垃圾回收机制,它可以自动检测和回收不再使用的对象,从而释放内存空间。
Python的垃圾回收机制基于引用计数,即每个对象都有一个引用计数器,记录当前有多少个引用指向这个对象。当一个对象的引用计数器变为0时,即没有任何引用指向它时,这个对象就变成了垃圾,可以被回收。Python会定期检查所有的对象,将引用计数器为0的对象回收。
此外,Python还采用了循环引用垃圾回收机制,以解决对象之间存在循环引用的问题。当两个或多个对象之间互相引用,但没有其他引用指向它们时,这些对象就成了不可达对象。Python的垃圾回收机制会检测这些不可达对象,并将它们回收。
Python的垃圾回收机制还包括了分代回收机制,即将对象分为不同的代,每个代使用不同的垃圾回收策略。新创建的对象通常被分配在0代,随着时间的推移,如果它们仍然存在,它们将被移到更高的代,最终在某一代中被回收。
迭代器和生成器
迭代器:
- 迭代器,英文 Iterator,它首先是个对象,其次它是访问可迭代序列(Iterable)的一种方式。通常其从序列的第一个元素开始访问,直到所有的元素都被访问才结束。
- 迭代器又是一个特殊的对象,特殊在于它必须实现两个方法: __ iter__和__next__.
迭代器几个特点
- 是有去无回的,迭代器只能前进不能回退!也就是说一旦迭代结束,要想再使用此迭代器对象从头开始遍历元素,将是不可行的!
- 迭代器无需提前知道整个列表的所有元素,
- 无需加载所有元素到RAM中尽而它是节省内存的(memory-efficient).
生成器:
- 生成器对象(generator object)一定也是迭代器对象(Iterator object)创建生成器的几种方法
- 生成器:使用了 yield 的函数被称为生成器(generator),在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next()
方法时从当前位置继续运行。调用一个生成器函数,返回的是一个迭代器对象。如果数据集有某种逻辑,就不必存储在一个列表中,只需编写一个生成器,它将在需要时生成这些值,基本不占用内存。
带参装饰器和非带参装饰器:
装饰器在Python中是一个非常强大的功能,可以用来修改或增强函数的行为。带参数和不带参数的装饰器都有共同的目标,但它们的实现方式和使用场景有所不同。它的价值在于为原函数f增加一些行为,前提必须不能破坏函数f,所以肯定不能改变f的内部结构,所以只能在调用f前后定义一些行为。
不带参数的装饰器:
这是最基础的装饰器形式,它接收一个函数,并返回一个新的函数。
def my_decorator(func):
def wrapper():
print("Something before the function call")
func()
print("Something after the function call")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
带参数的装饰器:
如果你想让装饰器接受自定义的参数,那么你需要在编写装饰器时多加一层嵌套。
def decorator_with_args(dec_arg1, dec_arg2):
def actual_decorator(func):
def wrapper(*args, **kwargs):
print(f"Decorator argument 1 = {dec_arg1}")
print(f"Decorator argument 2 = {dec_arg2}")
func(*args, **kwargs)
return wrapper
return actual_decorator
@decorator_with_args("arg1", "arg2")
def say_hello(name):
print(f"Hello, {name}")
在这个例子中,装饰器decorator_with_args接受两个参数,并在调用被装饰的函数之前打印这些参数。这使得装饰器的行为可以更容易地进行自定义。
总结起来,不带参数的装饰器是一种简单的方式,用于在不改变原始函数代码的情况下添加一些通用的功能,比如日志或计时等;而带参数的装饰器则提供了更高的灵活性,允许我们定制装饰器的行为。
docker怎么实现隔离?
Docker 使用 Linux 内核的特性来提供其容器的隔离。以下是 Docker 如何使用这些特性进行内存等资源的隔离的一般概述:
1. cgroups(控制组):Docker 使用 Linux 的 cgroups 功能来限制和隔离资源,包括 CPU、内存、磁盘 I/O 等。cgroups 允许Docker 对每一个容器分配可用资源的子集。
例如,对于内存,你可以使用 --memory参数在运行容器时设置内存限制。当容器达到这个限制时,它不会被允许使用更多的内存。这样可以防止一个容器耗尽系统的所有内存,影响其他容器或主机系统。docker run -it --memory=2g ubuntu:18.04
2. namespaces (命名空间):Docker 使用 Linux 的命名空间功能来提供工作区的隔离。不同的命名空间关注不同的隔离方面,例如 PID 命名空间隔离进程ID,NET 命名空间隔离网络接口,MNT 命名空间隔离挂载点等。
每个容器运行在各自独立的命名空间下,从而实现相互隔离。例如,每个容器都有自己的网络栈,所以每个容器都可以有自己的 IP地址、路由规则等,并且不会影响其它容器或者主机。
通过这两大机制,Docker 保证了每个容器都在独立安全的环境中运行,仿佛它们各自运行在自己的操作系统中,确保了资源的有效隔离和管理。