浅谈GO GC (垃圾回收)

垃圾回收 垃圾回收这里的垃圾指的是什么?为什么要回收? 在程序运行的时候,进程会在堆内存申请内存空间。但是在函数退出的时候会栈空间会被销毁。因此这一段堆内存空间没有任何的指针指向它(没有被引用)。因此会造成内存的浪费(内存泄露)。这一段的没有被引用的内存就是“垃圾”。,程序员不再需要手动管理内存的分配和释放,从而减少了由于内存管理不当引起的内存泄漏或悬空指针等问题。 常见的GC有哪几种 手动垃圾回收,代表语言(C,C++) 标记-清扫算法 三色标记法 标记-整理算法 移动整理法 复制整理法 分代回收 引用计数 简述常见的GC如何实现,和他们各自的优缺点。 手动垃圾回收: 实现:程序员自行调用函数销毁 优点:程序员对程序内存的掌控自由。 缺点:容易出现提前释放(悬挂指针)、忘记释放(内存泄漏)等问题。 标记-清扫算法: 实现:标记从根节点(栈内存、数据段)能追踪到的数据为游泳数据,其他未标记的数据就是垃圾数据,将对垃圾数据进行回收 优点:实现相对简单, 缺点:容易造成内存碎片化 标记-整理算法: 实现: 标记阶段与标记-清扫算法一致。 整理阶段: 移动整理法:整理移动有用的数据,使有用的数据尽可能紧凑的放在内存里。 优点:解决了内存碎片化的问题 缺点:多次扫描移动,会带来不小的性能开销 复制整理法:将内存划分为Form和To,将From空间中的有用数据都复制到To空间。并将Form和To空间的角色对换。 优点:解决了多次扫描移动的性能问题 缺点:内存使用率低。只有一半的堆内存空间被使用 分代回收: 实现:基于弱分代假说。将数据分为新生代、老年代。新生代、老年代采用不同的回收算法 引用计数 实现:每次对象应用都会更新对象的引用计数,当引用计数为0就回收该空间 优点:可以及时回收垃圾内存 缺点:高频更新引用计数会有不小性能开销,循环引用会导致引用计数永远不为0 什么是STW,为什么会有STW。 STW(stop the world)简单的就是让用户程序停下来。 STW期间,程序会进行垃圾回收 STW STW 是 Stop-The-World 的缩写,指的是在垃圾回收或某些系统操作过程中,暂停所有应用程序线程的行为,直到特定任务(通常是垃圾回收)完成。 ![e6d686db23ff699d03219905d8ceb2c1.png].(/e6d686db23ff699d03219905d8ceb2c1.png) 这里会带来一个问题,用户程序接受长时间STW吗? 为了解决这个问题就出现了增量式垃圾回收 增量式垃圾回收 实现:将一次GC分为多次,并和用户程序交互进行 优点:解决了STW时间长的问题 缺点:如果在标记后-清理前。创建的内容将会被误删除 为了避免误删可以采用三色抽象避免 三色抽象:标记数据分为 黑 - 灰 - 白 黑:已经遍历标记完的数据 灰:还未遍历完的数据 白:未遍历(应用不到)的垃圾数据 因此在清扫阶段发现有黑->白的数据。着这个白数据是在标记后-清理前创建的 因此Golang官方提出2种条件 强三色不变式:不允许出现黑色到白色的情况 弱三色不变式:允许黑色到白色的情况,但是这个白色需要被灰色引用 写屏障 ...

十二月 9, 2024

计算机在并发的情况下很大可能会出现数据错乱的问题,因此运入了“锁”用于解决该类问题。 常见的锁:互斥锁、自旋锁、读写锁、悲观锁、乐观锁 互斥锁 特点:容易实现,最基础的锁概念 解释:在使用一个资源时,先上锁使用。上锁后其他线程都无法再上锁。直到解锁后,其他线程才能上锁使用。 生活例子:公共厕所,谁要上厕所就要先上锁后使用,避免其他人也闯入使用厕所。 自旋锁 自旋锁与互斥锁十分类似。 区别: 自旋锁:加锁失败,线程还是会不断的去申请加锁,直到他拿到锁 互斥锁:加锁失败,线程会释放CPU资源,等待解锁后在申请上锁。 生活例子 -> 公共厕所 自旋锁:不断的在门口敲门,直到里面的人出来 互斥锁:不动了,直到里面的人出来叫你。 读写锁 顾名思义就是由读锁和写锁组成的。读数据往往是可以并发的读,并发写数据可能会导致数据不符合预期的情况 读数据需要满足目前无写锁。 写数据需要满足目前无写锁、读锁。 生活例子:假设一个班级的考试成绩都写到同一个本子上。而学生会读成绩,老师会写成绩。学生看成绩可以同时看,而语文老师和数学老师不能并发的把成绩写到一页里。 读写锁可以分为 读优先锁 写优先锁 公平读写锁 读优先锁 学生看到有老师在等待改写数据时,不理会老师继续读数据 写优先锁 读数据还需要满足无在等待的写锁 学生看到有老师在等待改写数据时,会主动让老师写数据。 公平读写锁 将读学请求放入一个队列,排队的去读写 学生和老师排个队伍 乐观锁与悲观锁 互斥锁、自旋锁、读写锁都是悲观锁 悲观锁:多线程同时修改共享资源的概率比较高,容易发生冲突。因此访问共享资源前需要上锁。 乐观锁:认为发生冲突的概率比较低。并不会上任何的锁,而是校验该数据是否别修改过,修改过着放弃本次操作

