面试宝典 | 不完全总结

本文仅对同作者本人专业相关的面试可能涉及到的问题进行不完全总结。

更新:2023 / 01 / 25



硬件


软件

概念

  1. 浅拷贝和深拷贝的区别?1

所谓浅拷贝就是对引用的拷贝,所谓深拷贝就是对对象的资源的拷贝。

修改不可变对象(str、tuple)需要开辟新的空间。
修改可变对象(list等)不需要开辟新的空间。

  • 浅拷贝是在另一块地址中创建一个新的变量或容器,但是容器内的元素的地址均是源对象的元素的地址的拷贝。也就是说新的容器中指向了旧的元素( 新瓶装旧酒 )。

以下面的示例为例,

a=['hello',[1,2,3]]
b=a[:]
print([id(x) for x in a])				# [4340491440, 4634841216]
print([id(x) for x in b])				# [4340491440, 4634841216]

a[0]='world'
a[1].append(4)
print(a)								# ['world', [1, 2, 3, 4]]
print([id(x) for x in a])				# [4340491888, 4634841216]
print(b)								# ['hello', [1, 2, 3, 4]]
print([id(x) for x in b])				# [4340491440, 4634841216]
  • 深拷贝是在另一块地址中创建一个新的变量或容器,同时容器内的元素的地址也是新开辟的,仅仅是值相同而已,是完全的副本。也就是说( 新瓶装新酒 )。

以下面的示例为例,

from copy import deepcopy
a=['hello',[1,2,3]]
b=deepcopy(a)
print([id(x) for x in a])      			# [4345123952, 4657140160]
print([id(x) for x in b])      			# [4345123952, 4657471936]

a[0]='world'
a[1].append(4)
print(a)                      			 # ['world', [1, 2, 3, 4]]
print([id(x) for x in a])     			 # [4345124272, 4657140160]
print(b)                      			 # ['hello', [1, 2, 3]]
print([id(x) for x in b])     			 # [4345123952, 4657471936]

=======================================================================

  1. 面向对象编程和面向过程编程的区别?2
  • 面向过程Procedure Oriented Programming,简称 POP,如 C 语言):
    注重过程。
    当解决一个问题的时候,面向过程会把事情拆分成一个个函数和数据(用于方法的参数) 。然后按照一定的顺序,执行完这些方法(每个方法看作一个过程),等方法执行完了,事情就搞定了。

  • 面向对象( Object Oriented Programming,简称 OOP,如 C++JAVA 等语言):
    注重对象。
    当解决一个问题的时候,面向对象会把事物抽象成对象的概念,就是说这个问题里面有哪些对象,然后给对象赋一些属性和方法,然后让每个对象去执行自己的方法,问题得到解决

以具体的实例来解释二者的区别,即蛋炒饭和盖浇饭:

  • 用面向过程的方法写出来的程序是一份蛋炒饭。米饭和蛋混匀。
    蛋炒饭的好处就是入味均匀,吃起来香。
    但如果恰巧你不爱吃鸡蛋,只爱吃青菜的话,那么唯一的办法就是全部倒掉,重新做一份青菜炒饭了。
  • 用面向对象写出来的程序是一份盖浇饭。浇头和米饭可任意搭配,比如要一份红烧肉盖饭就在一份米饭上浇红烧肉;在一份青椒土豆盖浇饭,就浇一份青椒土豆。
    盖浇饭的更替相对简单,只需要把上面的盖菜拨掉,更换一份盖菜就可以了。
    盖浇饭的缺点是入味不均,可能没有蛋炒饭那么香。

到底是蛋炒饭好还是盖浇饭好呢?——

盖浇饭的好处就是 分离,耦合度( Coupling )低,可维护性( maintainability )高。饭不满意就换饭,菜不满意换菜。

蛋炒饭将 搅和在一起,想换 中任何一种都很困难,耦合度很高,以至于 可维护性 比较差。

总结来说:

  • 面向过程
    优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux / Unix 等一般采用面向过程开发,性能是最重要的因素。
    缺点:没有面向对象易维护、易复用、易扩展。
  • 面向对象
    优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
    缺点:性能比面向过程低。

实现

