互斥锁与自旋锁

在现实生活中,锁的作用无非是将物品关起来,防止被他人进入等。而在程序中锁的概念也是如此,当同一个内存地址被同时访问时,会造成数据竞争,意思是不知道当前变量被谁所操作,所以引入了锁的概念,当有人来访问时,对此变量加上一把锁来防止其他对象对其访问,这样才会更安全的使用变量做操作。现实中锁的种类有多种,比如物理锁、指纹锁等。在程序中亦是如此,这里大类氛围两种,互斥锁和自旋锁。 1. 数据竞争(Data Race)1 Data Race 又称 Race Condition。它旨在描述一个系统或者进程的输出依赖于不受控制的事件出现顺序或者出现时机。此词源自于两个信号试着彼此竞争,来影响谁先输出。 计算机为了提高性能,出现了多线程和多进程,增加了执行并行能力,但是在保持数据一致上就需要外力来维持。而造成这一原因的就是数据竞争,多个执行者对一个事物的操作。最为常见的例子是在银行存取款,如果付款存在多个交易时较为容易出现这类问题。 事例 如果你银行卡中有余额 1000 元,当你同时购买两个售价分别是 100 和 200 元的商品时,在银行账户中的操作可能是这样的 1000 -100 = 900 // 购买 100 元商品时扣款 900 - 200 = 700 // 购买成功第一个商品后,扣款 200 元商品 1000 - 200 = 800 // 购买 200 元善品时扣款 余额可能是这三种,因为在同时付款的时候,你不知道哪个扣款是先处理操作。所以会出现上述情况,按理说付款完成后余额剩余 700 才是正确的,但是为什么会出现这种情况,是因为出现了数据竞争,但进行扣款操作时候,两者都拿到了总额为 1000 的进行扣除,其中有一方肯定错误的扣除处理。 # 使用Golang实现 package main import ( "fmt" "sync" ) // 余额 var Balance uint = 1000 // wg var wg sync.WaitGroup // 消费行为 func consume(money uint) uint { defer wg.Done() Balance -= money return Balance } func do() { defer wg.Wait() goods := [2]uint{100, 200} // 同时有两款交易进行中 for i := range goods { wg.Add(1) go func(i int) { fmt.Printf("Current balance is: %v\n", consume(goods[i])) }(i) } } 运行函数do(),得到输出为: ...

一月 24, 2025 · 6 分钟 · anoya

词法分析

编译原理概述 在编译性语言中,当使用类似 build 指令进行编译时,在其内部会严格进行下面机制的转换,从编码层转变为机器语言层或是汇编语言,从而运行代码赋予的操作在此机器上。但是这一过程对于我们使用者来说是便捷的,不用过多的考虑构建的过程,只需要注重其结果。 在编译型语言中,常见编译过程包括: 词法分析:源码转换为 Token 语法分析:将 Token 转换为 AST 语义分析:分析校验 AST 的合法性 代码生成:AST 到 SSA 的转换 在上述流程中,其主要是将源码一步步的抽象化,这种也像在设计一种编程语言时,通过将理念抽象,获得更直观的理解,也是方便编码从业者的使用。从这一方面可以看出,对于编译也是如此。从可读语言进行抽象化,其目的旨在后续分析做铺垫。 所以说编译器主要实现的步骤则是从字符序列转换为 SSA,而这一过程,在 Golang 中可通过命令进行显式输出为 HTML。 SSA 生成 例如最为常见的一个 Go 代码(下文所有分析依据于此片段): package main import "fmt" func main() { fmt.Println("Hello, world") } 这段无疑是最为熟悉,但从保存文件到运行,中间做了什么操作,对我们来说属于黑盒,得到的只有结果,而中间的过程是不可见的。所以作为有追求有梦想的开发者,必然是会将盒子打开来一探究竟,它是如何在盒子中运作的? 在使用 Go自带的 run 命令后,程序从编译态(Go 为静态语言)到运行态的过程,首先经过编译器,而其主要负责以下操作: 通过 Token 构建 AST,构建抽象语法树。当前阶段主要负责分析、检查和代码生成 加载 Runtime 运行,在 main 函数之前加载代码,比如 GC、Schedule 等 Terminal 中执行命令:GOSSAFUNC=main go build main.go, 即可在编译过程中将转换为 SSA 的流程暴露出来。命令执行后,会在当前目录下生成文件 ssa.html。SSA(Static Single Assignment)是指静态单赋值,也是编译器优化和转换源代码的中间表示形式(这是编译的最后阶段,暂时不做解读)。 命令执行输出结果如下图所示 将ssa.html文件打开,在浏览器中显示如下图 可以大致浏览下页面,从 Source Code 到 SSA 的转换,这就是在编译过程中,将我们可读的字符序列,转换或是说抽象为特定结构。 ...

一月 24, 2025 · 2 分钟 · anoya

初识Go内存管理

