计算机基础
# 计算机组成原理
# 冯诺依曼计算机模型
# 计算机五大核心
控制器(Control)
运算器(Datapath)
存储器(Memory)
输入(Input system)
输出(Output system)
# CPU指令结构
控制单元
运算单元
数据(存储)单元
# CPU缓存结构
CPU读取存储器数据过程
CPU为何要有高速缓存
- 内存和硬盘的发展速度远远不及 CPU
- 空间局部性 、时间局部性
带有高速缓存的CPU执行计算的流程
# CPU运行安全等级
# CPU缓存架构
CPU为何要有高速缓存
原因
- 时间局部性(Temporal Locality)
- 空间局部性(Spatial Locality)
带有高速缓存的CPU执行计算的流程
- 程序以及数据被加载到主内存
- 指令和数据被加载到CPU的高速缓存
- CPU执行指令,把结果写到高速缓存
- 高速缓存中的数据写回主内存
目前流行的多级缓存结构
- 3级缓存
# CPU缓存一致性协议详解
# CPU高速缓存(Cache Memory)
# CPU为何要有高速缓存
- 时间局部性(Temporal Locality)
- 空间局部性(Spatial Locality)
- 内存和硬盘的发展速度远远不及 CPU,不能依赖他们就只能依赖自己的缓存了。
# 带有高速缓存的CPU执行计算的流程
- 程序以及数据被加载到主内存
- 指令和数据被加载到CPU的高速缓存
- CPU执行指令
- 再把结果写到高速缓存
- 高速缓存中的数据写回主内存
# 目前流行的多级缓存结构
- 3级缓存
- 2级缓存
# 多核CPU多级缓存一致性协议MESI
多核CPU的情况下有多个一级缓存,如何保证缓存内部数据的一致,不让系统数据混乱。
# MESI协议缓存状态
- M 修改 (Modified)
- E 独享、互斥 (Exclusive)
- S 共享 (Shared)
- I 无效 (Invalid)
# MESI状态转换
- E -> S -> I
- S -> M
# 多核缓存协同操作
同一时刻交给总线裁决
# 单核读取
独享
# 双核读取
A/B都是S状态(共享)
# 修改数据
A为M,B为I
# 同步数据
- A从 M -> E -> S
- B 从 I -> S
- 循环上面的读取操作
# 缓存行伪共享
如果需要修改“共享同一个缓存行的变量” (64byte),就会无意中影响彼此的性能,这就是伪共享(False Sharing)
# 怎么解决伪共享 (opens new window)
- 手动填充.
- Java8中新增了一个注解:@sun.misc.Contended 加上这个注解的类会自动补齐缓存行,需要注意的是此注解默认是无效的,需要在jvm启动时设置 -XX:-RestrictContended 才会生效。
# MESI优化和他们引入的问题(只能用其他更好的协议去优化)
CPU切换状态阻塞解决-存储缓存(Store Bufferes)
修改本地缓存中的一条信息,那么你必须将I(无效)状态通知到其他拥有该缓存数据的CPU缓存中,并且等待确认。等待确认的过程会阻塞处理器,这会降低处理器的性能。
Store Bufferes
- 处理器把它想要写入到主存的值写到缓存,然后继续去处理其他事情。当所有失效确认(Invalidate Acknowledge)都接收到时,数据才会最终被提交。
Store Bufferes的风险
- 就是处理器会尝试从存储缓存中读取值,但它还没有进行提交
- 保存什么时候会完成,这个并没有任何保证
硬件内存模型
缓存的一致性消息传递是要时间的,这就使其切换时会产生延迟。当一个缓存被切换状态时其他缓存收到消息完成各自的切换并且发出回应消息这么一长串的时间中CPU都会等待所有缓存响应完成。可能出现的阻塞都会导致各种各样的性能问题和稳定性问题。
# 操作系统
# 执行空间保护
内核线程模型(KLT)
用户线程模型(ULT)
# 进程
- 栈指令集架构
- 寄存器指令集架构
# 进程间通信的方式
- 管道(pipe)及有名管道(named pipe):管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
- 信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。
- 消息队列(message queue):消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。
- 共享内存(shared memory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。
- 信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。
- 套接字(socket):这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。
# 线程
# 进程与线程的区别
进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
进程拥有共享的资源,如内存空间等,供其内部的线程共享
进程间通信较为复杂
- 同一台计算机的进程通信称为 IPC(Inter-process communication)
- 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
线程更轻量,线程上下文切换成本一般上要比进程上下文切换低
# 线程间通信协议 : 管程
- 管程是指管理共享变量以及对共享变量操作的过程,让它们支持并发。
- 在管程的发展史上,先后出现过三种不同的管程模型,分别是Hasen模型、Hoare模型和MESA模型。现在正在广泛使用的是MESA模型。
- MESA管程模型, 也是java中的synchronized和AQS的设计思想。
# 线程的同步互斥
线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。
线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。
四种线程同步互斥的控制方法
- 临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。(在一段时间内只允许一个线程访问的资源就称为临界资源)。
- 互斥量:为协调共同对一个共享资源的单独访问而设计的。
- 信号量:为控制一个具有有限数量用户资源而设计。
- 事件:用来通知线程有一些事件已发生,从而启动后继任务的开始。
# 操作系统层面线程生命周期
操作系统层面的线程生命周期基本上可以用下图这个“五态模型”来描述。这五态分别:
初始状态,指的是线程已经被创建,但是还不允许分配 CPU 执行。这个状态属于编程语言特有的,不过这里所谓的被创建,仅仅是在编程语言层面被创建,而在操作系统层面,真正的线程还没有创建。
可运行状态,指的是线程可以分配 CPU 执行。在这种状态下,真正的操作系统线程已经被成功创建了,所以可以分配 CPU 执行。
运行状态: 当有空闲的 CPU 时,操作系统会将其分配给一个处于可运行状态的线程,被分配到 CPU 的线程的状态就转换成了运行状态。
休眠状态: 运行状态的线程如果调用一个阻塞的 API(例如以阻塞方式读文件)或者等待某个事件(例如条件变量),那么线程的状态就会转换到休眠状态,同时释放 CPU 使用权,休眠状态的线程永远没有机会获得 CPU 使用权。当等待的事件出现了,线程就会从休眠状态转换到可运行状态。
终止状态: 线程执行完或者出现异常就会进入终止状态,终止状态的线程不会切换到其他任何状态,进入终止状态也就意味着线程的生命周期结束了。
这五种状态在不同编程语言里会有简化合并。Java 语言里则把可运行状态和运行状态合并了,这两个状态在操作系统调度层面有用,而 JVM 层面不关心这两个状态,因为 JVM 把线程调度交给操作系统处理了。
# 查看进程线程的方法
# windows
- 任务管理器可以查看进程和线程数,也可以用来杀死进程
- tasklist 查看进程
- taskkill 杀死进程
# linux
- ps -fe 查看所有进程
- ps -fT -p 查看某个进程(PID)的所有线程
- kill 杀死进程
- top 按大写 H 切换是否显示线程
- top -H -p 查看某个进程(PID)的所有线程
# Java
- jps 命令查看所有 Java 进程
- jstack 查看某个 Java 进程(PID)的所有线程状态
- jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)
# 上下文切换
上下文切换是指CPU(中央处理单元)从一个进程或线程到另一个进程或线程的切换。
上下文切换只能在内核模式下发生。内核模式是CPU的特权模式,其中只有内核运行,并提供对所有内存位置和所有其他系统资源的访问。其他程序(包括应用程序)最初在用户模式下运行,但它们可以通过系统调用运行部分内核代码。
linux系统 相关指令 :
vmstat 1
pidstat
# 协程(纤程)
用户级线程ULT
协程的特点在于是一个线程执行,那和多线程比,协程有何优势?
线程的切换由操作系统调度,协程由用户自己进行调度,因此减少了上下文切换,提高了效率。
线程的默认stack大小是1M,而协程更轻量,接近1k。因此可以在相同的内存中开启更多的协程。
不需要多线程的锁机制:因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
注意: 协程适用于被阻塞的,且需要大量并发的场景(网络io)。不适合大量计算的场景。
# 字节问题
bit字 就是一个2进制位; 一字节 byte = 8 bit; 字节是基础单位
int 占4字节 ; 4 * 8 = 32位 ; 一个符号位标识 所以是31 ; 因为有个0 所以正数还得减一。