内存(Memory)
- 只读数据段(RODATA/RDATE)
- 堆(Heap)
- 栈(Stack)
-
只读数据段
- 存放常量
-
Stack
- 是程序运行的基础
- 每当一个函数被调用时,一块连续的内存就会在栈顶被分配出来,这块内存被称为帧(frame).
- 自顶向下增长,出去入口帧(entry frame),就是main()对应的帧,随着main()函数一层层调用,栈会一层层扩展,调用结束,栈又会一层层回溯,把内存释放
- 调用过程中,一个新的帧会分配足够的空间存储寄存器的上下文
- 在函数里使用到的通用寄存器在栈保存一个副本,函数调用结束,通过副本,可以恢复出原本的寄存器的上下文
- 函数的局部变量也都会在帧分配的时候被预留出来
-
Heap
- 当我们需要动态大小的内存时,只能用堆
- 动态生命周期的内存也需要分配到堆上
- 堆上分配出来的每一块内存需要显式地释放,这就使内存有更加灵活的生命周期
数据什么时候放在栈上什么时候放在堆上?
- 在编译时,一切无法确定大小或者可以改变的数据,都无法安全地放在栈上,最好放在堆上
- 只要可能,我们应该把变量分配到栈上,这样可以达到更好的运行速度
- 栈上存放的数据是静态的,固定大小,固定生命周期,在编译期可以确定
- 堆上存放的数据是动态的,不固定大小,不固定生命周期,在编译期不能确定
数据
- 值
- 类型
- 指针
- 引用
- 值
- 类型是对值的区分,包含了值在内存中的
长度
,对齐以及值可以进行操作等信息 - 一个值是符合一个特定类型的数据的某个实体
- 值是无法脱离具体的类型讨论的
- 类型
- 原生类型(primitive type): 固定大小,可以分配在栈上
- 字符
- 整数
- 浮点数
- 布尔值
- 数组
- 元组
- 枚举
- 指针: 指针的使用限制少,如果没有正确的类型解引用一个指针,会引发各种内存问题,造成潜在安全漏洞
- 引用: 引用的解引用访问是受限的,它只能解引用到它引用数据的类型,不能用作它用
- 组合类型(composite type)
- 结构体: 多个类型组合在一起共同表达一个值的复杂数据结构
- 标签联合: 可以存储一组不同但固定的类型的类型中的某个类型的对象,具体是由标签决定
- 其他类型
- 函数
- 闭包
代码
- 函数: 对代码中重复行为的抽象
- 方法: 在类中或者对象中定义的函数,只为某种类型定义的函数,和对象的指针发生关联
- 闭包: 将函数或者说代码和其环境(自由变量)一起存储的一种数据结构。
- 接口: 将使用方和实现方隔离开来,使两者不直接有依赖关系,大大提高了复用性和扩展性
- 虚表: 存储多态接口方法的列表,辅助运行时代码执行,方便进行动态分派,是运行时多态的基础
面向接口
的设计师软件开发中的重要能力,当我们在运行期使用接口来引用具体类型的时候,代码就具备了运行时多态
的能力。
在运行时
,一旦使用了关于接口的引用
,变量原本的类型被抹去,我们无法单纯从一个指针分析出这个引用具备什么样的能力。
在生成这个引用的时候,我们需要构建胖指针
,除了指向数据本身,还需要指向一张涵盖这个接口所支持方法的列表。
这个列表就是我们熟知的虚表
(virtual table)
运行方式
- 并发: 是一种能力, 同时与多件事情打交道的能力(交替执行多件不同的事情)
- 并行: 是一种手段, 在多个
CPU Core
上执行同样的代码 - 同步: 一个任务开始后,后续的操作会阻塞,直到这个任务结束,保证了代码的因果关系,保证程序正确性
- 异步: 指一个任务开始执行后,与它没有因果关系的其它任务可以正常执行,不必等待前一个任务结束
- Promise / Future / Delay / Deferred: 是一个对象,用来描述在未来某个时刻才能获得的结果的值
- async / await:
async
用来定义
一个可以并发执行的任务,await
则触发
这个任务并执行
在遭遇I/O
处理时,高效CPU指令
和低效I/O
之间的巨大鸿沟,成为了软件的性能杀手。
和内存访问相比,I/O
操作的访问速度低了两个数量级,一旦遇到I/O操作
,CPU
就只能闲置来等待I/O设备
运行完毕。
因此操作系统为应用程序提供了异步I/O
,让应用可以在当前I/O
处理完毕之前,将CPU时间
用作其它任务的处理
很多拥有高并发处理能力
的编程语言,会在用户程序中嵌入一个M:N
的调度器,把M
个并发任务,合理地分配在N
个CPU Core
上并行运行,
让程序的吞吐量达到最大。比如Golang的Goroutine
Promise一般存在三个状态:
- 初始状态, Promise还未运行
- 等待(pending)状态, Promise已运行,还未结束
- 结束状态, Promise成功解析出一个值,或者执行失败
编程范式
-
泛型编程
数据结构的泛型
: 把参数化数据结构理解成一个产生类型的函数,是一种高级抽象代码的泛型化
: 当我们使用泛型结构编写代码时,相关的代码也需要额外的抽象
通过参数化让数据结构像函数一样延迟绑定,提升通用性,类型的参数可以用接口约束,使类型满足一定的行为, 同时,在使用泛型结构时,我们的代码也需要更高的抽象度。