多线使用场景 ?

  1. LockRlock
  • 多线程访问共享资源 3
    适用模型可套用排队上卫生间,谁拿住了 Lock 谁就拥有当前卫生间的使用权。各个线程排队。
    多线程应用中,某个资源被多个线程共享访问,线程在线程函数执行前通过使用 lock 独占该资源,直至执行完成后,释放该 Lock,则可确保排队变成单线程一个一个执行,即每次只有一个线程占用 Lock
    Lock 是阻塞其他线程对共享资源的访问,且同一线程只能 acquire 一次,如多于一次就出现了死锁,程序无法继续执行。
    为了保证线程对共享资源的独占,又避免死锁的出现,就有了 RLockRLock 允许在同一线程中被多次 acquire,线程对共享资源的释放需要把所有锁都release。
  1. Condition
  • 多线程同步共享状态的权限:
    适用模型可套用消费者-生产者模型。生产者生产足量生产资料后通知消费者可产生消费,消费者消费足量生产资料后通知生产者继续生产。
  1. semaphore
  • 多线程执行密集型任务:
    适用模型可套用排队看医生。一个科室有6个医生,则同一时间可以有6个病人看病。每1个病人看完病,对应的医生可以放1个新病人进来看病。
    对于密集型任务时,需通过信号量限制一个时间点内的线程数量。
  1. Event
  • 多线程实现某个标志性事件作为信号进行同步:
    适用模型可套用红绿灯-过马路,或者服务器连接、镜像部署。
    子线程在收到主线程发出的标志性事件作为信号前阻塞,在收到信号后开始正常工作。
  1. Timer
    适用模型可套用定时任务。
  2. Barrier
    适用模型可套用播放器:一个线程做播放器初始化工作(加载本地文件或者获取播放地址),一个线程获取视频画面,一个线程获取视频声音,只有当初始化工作完毕,视频画面获取完毕,视频声音获取完毕,播放器才会开始播放,其中任意一个线程没有完成,播放器会处于阻塞状态直到三个任务都完成。

开3个线程按照顺序打印ABC 10次?

  1. 先后开启 10 个线程。每个线程按先后打印列表中的 ABC 3个元素。
  • Rlock
import threading, logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s: %(levelname)s  %(message)s',
    datefmt='%Y-%m-%d %A %H:%M:%S'
)

def print(l, rlock):
    rlock.acquire()
    for i in l:
        logging.info(f"{threading.current_thread().name}: {i}")
    rlock.release()

if __name__ == "__main__":
    rlock = threading.RLock()
    content = ['A', 'B', 'C']
    for i in range(10):
        t = threading.Thread(name=f'Thread-{i}', target=print, args=(content, rlock))
        t.start()

效果图如下所示:

2023-01-26 Thursday 19:57:23: INFO  Thread-0: A
2023-01-26 Thursday 19:57:23: INFO  Thread-0: B
2023-01-26 Thursday 19:57:23: INFO  Thread-0: C
2023-01-26 Thursday 19:57:23: INFO  Thread-1: A
2023-01-26 Thursday 19:57:23: INFO  Thread-1: B
2023-01-26 Thursday 19:57:23: INFO  Thread-1: C
......
2023-01-26 Thursday 19:57:23: INFO  Thread-9: A
2023-01-26 Thursday 19:57:23: INFO  Thread-9: B
2023-01-26 Thursday 19:57:23: INFO  Thread-9: C
  1. 先后开启 3 个线程。

waitnotifynotifyAll 来一环套一环进行线程通信,从而按顺序循环打印 a b c
思路就是 3 个线程用同一把锁,刚开始,a 线程获取锁,打印 a,设置下一个打印 b,并同时唤醒 b c,这时候,b c 线程都阻塞等待,如果 c 抢到了锁,进入代码执行,由于不符合条件,会 wait(同时释放锁),直到 b 抢到锁,符合条件打印,如此,顺序执行下去 4

  • Condition

示例1: 以次数来控制条件

import threading
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s: %(levelname)s  %(message)s',
    datefmt='%Y-%m-%d %A %H:%M:%S'
)

global count

class printA(threading.Thread):

    def __init__(self, condition):
        super().__init__()
        self.setName(name='Thread-A')
        self.condition = condition

    def run(self):
        global count
        while count > 0:
            if condition.acquire():
                if count % 3 == 0:
                    logging.info(f"{threading.current_thread().name}: A")
                    count -= 1
                    condition.notifyAll()
                else:
                    condition.wait()
                condition.release()