计算机存储设备的读写入读金字塔 我们所说的内存管理是在堆上,栈不需要操作,因为是自动回收的 栈:先进后出,出栈和入栈 内存管理基于 tcmalloc (thread-caching malloc), Page: 大小为 4kb,无论是申请还是释放内存都以 Page 为单位,也就是 4kb Span: 内存块,由多个 Page 构成 SizeClass: Object: 分配流程 当程序需要内存时,会像堆请求分配内存,得到响应后,堆内存会从为分配的内存中切割出一小块内存,用来分配。中间使用链表将各个小块堆内存连接起来 分配对象时,会优先在已分配(释放后)的内存中查找近似的大小块,如果没有合适的,将在未分配的堆内存中切割类似大小的内存用来分配。 当分配的内存多了起来,在每一段分配的内存中会存在未使用的内存,这就是碎片化产生的原因。那么如何将未使用的内存,也就是碎片的内存重新利用起来? 碎片化内存利用 内存对齐 CPU 访问内存时是以字长为单位,减少对内存的访问量,加大 CPU 处理的吞吐量 现在 CPU 普遍是 64 位,字长为 8 字节,32 位的CPU,字长位 4字节 内存对象 当 非内存对齐 TCMalloc Google 开源内存分配 小对象分配 小对象的分配基于 page,当需要分配的对象小于单个 page 时,采用小对象分配方式 page 单个 page 占用为 4kb, 定长对象分配 变长对象分配 内存分配时,“取整”操作,如7字节的占用,就分配8字节的长度。但是分配并不是按照2的幂数, 目前所得时8, 16, 32, 48, 64, 80(等待测试),这样分配的目的是为了减少碎片化,如果按照2的幂数计算,如65字节的占用,将会分配128字节的长度,碎片化占比接近一半。 大对象分批 span span 是一种分配单位, RadixTree ...

一月 24, 2025 · 1 分钟 · anoya

Flutter模块之Provider

添加依赖 dependencies: provider: ^4.3.2+4 MultiProvider 对多个 Provider 整理 name description Provider 最基础的 provider 组成,接收一个值并暴露它, 无论值是什么。 ListenableProvider 供可监听对象使用的特殊 provider,ListenableProvider 会监听对象,并在监听器被调用时更新依赖此对象的 widgets。 ChangeNotifierProvider 为 ChangeNotifier 提供的 ListenableProvider 规范,会在需要时自动调用ChangeNotifier.dispose。 ValueListenableProvider 监听 ValueListenable,并且只暴露出ValueListenable.value。 StreamProvider 监听流,并暴露出当前的最新值。 FutureProvider 接收一个Future,并在其进入 complete 状态时更新依赖它的组件。 Provider 最基础的 provider 组成,接收一个值并暴露它, 无论值是什么。但是不是会 ui 上刷新 生成 model class CountModel { // 全局状态 int count = 0; // 状态指定方法 add() { this.count++; print(this.count); } } main.dart import 'package:FlutterProvider/model/CountModel.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; main() { runApp( // 使用Provider管理 Provider<CountModel>( create: (_) { return CountModel(0); }, child: MyApp(), ), ); } class MyApp extends StatelessWidget { const MyApp({Key key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: "Provider", home: HomePage(), ); } } class HomePage extends StatelessWidget { const HomePage({Key key}) : super(key: key); @override Widget build(BuildContext context) { // 获取Provider管理的model CountModel count = Provider.of<CountModel>(context); return Scaffold( appBar: AppBar( title: Text("Flutter Provider"), ), body: Center( child: Text("当前的值:${count.count.toString()}"), ), floatingActionButton: FloatingActionButton( onPressed: () { // 调用model中的方法,会在control中输出,但是不会在ui上更新 count.add(); }, child: Icon(Icons.add), ), ); } } ChangeNotifierProvider(常用) 为 ChangeNotifier 提供的 ListenableProvider 规范,会在需要时自动调用ChangeNotifier.dispose。 ...

一月 24, 2025 · 2 分钟 · anoya

Colima启动异常

vz driver is running but host agent is not 记录一次 Colima 的使用异常 在 MacOS 更新到 14.4.1 (23E224)后,使用命令colima start提示错误: ➜ ~ colima start INFO[0000] starting colima INFO[0000] runtime: docker INFO[0001] starting ... context=vm > Using the existing instance "colima" > errors inspecting instance: [vz driver is running but host agent is not] FATA[0001] error starting vm: error at 'starting': exit status 1 在网络上找了很久的解决办法,尝试了许多也都无法解决。后来又在 Colima 的 github 上创建了 Issue,一时半会也没有人来解答。也有人遇到这个问题创建了Issues,下拉看了好长也没有一个标准的解决方法。 Troubleshooting and FAQs Check here for Frequently Asked Questions. 在 Colima 的 README.md 中看到了希望。点击去一目十行的浏览下来,找到了关键词Colima not starting。我知道问题将被解决掉,大致浏览了下,原因是在电脑关机时,Colima 创建的 Vm 没有安全退出(优雅关机)。所以在开机启动 Vm 后的状态处于Broken ...

一月 24, 2025 · 1 分钟 · anoya