十二月 8, 2024

程序与内存之间的关系

程序与内存之间的关系 数据类型本质:固定内存大小的别名 数据类型的作用:编译器预算对象(变量)分配的内存空间大小 内存四区:栈区(Stack),堆区(heap),全局区-静态全局变量区,全局区-常量区 运行流程说明 操作系统把物理硬盘代码load到内存 操作系统把代码分成四个区 操作系统找到main函数入口执行 内存四区 栈区(Stack) :空间较小,要求数据读写性能高,数据存放时间较短暂。由编译器自动分配和释放,存放函数的参数值、函数的调用流程方法地址、局部变量等(局部变量如果产生逃逸现象,可能会挂在在堆区) 堆区(heap):空间充裕,数据存放时间较久。一般由开发者分配及释放(但是Golang中会根据变量的逃逸现象来选择是否分配到栈上或堆上),启动Golang的GC由GC清除机制自动回收。 全局区-静态全局变量区:全局变量的开辟是在程序在main之前就已经放在内存中。而且对外完全可见。即作用域在全部代码中,任何同包代码均可随时使用,在变量会搞混淆,而且在局部函数中如果同名称变量使用:=赋值会出现编译错误。 全局变量最终在进程退出时,由操作系统回收。 全局区-常量区:常量区也归属于全局区,常量为存放数值字面值单位,即不可修改。或者说的有的常量是直接挂钩字面值的。 比如: const cl = 10 cl是字面量10的对等符号。 所以在golang中,常量是无法取出地址的,因为字面量符号并没有地址而言。

十二月 8, 2024

进程、线程、协程

从计算机的发展上,这概念从大到小的顺序是程序->进程->线程->协程 基础概念 进程:系统进行资源调度和分配的的基本单位 线程:线程是操作系统调度与执行的基础单位 协程:又称为用户级线程、微线程 核心点 进程 是操作系统资源调度的基础单位单元; 进程是能独立运行、独立获取资源的基本单位 线程 是操作系统最小调度单元; 创建、销毁、调度交由内核完成,cpu 需完成用户态与内核态间的切换; 可充分利用多核,实现并行. 协程 与线程存在映射关系,为 M:1; 创建、销毁、调度在用户态完成,对内核透明,所以更轻; 从属同一个内核级线程,无法并行;一个协程阻塞会导致从属同一线程的所有协程无法执行. 生活例子 CPU 电力 进程 工厂 线程 工厂部门 假设这个城市就一个工厂(进程)、 一个工厂部门(线程)、一条电线(单核)的情况。 这时候工厂(进程) 中的一个工厂部门(线程),电线(单核)可以完全为他工作。 但是现在出现问题了,现在任务量太大了,工厂就划分了2个工厂部门(线程),让这2个部门同时干活,但是现在只有一条电线(单核),因此这2个部门不得不轮流用电(CPU时间片)。 但是现在又出现问题了,老板发现切换电源需要有资质的电工来操作(内核),每次切换的开销太大了。因此把这些让一个工厂部门(线程)长期通电,自己内部让原先2个部门的人轮流进去通电的工厂部门内工作。应为这是工厂内部的人员(用户态)调度,不涉及电工(内核态)。切换的代价相对较小,这就是协程

十二月 8, 2024

原码、反码、补码、移码

名词解释 原码:原码是最简单的整数表示方法,其中最高位用来表示符号(0表示正数,1表示负数),其余位表示数值的绝对值。例如,+5的原码是00000101,-5的原码是10000101。 反码:反码是通过对原码取反得到的,即将正数的原码保持不变,负数的原码的所有位取反(包括符号位)。例如,+5的反码是00000101(与原码相同),-5的反码是11111010。 补码:补码是计算机中最常用的整数表示方法。正数的补码与其原码相同,而负数的补码是其反码加1。补码的特点是在进行加减法运算时,不需要单独处理符号位。例如,+5的补码是00000101,-5的补码是11111011。 移码:移码是一种不常见的整数表示方法,在计算机领域中较少使用。它与补码类似,但是所有的数值都被偏移了一个固定的值,通常是一个中间值,以使得正数的表示始终比负数的表示更大。这种表示方法在某些特殊的硬件设计中可能会使用。 例子 1.原码为正数 整数 +1 0000 0001 // 原码 0000 0001 // 反码 // 正数的 反码 = 原码 0000 0001 // 补码 // 正数的 补码 = 反码 = 原码 1000 0001 // 移码 // 移码 = 补码的符号位取反 2.原码为0 整数 +0 0000 0000 // 原码 0000 0000 // 反码 // 正数的 反码 = 原码 0000 0000 // 补码 // 正数的 补码 = 反码 = 原码 1000 0000 // 移码 // 移码 = 补码的符号位取反 整数 -0 1000 0000 // 原码 1111 1111 // 反码 // 负数的 反码 = 原码除符号位不变 其他全取反 0000 0000 // 补码 // 负数的 补码 = 反码+1 //这里由于越界,取后8位则结果为8个0 1000 0000 // 移码 // 移码 = 补码的符号位取反 3.原码为负数 ...

十二月 8, 2024