class printB(threading.Thread):

    def __init__(self, condition):
        super().__init__()
        self.setName(name='Thread-B')
        self.condition = condition

    def run(self):
        global count
        while count > 0:
            if condition.acquire():
                if count % 3 == 2:
                    logging.info(f"{threading.current_thread().name}: B")
                    count -= 1
                    condition.notifyAll()
                else:
                    condition.wait()
                condition.release()


class printC(threading.Thread):

    def __init__(self, condition):
        super().__init__()
        self.setName(name='Thread-C')
        self.condition = condition

    def run(self):
        global count
        while count > 0:
            if condition.acquire():
                if count % 3 == 1:
                    logging.info(f"{threading.current_thread().name}: C")
                    count -= 1
                    condition.notifyAll()
                else:
                    condition.wait()
                condition.release()

if __name__ == "__main__":

    count = 30
    lock = threading.RLock()
    condition = threading.Condition(lock)
    t1 = printA(condition)
    t2 = printB(condition)
    t3 = printC(condition)

    t1.start()
    t2.start()
    t3.start()

效果如下所示:

2023-01-26 Thursday 20:43:59: INFO  Thread-A: A
2023-01-26 Thursday 20:43:59: INFO  Thread-B: B
2023-01-26 Thursday 20:43:59: INFO  Thread-C: C
2023-01-26 Thursday 20:43:59: INFO  Thread-A: A
2023-01-26 Thursday 20:43:59: INFO  Thread-B: B
2023-01-26 Thursday 20:43:59: INFO  Thread-C: C
......
2023-01-26 Thursday 20:43:59: INFO  Thread-A: A
2023-01-26 Thursday 20:43:59: INFO  Thread-B: B
2023-01-26 Thursday 20:43:59: INFO  Thread-C: C

示例2: 以内容来控制条件 5

import threading
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s: %(levelname)s  %(message)s',
    datefmt='%Y-%m-%d %A %H:%M:%S'
)

global count, content

class printA(threading.Thread):

    def __init__(self, condition):
        super().__init__()
        self.setName(name='Thread-A')
        self.condition = condition

    def run(self):
        global content
        for i in range(count):
            with condition:
                while content != 'A':
                    condition.wait()
                else:
                    logging.info(f"{threading.current_thread().name}: A")
                    content = 'B'
                    condition.notifyAll()


class printB(threading.Thread):

    def __init__(self, condition):
        super().__init__()
        self.setName(name='Thread-B')
        self.condition = condition

    def run(self):
        global content
        for i in range(count):
            with condition:
                while content != 'B':
                    condition.wait()
                logging.info(f"{threading.current_thread().name}: B")
                content = 'C'
                condition.notifyAll()


class printC(threading.Thread):

    def __init__(self, condition):
        super().__init__()
        self.setName(name='Thread-C')
        self.condition = condition

    def run(self):
        global content
        for i in range(count):
            with condition:
                while content != 'C':
                    condition.wait()
                logging.info(f"{threading.current_thread().name}: C")
                content = 'A'
                condition.notifyAll()

if __name__ == "__main__":

    count = 10
    content = 'A'
    lock = threading.RLock()
    condition = threading.Condition(lock)
    t1 = printA(condition)
    t2 = printB(condition)
    t3 = printC(condition)

    t1.start()
    t2.start()
    t3.start()

效果如下所示,

2023-01-26 Thursday 20:55:18: INFO  Thread-A: A
2023-01-26 Thursday 20:55:18: INFO  Thread-B: B
2023-01-26 Thursday 20:55:18: INFO  Thread-C: C
2023-01-26 Thursday 20:55:18: INFO  Thread-A: A
2023-01-26 Thursday 20:55:18: INFO  Thread-B: B
2023-01-26 Thursday 20:55:18: INFO  Thread-C: C
......
2023-01-26 Thursday 20:55:18: INFO  Thread-A: A
2023-01-26 Thursday 20:55:18: INFO  Thread-B: B
2023-01-26 Thursday 20:55:18: INFO  Thread-C: C

参考链接


  1. 谈谈python中的深拷贝和浅拷贝 ↩︎

  2. 2分钟让你明白什么是面向对象编程 ↩︎

  3. python中的RLock和Lock ↩︎

  4. 面试题:用三个线程按顺序循环打印 abc 三个字母,比如 abcabcabc ↩︎

  5. 开3个线程按照顺序打印ABC 10次 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值