赞
踩
1.进程与线程区别
进程和线程是操作系统中的两个核心概念,它们有以下区别:
2.线程同步的方式:互斥锁、自旋锁、读写锁、条件变量
线程同步是指多个线程之间按照一定的顺序和规则共享资源或进行合作的过程。以下是常见的线程同步方式:
3.互斥锁与自旋锁的底层区别
互斥锁和自旋锁都是用来保护共享资源的锁,但它们的底层实现方式有所不同。
互斥锁是一种阻塞锁,当有线程占用锁的时候,其他线程需要等待,直到原来的线程释放锁以后才能获得锁。在Linux中,互斥锁的实现使用了futex(fast userspace mutex)系统调用,当线程需要获取锁时,如果锁没有被占用,线程会立即获得锁;如果锁已经被占用,线程会将自己加入到等待队列中,并且调用futex系统调用将自己设置为休眠状态,直到其他线程释放锁并唤醒它。
自旋锁是一种忙等锁,当有线程占用锁的时候,其他线程会不停地尝获取锁,直到锁被释放。在Linux中,自旋锁的实现使用了原子操作指令(如CAS、XCHG等),当线程需要获取锁时,它会不停地尝试修改锁的状态,直到成功修改为锁可用。
因为自旋锁不涉及线程的上下文切换,所以它的效率比互斥锁更高。但是,如果锁的占用时间较长,自旋锁会一直占用CPU资源,造成浪费。而互斥锁在占用时间较长时会将等待的线程挂起,等到锁被释放后再唤醒线程,相对于自旋锁有更好的表现。
另外,自旋锁只适用于多核处理器,因为在单核处理器上自旋等待会导致其他线程不能运行,造成死锁。而互斥锁则可以在单核或多核处理器上使用。
4.孤儿进程与僵尸进程
孤儿进程和僵尸进程是两种不同的进程状态。
孤儿进程是指其父进程已经退出或被终止,而子进程还在运行的进程。孤儿进程由init进程(pid为1的进程)接管并称为init进程的子进程。init进程会定期扫描系统中的孤儿进程并将其回收资源,以防止资源泄露。
僵尸进程是指已经但是父进程尚未回收其资源的进程。在进程结束时,内核会保存一段时间进程的一些状态信息以便父进程在以后的某个时间点获取这些信息(例如进程的退出状态码)。这段时间被称为僵尸状态。如果父进程没有主动回收这些资源,这些进程就会一直处于僵尸状态,占用系统资源。可以通过调用waitpid()来回收子进程资源,避免出现僵尸进程。
总的来说,孤儿进程和僵尸进程都会占用系统资源,并有可能导致系统崩溃,因此需要及时处理。处理孤儿进程可以通过定期扫描系统并回收资源来避免资源泄露,处理僵尸进程可以通过回收资源来避免系统资源的浪费。
5.死锁及避免
死锁是指两个或多个进程互相等待对方释放资源,导致所有进程都无法继续运行的状态。要避免死锁,可以考虑以下几点:
6.多线程与多进程比较
多线程和多进程都是提高程序运行效率的方法,但它们之间也存在一些差异。
7.进程间通信:PIPE、FIFO、消息队列、信号量、共享内存、socket
进程间通信(IPC,Inter-Process Communication)是指两个或多个进程之间进行数据交换或资源共享的过程。在Unix/Linux操作系统中,经典的进程间通信方式包括PIPE、FIFO、消息队列、信号量、共享内存和socket等方式。
8.管道与消息队列对比
管道和消息队列都是进程间通信机制,但二者也存在着一些不同点。
9.fork进程的底层:读时共享,写时复制
在Linux操作系统中,当进程调用fork()系统调用创建一个子进程时,该子进程会被复制出一个完整的进程实体,包括了该进程的代码段、数据段、进程堆栈、文件描述符表、信号处理程序等,但是这些数据结构并不是简单地被完全复制的,其实是通过一种“读时共享、写时复制”的技术进行处理的。具体来讲:
10.线程上下文切换的流程
线程上下文切换是指从一个线程切换到另一个线程执行的过程。下面是一般情况下线程上下文切换的流程:
11.进程的调度算法
进程调度算法是操作系统中用于决定哪个进程可执行的一种策略。常用的进程调度算法有以下几种:
12.阻塞IO与非阻塞IO
阻塞 I/O 和非阻塞 I/O 是指 I/O 操作的两种不同模式:
13.同步与异步的概念
同步和异步指的是程序或系统对任务处理方式的不同方法。
14.静态链接与动态链接的过程
静态链接和动态链接是将多个目标文件或者库文件连接成可执行文件的两种方式。
15.虚拟内存概念(非常重要)
虚拟内存是一种操作系统技术,它将内存的物理地址和逻辑地址分开管理,让应用程序认为自己独享整个计算机的内存,从而实现更高效、更安全、更灵活的内存管理。
虚拟内存的原理是,在应用程序和物理内存之间增加了一个软件层,称为虚拟内存管理器。虚拟内存管理器将内存空间分割成一些固定大小的页面(page),并在应用程序和物理内存之间建立一个虚拟到物理的地址映射表。当应用程序需要访问内存时,它只需要向虚拟内存管理器请求访问一个地址,虚拟内存管理器会将其转换为物理地址,并将所需的页面加载到内存中。
虚拟内存的优点包括:
16.MMU地址翻译的具体流程
MMU(Memory Management Unit)是一种硬件设备,用于将应用程序所使用的逻辑地址翻译为物理地址。在现代计算机系统中,所有的内存访问都经过MMU进行地址翻译和权限检查,以保护系统的安全性和稳定性。
MMU的地址翻译流程如下:
17.缺页处理过程
缺页(Page Fault)是指访问一个没有分配物理内存的虚拟地址,这时操作系统需要将该页面加载到内存中。缺页处理是一种重要的内存管理技术,其主要流程如下:
18.缺页置换算法:最久未使用算法、先进先出算法、最佳置换算法
缺页置换算法是操作系统在缺页异常时选择要置换的页面的一种策略,三种常见的算法有:
19.IO多路复用:select、poll、epoll的区别(非常重要,几乎必问,回答得越底层越好,要会使用)
IO多路复用是指通过一种机制,使单个进程可以监视多个文件描述符(Socket、文件等)的可读可写状态,从而实现对多个IO操作的同时监听。主要有以下三种实现方式:
20.手撕一个最简单的server端服务器(socket、bind、listen、accept这四个API一定要非常熟练)
import socket
def main():
# 创建socket对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定ip和端口号
server_socket.bind(('127.0.0.1', 8000))
# 监听端口号
server_socket.listen(128)
while True:
# 接受客户端socket连接请求,并返回新的socket对象及客户端的地址
client_socket, client_addr = server_socket.accept()
# 从客户端socket中读取数据
recv_data = client_socket.recv(1024)
print(recv_data.decode('gbk'))
# 向客户端socket发送数据
client_socket.send('Welcome to the server!'.encode('gbk'))
# 关闭客户端socket连接
client.close()
if __name__ == '__main__':
main()
步骤如下:
这是一个基本的Server服务器,没有做异常处理和多线程等复杂操作,仅用于演示简单的socket编程过程。
21.线程池
线程池是一种多线程处理方式,它包含了两个核心部分:一是线程池管理器,二是线程池。线程池管理器负责生成、启动和销毁线程池,线程池则负责管理、调度和执行线程任务。
线程池的主要优点是避免了线程的频繁创建和销毁,因为此过程是需要消耗比较多的系统资源的。相反,线程池中的线程可以被重复利用,从而可以更好地管理线程,并减少系统开销。
线程池的主要工作流程如下:
22.基于事件驱动的reactor模式
基于事件驱动的reactor模式是一种网络编程模式,通常用于高性能、低延迟、大并发的网络通信中。这种模式的核心是将I/O(读写)操作抽象为事件,由事件驱动框架触发事件并调用相应的处理函数。
其中reactor是指事件驱动的核心框架,其主要功能是监听文件描述符(如socket),当文件描述符可读或可写时,reactor会回调预先注册的Handler将处理方法挂在事件循环中。事件循环主要负责响应事件并执行相应的回调,以完成I/O操作。
在reactor模式中,每个通信连接都会对应一个Handler对象,负责处理该连接的读写事件,并将事件添加到事件循环中。事件循环通过轮询所有注册的事件,来执行相应的I/O操作,以及检查新连接的到来,从而实现高效的网络通信。
reactor模式的优点是高性能、高并发、低延迟和易于扩展,尤其在网络编程中,可以极大地提升系统性能。在实际应用中,比较出名的reactor编程框架有Twisted、Tornado、Netty等。
23.边沿触发与水平触发的区别
在事件驱动的编程模型中,边沿触发和水平触发是两种常用的I/O事件触发方式。
边沿触发:
在边沿触发(Edge-Triggered)机制中,当一个I/O事件发生时,系统会通过相应的回调函数通知用户。此时用户需要尽可能地处理事件,并检查是否还有剩余数据。如果有剩余数据需要处理,则需要挂起该事件,等待下一个边沿触发时再次处理。
边沿触发的特点是:只有在状态发生改变(如有新数据到来)时才会触发回调,对于大量的事件处理效率更高。
水平触发:
在水平触发(Level-Triggered)机制中,当一个I/O事件发生时,系统会反复地通知用户,直到用户处理完全部数据。这种机制要求用户需要不断地处理事件。如果未处理完数据,则沿用之前的事件处理方式,重复通知用户处理该事件。
水平触发的特点是:只要数据可读或可写,就会不断触发回调,需要用户不断进行处理。
总的来说,边沿触发相对于水平触发,更加高效、更加节省系统资源,但是也需要更加细致的设计和编程,以保证所有事件都能得到处理。而水平触发则相对简单,但在处理大量数据的情况下,可能会对系统性能产生较大的影响。
24.数据存储引擎:InnoDB、myISAM、Memory
InnoDB、MyISAM和Memory都是MySQL的存储引擎,分别有不同的特点和优势。
InnoDB是MySQL的一个事务安全的存储引擎,支持ACID事务和行级锁,并发能力较强,适合处理大规模的数据和高并发的读和写。同时,InnoDB对于数据的完整性有一定的保障,通过支持外键约束和回滚日志(redo log和undo log),可以保证即使出现了异常情况,也能够进行回滚恢复,因此在数据层面的可靠性较高。但是,InnoDB的写入效率相对不高,需要经常进行磁盘操作,且索引需要较多的存储空间。
MyISAM是MySQL的一个较为简单的存储引擎,适用于大量插入和查询的场景。MyISAM支持全文本索引,可以对文本数据进行高效的搜索,但不支持ACID事务和行级锁,对于数据的完整性和并发能力较差。因此,MyISAM通常用于不需要频繁写入和事务的应用,如日志记录等。
Memory是MySQL的一个内存存储引擎,数据都存储在内存中,因此读取速度较快且对于高并发读写也具有较好的性能。Memory支持hash索引,但不支持B+树索引,因此在查询方面有一些限制。同时,Memory对于数据的完没有MyISAM和InnoDB那么强,不能保证数据持久性,而缓存控制和内存使用也需要进行一定的调整。
选择合适的存储引擎需要考虑到具体的应用场景和要求,包括数据容量、读写频率、事务和索引等方面的要求和限制。
25.数据库索引类型及原理:B+树索引、哈希表索引
数据库索引是一种用于提高数据库查询效率的数据结构。常见的数据库索引类型有B+树索引和哈希表索引。
B+树索引是一种基于B树的索引结构,通常用于查询范围较广的数据。它的原理是将数据按照顺序存放在节点中,通过二分法快速查找所需数据。B+树索引的最底层节点保存实际数据,而非叶子节点只存放指针,这样可以很快地找到对应的数据,同时也避免了非叶子节点的频繁读写操作。B+树索引还具有良好的可扩展性,可以支持海量数据的存储和处理。
哈希表索引是利用哈希算法将关键字映射到表中的一个位置,以加快查找的速度。哈希表的原理是将关键字与记录的地址进行映射,然后将映射结果作为索引访问表格。哈希表索引具有快速查找数据的优势,适用于查询单个记录时。但是,哈希表索引不支持范围查询,而且在数据量大时可能会出现哈希冲突,影响查询效率。因此,哈希表索引适用于存储相对较少的数据。
26.锁:悲观锁、乐观锁
锁是一种并发控制机制,用于保护共享资源的正确访问,防止多个线程同时读写共享资源导致数据不一致或产生其他问题。
悲观锁:
悲观锁指的是在执行操作之前,先加锁,后释放锁,即认为当前数据可能被其他线程修改,因此先将数据锁定,在执行操作完成后再释放锁并解除锁定。悲观锁的特点是:使用时锁定资源,效率较低,但是可以保证数据操作的安全性。
常见的悲观锁有互斥锁,在Java中就是synchronized关键字,还有ReentrantLock。
乐观锁:
乐观锁指的是先读取数据版本号,然后在执行操作时,重新读取数据的版本号,如果两次读取的数据版本号相同,则认为没有其他线程对数据进行修改,可以进行修改操作,否则视为操作失败,并且需要重试。乐观锁的特点是:不使用锁,效率较高,但是需要保证所有操作都是并发安全的。
常见的乐观锁有AtomicInteger、CAS机制、版本号机制等。
总的来说,悲观锁和乐观锁都有各自的优点与缺点,具体使用哪种锁应该根据具体情况选择。在Java 8中,对于一些并发操作,如CAS、原子操作,语言本身已经提供了丰富的支持,可以大大简化编程工作。
27.事务:事务的四大特性(ACID)、事务并发的三大问题、事务隔离级别及实现原理
事务是指由一系列要么全部执行,要么全部不执行的操作所组成的逻辑处理单元,是保证数据一致性的重要手段。事务通常具有四大特性(ACID):原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。
事务并发的三大问题是:脏读、不可重复读和幻读。脏读指的是未提交的数据被其他事务读取,而这些数据可能随时会被回滚;不可重复读指的是在同一事务中,读取同一数据多次,但由于并发操作导致数据被修改,结果读取的结果不同;幻读指的是在某个时间段内,一个事务多次读取同一数据,但其他事务不断地插入新行,导致第一个事务看到的行数不同。
为解决以上并发问题,事务通过设置隔离级别来实现。事务隔离级别分为四种:read uncommitted(未提交读)、read committed(提交读)、repeatable read(可重复读)和serializable(串行化)。其实现原理主要是通过锁机制来实现隔离,通常有两种锁:共享锁和排他锁。
在实现事务隔离时,通常会引入锁的机制,来限制对共享资源的并发访问,从而保证所定义的隔离级别的正确性。在多数数据库中,读锁和写锁均采用“共享-排他锁(Shared Lock-Exclusive Lock)”机制来实现。同时,对于一些特殊的情况,如死锁、饥饿等,需要通过特定的方法来进行监测和处理,以保证事务能够安全、正确地执行。
28.多版本并发控制实现机制(MCVV)原理
多版本并发控制(MVCC)是一种保证事务隔离性的并发控制机制。MVCC 把每次修改的数据都保存下来,形成对应的版本号,每个事务在启动时会读取一个版本号号段,以保证这个事务内所需读取的所有数据都是同一时间点上的一个快照。
MVCC 返回的快照数据都是一致性的,但其实现方式和其他并发控制机制是有很大不同的。在 MVCC 中,每个事务都可以看到不同的数据版本,而不是最新的数据。在读取一个数据时,事务会获取该数据的快照版本,并且在本次事务内都只能访问到这个快照版本的数据。如果需要修改数据,MVCC 还会把修改后的数据保存为另一个版本,而不是立刻覆盖原来的版本。这样就可以达到多版本并行操作的目的。
MVCC 的实现方式通常是基于行级别的锁定机制,每次读取数据时,会将当前事务的版本号与读取到的数据的版本号进行对比,如果当前事务的版本号比读取到的数据的版本号旧,那么就需要重新读取数据,直至获取到当前事务的最新版本,从而保证读取数据的一致性。
MVCC 实现机制的优点包括:并发能力强、读写操作互不干扰、无需阻塞和等待。但也需要支付额外的存储、查询代价等,实际应用中需要根据应用场景进行权衡。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。