golang��д�ļ�Դ�����
大纲
概述
chan 是文件文件 golang 的核心结构,是源码与其他高级语言区别的显著特色之一,也是分析 goroutine 通信的关键要素。尽管广泛使用,读写读写但对其深入理解的文件文件8的源码人却不多。本文将从源码编译器的源码视角,全面剖析 channel 的分析用法。
channel 的读写读写本质
从实现角度来看,golang 的文件文件 channel 实质上是环形队列(ringbuffer)的实现。我们将 chan 称为管理结构,源码channel 中可以放置任何类型的分析对象,称为元素。读写读写
channel 的文件文件使用方法
我们从 channel 的使用方式入手,详细介绍 channel 的源码使用方法。
channel 的创建
创建 channel 时,用户通常有两种选择:创建带有缓冲区和不带缓冲区的 channel。这对应于 runtime/chan.go 文件中的 makechan 函数。
channel 入队
用户使用姿势:对应函数实现为 chansend,位于 runtime/chan.go 文件。
channel 出队
用户使用姿势:对应函数分别是 chanrecv1 和 chanrecv2,位于 runtime/chan.go 文件。
结合 select 语句
用户使用姿势:对应函数实现为 selectnbsend,位于 runtime/chan.go 文件中。
结合 for-range 语句
用户使用姿势:对应使用函数 chanrecv2,位于 runtime/chan.go 文件中。
源码解析
以上,我们通过宏观的用户使用姿势,了解了不同使用姿势对应的不同实现函数,接下来将详细分析这些函数的实现。
makechan 函数
负责 channel 的创建。在 go 程序中,当我们写类似 v := make(chan int) 的初始化语句时,就会调用不同类型对应的初始化函数,其中 channel 的初始化函数就是 makechen。
runtime.makechan
定义原型:
通过这个,我们可以了解到,声明创建一个 channel 实际上是得到了一个 hchan 的指针,因此 channel 的核心结构就是基于 hchan 实现的。
其中,t 参数指定元素类型,size 指定 channel 缓冲区槽位数量。如果是带缓冲区的 channel,那么 size 就是槽位数;如果没有指定,那么就是 0。
makechan 函数执行了以下两件事:
1. 参数校验:主要是越界或 limit 的校验。
2. 初始化 hchan:分为三种情况:
所以,我们看到除了 hchan 结构体本身的内存分配,该结构体初始化的关键在于四个字段:
hchan 结构
makechan 函数负责创建了 chan 的核心结构-hchan,接下来我们将详细分析 hchan 结构体本身。
在 makechan 中,初始化时实际上只初始化了四个核心字段:
我们使用 channel 时知道,channel 常常会因为两种情况而阻塞:1)投递时没有空间;2)取出时还没有元素。
从以上描述来看,就涉及到 goroutine 阻塞和 goroutine 唤醒,这个功能与 recvq,sendq 这两个字段有关。
waitq 类型实际上是一个双向列表的实现,与 linux 中的 LIST 实现非常相似。
chansend 函数
chansend 函数是访问加密源码在编译器解析到 c <- x 这样的代码时插入的,本质上就是把一个用户元素投递到 hchan 的 ringbuffer 中。chansend 调用时,一般用户会遇到两种情况:
接下来,我们看看 chansend 究竟做了什么。
当我们在 golang 中执行 c <- x 这样的代码,意图将一个元素投递到 channel 时,实际上调用的是 chansend 函数。这个函数分几个场景来处理,总结来说:
关于返回值:chansend 返回值标明元素是否成功入队,成功则返回 true,否则 false。
select 的提前揭秘:
golang 源代码经过编译会变成类似如下:
而 selectnbasend 只是一个代理:
小结:没错,chansend 功能就是这么简单,本质上就是一句话:将元素投递到 channel 中。
chanrecv 函数
对应的 golang 语句是 <- c。该函数实现了 channel 的元素出队功能。举个例子,编译对应一般如下:
golang 语句:
对应:
golang 语句(这次的区别在于是否有返回值):
对应:
编译器在遇到 <- c 和 v, ok := <- c 的语句时,会换成对应的 chanrecv1,chanrecv2 函数,这两个函数本质上都是一个简单的封装,元素出队的实现函数是 chanrecv,我们详细分析这个函数。
chanrecv 函数的返回值有两个值,selected,received,其中 selected 一般作为 select 结合的函数返回值,指明是否要进入 select-case 的代码分支,received 表明是否从队列中成功获取到元素,有几种情况:
selectnbsend 函数
该函数是 c <- v 结合到 select 时的函数,我们使用 select 的 case 里面如果是一个 chan 的表达式,那么编译器会转换成对应的 selectnbsend 函数,如下:
对应编译函数逻辑如下:
selectnbsend 本质上也就是个 chansend 的封装:
chansend 的内部逻辑上面已经详细说明过,唯一不同的就是 block 参数被赋值为 false,也就是说,在 ringbuffer 没有空间的情况下也不会阻塞,直接返回。划重点:chan 在这里不会切走执行权限。
selectnbrecv 函数
该函数是 v := <- c 结合到 select 时的函数,我们使用 select 的 case 里面如果是一个 chan 的表达式,那么编译器会转换成对应的 selectnbsrecv 函数,如下:
对应编译函数逻辑如下:
selectnbrecv 本质上也就是个 chanrecv 的封装:
chanrecv 的内部逻辑上面已经详细说明过,在 ringbuffer 没有元素的情况下也不会阻塞,直接返回。这里不会因此而切走调度权限。
selectnbrecv2 函数
该函数是 v, ok = <- c 结合到 select 时的函数,我们使用 select 的 case 里面如果是一个 chan 的表达式,那么编译器会转换成对应的 selectnbrecv2 函数,如下:
对应编译函数逻辑如下:
selectnbrecv2 本质上是个 chanrecv 的封装,只不过返回值不一样而已:
chanrecv 的内部逻辑上面已经详细说明过,在 ringbuffer 没有元素的情况下也不会阻塞,直接返回。这里不会因此而切走调度权限。selectnbrecv2 与 selectnbrecv 函数的不同之处在于还有一个 ok 参数指明是否获取到了元素。
chanrecv2 函数
chan 可以与 for-range 结合使用,编译器会识别这种语法。如下:
这个本质上是个 for 循环,我们知道 for 循环关键是拆分成三个部分:初始化、条件判断、源码市场诚信条件递进。
那么在我们 for-range 和 chan 结合起来之后,这三个关键因素又是怎么理解的呢?简述如下:
init 初始化:无
condition 条件判断:
increment 条件递进:无
当编译器遇到上面 chan 结合 for-range 写法时,会转换成 chanrecv2 的函数调用。目的是从 channel 中出队元素,返回值为 received。首先看下 chanrecv2 的实现:
chan 结合 for-range 编译之后的伪代码如下:
划重点:从这个实现中,我们可以获取一个非常重要的信息,for-range 和 chan 的结束条件只有这个 chan 被 close 了,否则一直会处于这个死循环内部。为什么?注意看 chanrecv 接收的参数是 block=true,并且这个 for-range 是一个死循环,除非 chanrecv2 返回值为 false,才有可能跳出循环,而 chanrecv2 在 block=true 场景下返回值为 false 的唯一原因只有:这个 chan 是 close 状态。
总结
golang 的 chan 使用非常简单,这些简单的语法糖背后其实都是对应了相应的函数实现,这个翻译由编译器来完成。深入理解这些函数的实现,对于彻底理解 chan 的使用和限制条件是必不可少的。深入理解原理,知其然知其所以然,你才能随心所欲地使用 golang。
map在golang的底层实现和源码分析
在Golang 1..2版本中,map的底层实现由两个核心结构体——hmap和bmap(此处用桶来描述)——构建。初始化map,如`make(map[k]v, hint)`,会创建一个hmap实例,包含map的所有信息。makemap函数负责创建hmap、计算B值和初始化桶数组。
Golang map的高效得益于其巧妙的设计:首先,key的hash值的后B位作为桶索引;其次,key的hash值的前8位决定桶内结构体的数组索引,包括tophash、key和value;tophash数组还用于存储标志位,当桶内元素为空时,标志位能快速识别。读写删除操作充分利用了这些设计,包括更新、新增和删除key-value对。
删除操作涉及到定位key,移除地址空间,更新桶内tophash的标志位。而写操作,虽然mapassign函数返回value地址但不直接写值,实际由编译器生成的汇编指令提高效率。扩容和迁移机制如sameSizeGrow和biggerSizeGrow,针对桶利用率低或桶数组满的情况,通过调整桶结构和数组长度,优化查找效率。
evacuate函数负责迁移数据到新的桶区域,并清理旧空间。最后,虽然本文未详述,但订阅"后端云"公众号可获取更多关于Golang map底层实现的深入内容。
golang源码系列---手把手带你看heap实现
heap包定义实现堆所需结构与操作方法,包含Interface接口,允许实现堆功能。mv音乐源码Push和Pop方法分别用于添加元素与移除堆顶元素。
构建堆时需实现sort.Interface接口。Heap包内部仅包含两个非导出函数,作为堆导出方法的基础。
down函数将堆顶元素下沉,保持堆结构。up函数则将当前节点上浮,确保堆的性质。
Init函数初始化堆结构。Push与Pop方法用于添加与移除元素,底层依赖up和down函数。
Remove方法移除指定位置元素,类似Pop,通过上浮下沉操作恢复堆结构。
Fix函数在节点值变化后,用于修复堆结构。
使用案例:以学生信息为例,根据年龄排序,并按升序输出。
总结:heap包提供实现堆所需的接口与方法,通过非导出函数与导出方法的配合,完成堆的操作与构建。实例化堆后,可根据具体需求使用Push、Pop、Remove与Fix方法,实现元素的添加、删除与结构修复。
浅析源码 golang kafka sarama包(一)如何生产消息以及通过docker部署kafka集群with kraft
本文将深入探讨Golang中使用sarama包进行Kafka消息生产的过程,以及如何通过Docker部署Kafka集群采用Kraft模式。首先,我们关注数据的生产部分。
在部署Kafka集群时,我们将选择Kraft而非Zookeeper,通过docker-compose实现。集群中,理解LISTENERS的含义至关重要,主要有几个类型:
Sarama在每个topic和partition下,会为数据传输创建独立的goroutine。生产者操作的起点是创建简单生产者的方法,接着维护局部处理器并根据topic创建topicProducer。
在newBrokerProducer中,run()方法和bridge的匿名函数是关键。它们反映了goroutine间的巧妙桥接,通过channel在不同线程间传递信息,体现了goroutine使用的精髓。
真正发送消息的过程发生在AsyncProduce方法中,这是数据在三层协程中传输的环节,虽然深度适中,但需要仔细理解。
sarama的架构清晰,但数据传输的核心操作隐藏在第三层goroutine中。输出变量的使用也有讲究:当output = p.bridge,它作为连接内外协程的桥梁;output = nil则关闭channel,output = bridge时允许写入。
Golang源码剖析panic与recover,看不懂你打我好了
哈喽,大家好,我是沙盘模拟源码asong,今天与大家来聊一聊go语言中的"throw、try.....catch{ }"。如果你之前是一名java程序员,我相信你一定吐槽过go语言错误处理方式,但是这篇文章不是来讨论好坏的,我们本文的重点是带着大家看一看panic与recover是如何实现的。上一文我们讲解了defer是如何实现的,但是没有讲解与defer紧密相连的recover,想搞懂panic与recover的实现也没那么简单,就放到这一篇来讲解了。废话不多说,直接开整。
Go 语言中panic 关键字主要用于主动抛出异常,类似 java 等语言中的 throw 关键字。panic 能够改变程序的控制流,调用 panic 后会立刻停止执行当前函数的剩余代码,并在当前 Goroutine 中递归执行调用方的 defer;
Go 语言中recover 关键字主要用于捕获异常,让程序回到正常状态,类似 java 等语言中的 try ... catch 。recover 可以中止 panic 造成的程序崩溃。它是一个只能在 defer 中发挥作用的函数,在其他作用域中调用不会发挥作用;
recover只能在defer中使用这个在标准库的注释中已经写明白了,我们可以看一下:
这里有一个要注意的点就是recover必须要要在defer函数中使用,否则无法阻止panic。最好的验证方法是先写两个例子:
运行我们会发现example2()方法的panic是没有被recover住的,导致整个程序直接crash了。这里大家肯定会有疑问,为什么直接写recover()就不能阻止panic了呢。我们在 详解defer实现机制(附上三道面试题,我不信你们都能做对)讲解了defer实现原理,一个重要的知识点**defer将语句放入到栈中时,也会将相关的值拷贝同时入栈。**所以defer recover()这种写法在放入defer栈中时就已经被执行过了,panic是发生在之后,所以根本无法阻止住panic。
通过运行结果可以看出panic不会影响defer函数的使用,所以他是安全的。
这里我开了两个协程,一个协程会发生panic,导致程序崩溃,但是只会执行自己所在Goroutine的延迟函数,所以正好验证了多个 Goroutine 之间没有太多的关联,一个 Goroutine 在 panic 时也不应该执行其他 Goroutine 的延迟函数。
其实我们在实际项目开发中,经常会遇到panic问题, Go 的 runtime 代码中很多地方都调用了 panic 函数,对于不了解 Go 底层实现的新人来说,这无疑是挖了一堆深坑。我们在实际生产环境中总会出现panic,但是我们的程序仍能正常运行,这是因为我们的框架已经做了recover,他已经为我们兜住底,比如gin,我们看一看他是怎么做的。
我们先来写个简单的代码,看看他的汇编调用:执行go tool compile -N -l -S main.go就可以看到对应的汇编码了,我们截取部分片段分析:
上面重点部分就是画红线的三处,第一步调用runtime.deferprocStack创建defer对象,这一步大家可能会有疑惑,我上一文忘记讲个这个了,这里先简单概括一下,defer总共有三种模型,编译一个函数里只会有一种defer模式。在讲defer实现机制时,我们一起看过defer的结构,其中有一个字段就是_panic,是触发defer的作用,我们来看看的panic的结构:
简单介绍一下上面的字段:
上面的pc、sp、goexit我们单独讲一下,runtime包中有一个Goexit方法,Goext能够终止调用它的goroutine,其他的goroutine是不受影响的,goexit也会在终止goroutine之前运行所有延迟调用函数,Goexit不是一个panic,所以这些延迟函数中的任何recover调用都将返回nil。如果我们在主函数中调用了Goexit会终止该goroutine但不会返回func main。由于func main没有返回,因此程序将继续执行其他gorountine,直到所有其他goroutine退出,程序才会crash。
下面就开始我们的重点吧~。
在讲defer实现机制时,我们一起看过defer的结构,其中有一个字段就是_panic,是触发defer的作用,我们来看看的panic的结构:简单介绍一下上面的字段:上面的pc、sp、goexit我们单独讲一下,runtime包中有一个Goexit方法,Goext能够终止调用它的goroutine,其他的goroutine是不受影响的,goexit也会在终止goroutine之前运行所有延迟调用函数,Goexit不是一个panic,所以这些延迟函数中的任何recover调用都将返回nil。如果我们在主函数中调用了Goexit会终止该goroutine但不会返回func main。由于func main没有返回,因此程序将继续执行其他gorountine,直到所有其他goroutine退出,程序才会crash。写个简单的例子:运行上面的例子你就会发现,即使在主goroutine中调用了runtime.Goexit,其他goroutine是没有任何影响的。所以结构中的pc、sp、goexit三个字段都是为了修复runtime.Goexit,这三个字段就是为了保证该函数的一定会生效,因为如果在defer中发生panic,那么goexit函数就会被取消,所以才有了这三个字段做保护。看这个例子:
英语好的可以看一看这个: github.com/golang/go/is...,这就是上面的一个例子,这里就不过多解释了,了解就好。
接下来我们再来看一看gopanic方法。
gopanic的代码有点长,我们一点一点来分析:
根据不同的类型判断当前发生panic错误,这里没什么多说的,接着往下看。
上面的代码都是截段,这些部分都是为了判断当前defer是否可以使用开发编码模式,具体怎么操作的就不展开了。
在第三部分进行defer内联优化选择时会执行调用延迟函数(reflectcall就是这个作用),也就是会调用runtime.gorecover把recoverd = true,具体这个函数的操作留在下面讲,因为runtime.gorecover函数并不包含恢复程序的逻辑,程序的恢复是在gopanic中执行的。先看一下代码:
这段代码有点长,主要就是分为两部分:
第一部分主要是这个判断if gp._panic != nil && gp._panic.goexit && gp._panic.aborted { ... },正常recover是会绕过Goexit的,所以为了解决这个,添加了这个判断,这样就可以保证Goexit也会被recover住,这里是通过从runtime._panic中取出了程序计数器pc和栈指针sp并且调用runtime.recovery函数触发goroutine的调度,调度之前会准备好 sp、pc 以及函数的返回值。
第二部分主要是做panic的recover,这也与上面的流程基本差不多,他是从runtime._defer中取出了程序计数器pc和栈指针sp并调用recovery函数触发Goroutine,跳转到recovery函数是通过runtime.call进行的,我们看一下其源码(src/runtime/asm_amd.s 行):
因为go语言中的runtime环境是有自己的堆栈和goroutine,recovery函数也是在runtime环境执行的,所以要调度到m->g0来执行recovery函数,我们在看一下recovery函数:
在recovery 函数中,利用 g 中的两个状态码回溯栈指针 sp 并恢复程序计数器 pc 到调度器中,并调用 gogo 重新调度 g , goroutine 继续执行,recovery在调度过程中会将函数的返回值设置为1。这个有什么作用呢? 在deferproc函数中找到了答案:
当延迟函数中recover了一个panic时,就会返回1,当 runtime.deferproc 函数的返回值是 1 时,编译器生成的代码会直接跳转到调用方函数返回之前并执行 runtime.deferreturn,跳转到runtime.deferturn函数之后,程序就已经从panic恢复了正常的逻辑。
在这里runtime.fatalpanic实现了无法被恢复的程序崩溃,它在中止程序之前会通过 runtime.printpanics 打印出全部的 panic 消息以及调用时传入的参数。
这就是这个逻辑流程,累死我了。。。。
结尾给大家发一个小福利,哈哈,这个福利就是如果避免出现panic,要注意这些:这几个是比较典型的,还有很多会发生panic的地方,交给你们自行学习吧~。
好啦,这篇文章就到这里啦,素质三连(分享、点赞、在看)都是笔者持续创作更多优质内容的动力!
Golang学习——error和创建error源码解析
Golang中的错误处理与Java或Python有着显著的不同。它没有类似于try...catch的结构来处理错误,这种处理方式在编程界引起了争议。正确且优雅地处理错误是值得深入研究的话题。 本文将对Golang中的错误概念和错误创建方法进行解析,同时解读源码,帮助读者更好地理解和运用。一. 初识error
在Golang中,错误被定义为`error`类型,它是标准库中的一个接口类型。`error`类型包含一个`Error()`方法,返回一个字符串描述,使得任何实现该接口的类型都可以作为错误使用。 `error`值可以被存储在变量中,也可以从函数中返回。`error`为`nil`时,表示没有错误发生。1. 什么是error
错误是指在业务过程中出现的问题,如打开文件失败,这类情况在预期之中。而异常则指的是不应该出现的问题却发生了,这类情况在预期之外。 错误是业务流程的一部分,而异常不是。`error`可以被视为一种类型,类似于`int`或`float`等。2. error源码
在`src/builtin/builtin.go`文件中,定义了`error`接口和相关实现。 `error`接口包含一个`Error()`方法,该方法返回描述错误的字符串。任何实现了`Error()`方法的类型都可以作为错误使用。 记住,`error`为`nil`表示没有错误。二. error创建
错误在Golang中可以通过两种方式创建:1. errors.New()函数
在`src/errors/errors.go`文件中,定义了`errors.New()`函数,该函数接受一个字符串参数,返回一个`error`对象。 `New()`函数创建一个错误,其格式为给定的文本。即使文本相同,每次调用`New()`也会返回不同的错误值。 错误值使用一个结构体`errorString`表示,包含一个`string`类型字段`s`,并实现了一个`Error()`方法。实战
实例中,使用`errors.New()`创建了一个错误对象。 输出显示了错误对象的类型为`errorString`指针,前面的`errors.`表明了其来自`errors`包。更具体的信息
在某些情况下,可能需要更具体的信息来描述错误,此时可以使用`fmt.Errorf()`函数。2. fmt.Errorf()函数
`fmt.Errorf()`函数用于将字符串格式化,并添加上下文信息,以更精确地描述错误。实战
实例中,通过`fmt.Errorf()`创建了一个带有上下文信息的错误对象。 输出显示了错误对象的类型为`*errors.errorString`,同时包含具体错误编码``。疑问解答
疑惑在于为什么`fmt.Errorf()`创建的错误对象类型也是`*errors.errorString`,实际上,这与`p.wrappedErr`字段有关。 通过源码分析,可以理解`p.wrappedErr`是`pp`结构体的一个字段,当格式化错误字符串中不包含`%w`占位符时,该字段为`nil`。 `fmt.wrapError`类型源自于当`p.wrappedErr`不为`nil`时,所执行的代码逻辑。这个类型是通过`wrapError`结构体实现的,它包含两个字段,并实现了两个方法。 至此,我们了解了Golang中错误的创建方式及其背后的原理。通过`errors.New()`和`fmt.Errorf()`函数,开发者可以有效地创建和处理错误,从而实现更健壮的代码。golangwriter
Golangï¼I/Oæä½ï¼åä¸ä¸è¦å°ç§è¿äºç¥è¯ç¹
I/Oæä½ä¹å«è¾å ¥è¾åºæä½ãå ¶ä¸Iæ¯æInputï¼Oæ¯æOutputï¼ç¨äºè¯»æè åæ°æ®çï¼æäºè¯è¨ä¸ä¹å«æµæä½ï¼æ¯ææ°æ®éä¿¡çééã
Golangæ ååºå¯¹IOçæ½è±¡é常精巧ï¼å个ç»ä»¶å¯ä»¥éæç»åï¼å¯ä»¥ä½ä¸ºæ¥å£è®¾è®¡çå ¸èã
ioå ä¸æä¾I/Oåå§æä½çä¸ç³»åæ¥å£ãå®ä¸»è¦å è£ äºä¸äºå·²æçå®ç°ï¼å¦oså ä¸çé£äºï¼å¹¶å°è¿äºæ½è±¡æ为å®ç¨æ§çåè½åä¸äºå ¶ä»ç¸å ³çæ¥å£ã
å¨ioå ä¸æéè¦çæ¯ä¸¤ä¸ªæ¥å£ï¼ReaderåWriteræ¥å£ï¼é¦å æ¥ä»ç»è¿è¯»çæä½ã
Readeræ¥å£çå®ä¹ï¼Read()æ¹æ³ç¨äºè¯»åæ°æ®ã
Readå°len(p)个åè读åå°pä¸ãå®è¿å读åçåèæ°nï¼0=n=len(p)ï¼ä»¥åä»»ä½éå°çé误ãå³ä½¿Readè¿åçnlen(p)ï¼å®ä¹ä¼å¨è°ç¨è¿ç¨
ä¸ä½¿ç¨pçå ¨é¨ä½ä¸ºæå空é´ãè¥ä¸äºæ°æ®å¯ç¨ä½ä¸å°len(p)个åèï¼Readä¼ç §ä¾è¿åå¯ç¨çä¸è¥¿ï¼èä¸æ¯çå¾ æ´å¤ã
å½Readå¨æå读ån0个åèåéå°ä¸ä¸ªé误æEOFæ åµï¼å®å°±ä¼è¿å读åçåèæ°ï¼è¿ç§ä¸è¬æ åµçä¸ä¸ªä¾åå°±æ¯Readerå¨è¾å ¥æµç»ææ¶ä¼è¿åä¸ä¸ªéé¶çåèæ°ï¼å¯è½çè¿åä¸æ¯err==EOFå°±æ¯err==nilãæ 论å¦ä½ï¼ä¸ä¸ä¸ªReadé½åºå½è¿å0ãEOFã
è°ç¨è åºå½æ»å¨èèå°é误erråå¤çn0çåèãè¿æ ·åå¯ä»¥å¨è¯»åä¸äºåèï¼ä»¥åå 许çEOFè¡ä¸ºåæ£ç¡®å°å¤çI/Oé误ã
Readçå®ç°ä¼é»æ¢è¿åé¶åèç计æ°åä¸ä¸ªnilé误ï¼è°ç¨è åºå°è¿ç§æ åµè§ä½ç©ºæä½ã
ReaderFromæ¥å£çå®ä¹ï¼å°è£ äºåºæ¬çReadFromæ¹æ³ã
ReadFromä»rä¸è¯»åæ°æ®å°å¯¹è±¡çæ°æ®æµä¸ï¼ç´å°rè¿åEOFæråºç°è¯»åé误为æ¢ï¼è¿åå¼næ¯è¯»åçåèæ°ï¼è¿åå¼errå°±æ¯rçè¿åå¼errã
å®ä¹ReaderAtæ¥å£ï¼ReaderAtæ¥å£å°è£ äºåºæ¬çReadAtæ¹æ³
ReadAtä»å¯¹è±¡æ°æ®æµçoffå¤è¯»åºæ°æ®å°pä¸ï¼å¿½ç¥æ°æ®ç读åæéï¼ä»æ°æ®çèµ·å§ä½ç½®å移offå¤å¼å§è¯»åï¼å¦æ对象çæ°æ®æµåªæé¨åå¯ç¨ï¼ä¸è¶³ä»¥å¡«æ»¡pï¼åReadAtå°çå¾ æææ°æ®å¯ç¨ä¹åï¼ç»§ç»åpä¸åå ¥ï¼ç´å°å°p填满ååè¿åã
å¨è¿ç¹ä¸ReadAtè¦æ¯Readæ´ä¸¥æ ¼ï¼è¿å读åçåèæ°nå读åæ¶éå°çé误ï¼å¦ænlen(p)ï¼åéè¦è¿åä¸ä¸ªerrå¼æ¥è¯´æï¼ä¸ºä»ä¹æ²¡æå°p填满ï¼æ¯å¦EOFï¼ï¼å¦æn=len(p)ï¼èä¸å¯¹è±¡çæ°æ®æ²¡æå ¨é¨è¯»å®ï¼åerrå°è¿ånilï¼å¦æn=len(p)ï¼èä¸å¯¹è±¡çæ°æ®åå¥½å ¨é¨è¯»å®ï¼åerrå°è¿åEOFæè nilï¼ä¸ç¡®å®ï¼
fileç±»æ¯å¨oså ä¸çï¼å°è£ äºåºå±çæ件æ述符åç¸å ³ä¿¡æ¯ï¼åæ¶å°è£ äºReadåWriteçå®ç°ã
读åæ件ä¸çæ°æ®ï¼
Writeræ¥å£çå®ä¹ï¼Write()æ¹æ³ç¨äºååºæ°æ®ã
Writeå°len(p)个åèä»pä¸åå ¥å°åºæ¬æ°æ®æµä¸ãå®è¿åä»pä¸è¢«åå ¥çåèæ°nï¼0=n=len(p)ï¼ä»¥åä»»ä½éå°çå¼èµ·åå ¥æååæ¢çé误ãè¥Writeè¿åçnlen(p)ï¼å®å°±å¿ é¡»è¿åä¸ä¸ªénilçé误ãWriteä¸è½ä¿®æ¹æ¤åççæ°æ®ï¼å³ä¾¿å®æ¯ä¸´æ¶çã
Seekeræ¥å£çå®ä¹ï¼å°è£ äºåºæ¬çSeekæ¹æ³ã
Seekerç¨æ¥ç§»å¨æ°æ®ç读åæéï¼Seek设置ä¸ä¸æ¬¡è¯»åæä½çæéä½ç½®ï¼æ¯æ¬¡ç读åæä½é½æ¯ä»æéä½ç½®å¼å§çã
whenceçå«ä¹ï¼
å¦æwhence为0ï¼è¡¨ç¤ºä»æ°æ®çå¼å¤´å¼å§ç§»å¨æé
å¦æwhence为1ï¼è¡¨ç¤ºä»æ°æ®çå½åæéä½ç½®å¼å§ç§»å¨æé
å¦æwhence为2ï¼è¡¨ç¤ºä»æ°æ®çå°¾é¨å¼å§ç§»å¨æé
offsetæ¯æé移å¨çå移é
è¿å移å¨åçæéä½ç½®å移å¨è¿ç¨ä¸éå°çä»»ä½é误
WriterToæ¥å£çå®ä¹ï¼å°è£ äºåºæ¬çWriteToæ¹æ³ã
WriterToå°å¯¹è±¡çæ°æ®æµåå ¥å°wä¸ï¼ç´å°å¯¹è±¡çæ°æ®æµå ¨é¨åå ¥å®æ¯æéå°åå ¥é误为æ¢ãè¿åå¼næ¯åå ¥çåèæ°ï¼è¿åå¼errå°±æ¯wçè¿åå¼errã
å®ä¹WriterAtæ¥å£ï¼WriterAtæ¥å£å°è£ äºåºæ¬çWriteAtæ¹æ³
WriteAtå°pä¸çæ°æ®åå ¥å°å¯¹è±¡æ°æ®æµçoffå¤ï¼å¿½ç¥æ°æ®ç读åæéï¼ä»æ°æ®çèµ·å§ä½ç½®å移offå¤å¼å§åå ¥ï¼è¿ååå ¥çåèæ°ååå ¥æ¶éå°çé误ãå¦ænlen(p)ï¼åå¿ é¡»è¿åä¸ä¸ªerrå¼æ¥è¯´æ为ä»ä¹æ²¡æå°på®å ¨åå ¥
fileç±»æ¯å¨oså ä¸çï¼å°è£ äºåºå±çæ件æ述符åç¸å ³ä¿¡æ¯ï¼åæ¶å°è£ äºReadåWriteçå®ç°ã
ååºæ°æ®å°æ¬å°æ件ï¼
Golangå°æ¥å¿åæ¶è¾åºå°æ§å¶å°åæ件æ¥å¸¸å¼åå½ä¸éè¦å°golangçlogå æå°çæ¥å¿åæ¶è¾åºå°æ§å¶å°åæ件ï¼åºè¯¥å¦ä½è§£å³è¿ä¸ªé®é¢ï¼
logå å¯ä»¥éè¿SetOutput()æ¹æ³æå®æ¥å¿è¾åºçæ¹å¼ï¼Writerï¼ï¼ä½æ¯åªè½æå®ä¸ä¸ªè¾åºçæ¹å¼ï¼Writerï¼ãæ们å©ç¨io.MultiWriter()å°å¤ä¸ªWriteræ¼æä¸ä¸ªWriter使ç¨çç¹æ§ï¼ælog.Println()è¾åºçå 容åæµå°æ§å¶å°åæ件å½ä¸ã
åæå°å
详解golangä¸bufioå çå®ç°åçæè¿ç¨golangåäºä¸ä¸ªå¤çæ件çèæ¬ï¼ç±äºå ¶ä¸æ¶åå°äºæ件读åï¼å¼å§ä½¿ç¨golangä¸çioå ï¼åæ¥åç°golangä¸æä¾äºä¸ä¸ªbufioçå ï¼ä½¿ç¨è¿ä¸ªå å¯ä»¥å¤§å¹ æé«æ件读åçæçï¼äºæ¯å¨ç½ä¸æç´¢åæ ·çæ件读å为ä»ä¹bufioè¦æ¯ioç读åæ´å¿«éå¢ï¼æ ¹æ®ç½ä¸çèµæåé 读æºç ï¼ä»¥ä¸æ¥è¯¦ç»è§£éä¸bufioçé«æå¦ä½å®ç°çã
bufioå ä»ç»?
bufioå å®ç°äºæç¼å²çI/Oãå®å è£ ä¸ä¸ªio.Readeræio.Writeræ¥å£å¯¹è±¡ï¼å建å¦ä¸ä¸ªä¹å®ç°äºè¯¥æ¥å£ï¼ä¸åæ¶è¿æä¾äºç¼å²åä¸äºææ¬I/Oç帮å©å½æ°ç对象ã
以ä¸ä¸ºå®æ¹å çä»ç»ï¼å¨å ¶ä¸æ们è½äºè§£å°çä¿¡æ¯å¦ä¸ï¼
bufioæ¯éè¿ç¼å²æ¥æé«æç
ç®åç说就æ¯ï¼ææ件读åè¿ç¼å²ï¼å åï¼ä¹åå读åçæ¶åå°±å¯ä»¥é¿å æ件系ç»çioä»èæé«é度ãåçï¼å¨è¿è¡åæä½æ¶ï¼å ææ件åå ¥ç¼å²ï¼å åï¼ï¼ç¶åç±ç¼å²åå ¥æ件系ç»ãçå®ä»¥ä¸è§£éæ人å¯è½ä¼è¡¨ç¤ºå°æäºï¼ç´æ¥æå 容-æ件åå 容-ç¼å²-æ件ç¸æ¯ï¼ç¼å²åºå¥½å没æèµ·å°ä½ç¨åãå ¶å®ç¼å²åºç设计æ¯ä¸ºäºåå¨å¤æ¬¡çåå ¥ï¼æåä¸å£æ°æç¼å²åºå 容åå ¥æ件ãä¸é¢ä¼è¯¦ç»è§£é
bufioå°è£ äºio.Readeræio.Writeræ¥å£å¯¹è±¡ï¼å¹¶å建å¦ä¸ä¸ªä¹å®ç°äºè¯¥æ¥å£ç对象
io.Readeræio.Writeræ¥å£å®ç°read()åwrite()æ¹æ³ï¼å¯¹äºå®ç°è¿ä¸ªæ¥å£ç对象é½æ¯å¯ä»¥ä½¿ç¨è¿ä¸¤ä¸ªæ¹æ³ç
bufioå å®ç°åç
bufioæºç åæ
Reader对象
bufio.Readeræ¯bufioä¸å¯¹io.Readerçå°è£
//Readerimplementsbufferingforanio.Readerobject.
typeReaderstruct{
buf[]byterd?io.Reader//readerprovidedbytheclientr,wint//bufreadandwritepositionserrerrorlastByte?intlastRuneSizeint}
bufio.Read(p[]byte)ç¸å½äºè¯»å大å°len(p)çå 容ï¼æè·¯å¦ä¸ï¼
å½ç¼ååºæå 容çæ¶ï¼å°ç¼ååºå å®¹å ¨é¨å¡«å ¥på¹¶æ¸ ç©ºç¼ååº
å½ç¼ååºæ²¡æå 容çæ¶åä¸len(p)len(buf),å³è¦è¯»åçå 容æ¯ç¼ååºè¿è¦å¤§ï¼ç´æ¥å»æ件读åå³å¯
å½ç¼ååºæ²¡æå 容çæ¶åä¸len(p)len(buf),å³è¦è¯»åçå 容æ¯ç¼ååºå°ï¼ç¼ååºä»æ件读åå 容å 满ç¼ååºï¼å¹¶å°p填满ï¼æ¤æ¶ç¼ååºæå©ä½å 容ï¼
以åå次读åæ¶ç¼ååºæå 容ï¼å°ç¼ååºå å®¹å ¨é¨å¡«å ¥på¹¶æ¸ ç©ºç¼ååºï¼æ¤æ¶åæ åµ1ä¸æ ·ï¼
以ä¸æ¯æºç
//Readreadsdataintop.
//Itreturnsthenumberofbytesreadintop.
//ThebytesaretakenfromatmostoneReadontheunderlyingReader,
//hencenmaybelessthanlen(p).
//AtEOF,thecountwillbezeroanderrwillbeio.EOF.
func(b*Reader)Read(p[]byte)(nint,errerror){
n=len(p)ifn==0{return0,b.readErr()
}ifb.r==b.w{ifb.err!=nil{
return0,b.readErr()}
iflen(p)=len(b.buf){
//Largeread,emptybuffer.//Readdirectlyintoptoavoidcopy.n,b.err=b.rd.Read(p)ifn0{panic(errNegativeRead)
}ifn0{b.lastByte=int(p[n-1])
b.lastRuneSize=-1
}returnn,b.readErr()}
//Oneread.
//Donotuseb.fill,whichwillloop.
b.r=0
b.w=0
n,b.err=b.rd.Read(b.buf)
ifn0{
panic(errNegativeRead)}
ifn==0{
return0,b.readErr()}
b.w+=n
}//copyasmuchaswecann=copy(p,b.buf[b.r:b.w])b.r+=nb.lastByte=int(b.buf[b.r-1])b.lastRuneSize=-1returnn,nil}
说æï¼
readerå é¨éè¿ç»´æ¤ä¸ä¸ªr,wå³è¯»å ¥ååå ¥çä½ç½®ç´¢å¼æ¥å¤ææ¯å¦ç¼ååºå å®¹è¢«å ¨é¨è¯»åº
Writer对象
bufio.Writeræ¯bufioä¸å¯¹io.Writerçå°è£
//Writerimplementsbufferingforanio.Writerobject.
typeWriterstruct{
errerrorbuf[]byten?intwrio.Writer}
bufio.Write(p[]byte)çæè·¯å¦ä¸
å¤æbufä¸å¯ç¨å®¹éæ¯å¦å¯ä»¥æ¾ä¸p
å¦æè½æ¾ä¸ï¼ç´æ¥æpæ¼æ¥å°bufåé¢ï¼å³æå 容æ¾å°ç¼å²åº
å¦æç¼å²åºçå¯ç¨å®¹éä¸è¶³ä»¥æ¾ä¸ï¼ä¸æ¤æ¶ç¼å²åºæ¯ç©ºçï¼ç´æ¥æpåå ¥æ件å³å¯
å¦æç¼å²åºçå¯ç¨å®¹éä¸è¶³ä»¥æ¾ä¸ï¼ä¸æ¤æ¶ç¼å²åºæå 容ï¼åç¨pæç¼å²åºå¡«æ»¡ï¼æç¼å²åºææå 容åå ¥æ件ï¼å¹¶æ¸ 空ç¼å²åº
å¤æpçå©ä½å 容大å°è½å¦æ¾å°ç¼å²åºï¼å¦æè½æ¾ä¸ï¼æ¤æ¶åæ¥éª¤1æ åµä¸æ ·ï¼åæå 容æ¾å°ç¼å²åº
å¦æpçå©ä½å 容ä¾æ§å¤§äºç¼å²åºï¼ï¼æ³¨ææ¤æ¶ç¼å²åºæ¯ç©ºçï¼æ åµåæ¥éª¤2ä¸æ ·ï¼åæpçå©ä½å 容ç´æ¥åå ¥æ件
//Writewritesthecontentsofpintothebuffer.
//Itreturnsthenumberofbyteswritten.
//Ifnnlen(p),italsoreturnsanerrorexplaining
//whythewriteisshort.
func(b*Writer)Write(p[]byte)(nnint,errerror){
forlen(p)b.Available()b.err==nil{varnint
ifb.Buffered()==0{
//Largewrite,emptybuffer.//Writedirectlyfromptoavoidcopy.n,b.err=b.wr.Write(p)}else{
n=copy(b.buf[b.n:],p)b.n+=nb.flush()}
nn+=n
p=p[n:]
}ifb.err!=nil{returnnn,b.err
}n:=copy(b.buf[b.n:],p)b.n+=nnn+=nreturnnn,nil}
说æï¼
b.wråå¨çæ¯ä¸ä¸ªio.writer对象ï¼å®ç°äºWrite()çæ¥å£ï¼æ以å¯ä»¥ä½¿ç¨b.wr.Write(p)å°pçå 容åå ¥æ件
b.flush()ä¼å°ç¼ååºå 容åå ¥æ件ï¼å½ææåå ¥å®æåï¼å 为ç¼ååºä¼åå¨å 容ï¼æ以éè¦æå¨flush()å°æ件
b.Available()为bufå¯ç¨å®¹éï¼çäºlen(buf)-n
ä¸å¾è§£éçæ¯å ¶ä¸ä¸ç§æ åµï¼å³ç¼ååºæå 容ï¼å©ä½p大äºç¼ååº
golangæ件æä½ææ
è¯è æï¼renameåmoveåçä¸æ ·
è¯è æï¼çæLinuxç读è åºè¯¥å¾çææé模å¼ï¼éè¿Linuxå½ä»¤chmodå¯ä»¥æ´æ¹æ件çæé
è¡¥å äºåææªä»ç»çflag
ä¸ä¸ªæ®éçæ件æ¯ä¸ä¸ªæå硬ççinodeçå°æ¹ã硬é¾æ¥å建ä¸ä¸ªæ°çæéæååä¸ä¸ªå°æ¹ãåªæææçé¾æ¥è¢«å é¤åæ件æä¼è¢«å é¤ã硬é¾æ¥åªå¨ç¸åçæ件系ç»ä¸æå·¥ä½ãä½ å¯ä»¥è®¤ä¸ºä¸ä¸ªç¡¬é¾æ¥æ¯ä¸ä¸ªæ£å¸¸çé¾æ¥ã
symboliclinkï¼åå«è½¯è¿æ¥ï¼å硬é¾æ¥æç¹ä¸ä¸æ ·ï¼å®ä¸ç´æ¥æå硬çä¸çç¸åçå°æ¹ï¼èæ¯éè¿ååå¼ç¨å ¶å®æ件ãä»ä»¬å¯ä»¥æåä¸åçæ件系ç»ä¸çä¸åæ件ã并ä¸æ¯ææçæä½ç³»ç»é½æ¯æ软é¾æ¥ã
å¤å¶æ件
å¯ä»¥ä½¿ç¨oså åå ¥ä¸ä¸ªæå¼çæ件ãå 为Goå¯æ§è¡å æ¯éæé¾æ¥çå¯æ§è¡æ件ï¼ä½ importçæ¯ä¸ä¸ªå é½ä¼å¢å ä½ çå¯æ§è¡æ件ç大å°ãå ¶å®çå å¦ioãï½ioutilï½ãï½bufioï½æä¾äºä¸äºæ¹æ³ï¼ä½æ¯å®ä»¬ä¸æ¯å¿ é¡»çã
ioutilå æä¸ä¸ªé常æç¨çæ¹æ³WriteFile()å¯ä»¥å¤çå建ï¼æå¼æ件ãååèsliceåå ³éæ件ä¸ç³»åçæä½ãå¦æä½ éè¦ç®æ´å¿«éå°ååèsliceå°æ件ä¸ï¼ä½ å¯ä»¥ä½¿ç¨å®ã
bufioå æä¾äºå¸¦ç¼ååè½çwriterï¼æä»¥ä½ å¯ä»¥å¨ååèå°ç¡¬çå使ç¨å åç¼åãå½ä½ å¤çå¾å¤çæ°æ®å¾æç¨ï¼å 为å®å¯ä»¥èçæä½ç¡¬çI/Oçæ¶é´ãå¨å ¶å®ä¸äºæ åµä¸å®ä¹å¾æç¨ï¼æ¯å¦ä½ æ¯æ¬¡åä¸ä¸ªåèï¼æå®ä»¬æå¨å åç¼åä¸ï¼ç¶åä¸æ¬¡åå ¥å°ç¡¬çä¸ï¼åå°ç¡¬çç磨æ以åæåæ§è½ã
读åæå¤N个åè
os.Fileæä¾äºæ件æä½çåºæ¬åè½ï¼èioãioutilãbufioæä¾äºé¢å¤çè¾ å©å½æ°ã
æç¼ååä¹æç¼å读ãç¼åreaderä¼æä¸äºå 容ç¼åå¨å åä¸ãå®ä¼æä¾æ¯os.Fileåio.Readeræ´å¤çå½æ°,缺ççç¼å大å°æ¯ï¼æå°ç¼åæ¯ã
Scanneræ¯bufioå ä¸çç±»å,å¨å¤çæ件ä¸ä»¥åé符åéçææ¬æ¶å¾æç¨ãé常æ们使ç¨æ¢è¡ç¬¦ä½ä¸ºåé符å°æ件å 容åæå¤è¡ãå¨CSVæ件ä¸ï¼éå·ä¸è¬ä½ä¸ºåé符ãos.Fileæ件å¯ä»¥è¢«å è£ æbufio.Scannerï¼å®å°±åä¸ä¸ªç¼åreaderãæ们ä¼è°ç¨Scan()æ¹æ³å»è¯»åä¸ä¸ä¸ªåé符ï¼ä½¿ç¨Text()æè Bytes()è·å读åçæ°æ®ã
åé符å¯ä»¥ä¸æ¯ä¸ä¸ªç®åçåèæè å符ï¼æä¸ä¸ªç¹æ®çæ¹æ³å¯ä»¥å®ç°åé符çåè½ï¼ä»¥åå°æé移å¨å¤å°ï¼è¿åä»ä¹æ°æ®ãå¦æ没æå®å¶çSplitFuncæä¾ï¼ç¼ºççScanLinesä¼ä½¿ç¨newlineå符ä½ä¸ºåé符ï¼å ¶å®çåéå½æ°è¿å æ¬ScanRunesåScanWords,çå¨bufioå ä¸ã
æå (zip)æ件
å ¶å®
临æ¶æ件åç®å½
ioutilæä¾äºä¸¤ä¸ªå½æ°:TempDir()åTempFile()ã使ç¨å®æ¯åï¼è°ç¨è è´è´£å é¤è¿äºä¸´æ¶æ件åæ件夹ãæä¸ç¹å¥½å¤å°±æ¯å½ä½ ä¼ éä¸ä¸ªç©ºå符串ä½ä¸ºæ件夹åçæ¶åï¼å®ä¼å¨æä½ç³»ç»ç临æ¶æ件夹ä¸å建è¿äºé¡¹ç®ï¼/tmponLinuxï¼ãos.TempDir()è¿åå½åæä½ç³»ç»ç临æ¶æ件夹ã
ä¸é¢çä¾åå¤å¶æ´ä¸ªæ件å 容å°å åä¸ï¼ä¼ éç»hashå½æ°ãå¦ä¸ä¸ªæ¹å¼æ¯å建ä¸ä¸ªhashwriter,使ç¨WriteãWriteStringãCopyå°æ°æ®ä¼ ç»å®ãä¸é¢çä¾å使ç¨md5hash,ä½ä½ å¯ä»¥ä½¿ç¨å ¶å®çWriterã
èègolangçzapçZapKafkaWriteræ¬æ主è¦ç 究ä¸ä¸golangçzapçZapKafkaWriter
WriteSyncerå åµäºio.Writeræ¥å£ï¼å®ä¹äºSyncæ¹æ³ï¼Sinkæ¥å£å åµäºzapcore.WriteSynceråio.Closeræ¥å£ï¼ZapKafkaWriterå®ç°Sinkæ¥å£åzapcore.WriteSynceræ¥å£ï¼å ¶Writeæ¹æ³ç´æ¥å°dataéè¿kafkaåéåºå»ã
golang context的使用和源码分析
context是golang的标准库,用于在多个goroutine之间传递上下文信息,方便进行协调与通信。通过context,可以实现goroutine之间的取消操作、数据传递等功能。
context的使用相对简单,主要接口包括Background()、TODO()、WithCancel、WithDeadline、WithTimeout、WithValue等。在需要传递context的goroutine中,可调用这些接口生成相应的context。
WithCancel、WithDeadline、WithTimeout接口均用于协调goroutine间的操作,返回CancelFunc函数,用于取消context。使用时需调用cancel()函数来取消context。通过select ctx.Done()可判断context是否取消。
WithValue接口用于在goroutine间传递key-value数据。获取数据时,会从当前context逐级向上查找,直至找到父context为止。
context内部结构设计精巧,包含cancel类与value类。cancel类通过propagateCancel()函数建立与parent context的关系,使用Done()与cancel()方法进行context的取消操作。value类用于在goroutine间传递数据,实现层级查找与数据传递。
深入源码分析,可以更好地理解context的内部机制与实现细节。通过官方文档与相关代码解析资料,可以深入了解context的原理与实践应用。
Golang源码分析Golang如何实现自举(一)
本文旨在探索Golang如何实现自举这一复杂且关键的技术。在深入研究之前,让我们先回顾Golang的历史。Golang的开发始于年,其编译器在早期阶段是由C语言编写。直到Go 1.5版本,Golang才实现了自己的编译器。研究自举的最佳起点是理解从Go 1.2到Go 1.3的版本,这些版本对自举有重要影响,后续还将探讨Go 1.4。
接下来,我们来了解一下Golang的编译过程。Golang的编译主要涉及几个阶段:词法解析、语法解析、优化器和生成机器码。这一过程始于用户输入的“go build”等命令,这些命令实际上触发了其他内部命令的执行。这些命令被封装在环境变量GOTOOLDIR中,具体位置因系统而异。尽管编译过程看似简单,但实际上包含了多个复杂步骤,包括词法解析、语法解析、优化器、生成机器码以及连接器和buildid过程。
此外,本文还将介绍Golang的目录结构及其功能,包括API、文档、C头文件、依赖库、源代码、杂项脚本和测试目录。编译后生成的文件将被放置在bin和pkg目录中,其中bin目录包含go、godoc和gofmt等文件,pkg目录则包含动态链接库和工具命令。
在编译Golang时,首先需要了解如何安装GCC环境。为了确保兼容性,推荐使用GCC 4.7.0或4.7.1版本。通过使用Docker镜像简化了GCC的安装过程,使得编译变得更为便捷。编译Golang的命令相对简单,通过执行./all即可完成编译过程。
最后,本文对编译文件all.bash和make.bash进行了深入解析。all.bash脚本主要针对nix系统执行,而make.bash脚本则包含了编译过程的关键步骤,包括设置SELinux、编译dist文件、编译go_bootstrap文件,直至最终生成Golang可执行文件。通过分析这些脚本,我们可以深入了解Golang的自举过程,即如何通过go_bootstrap文件来编译生成最终的Golang。
总结而言,Golang的自举过程是一个复杂且多步骤的技术,包含了从早期C语言编译器到自动生成编译器的转变。通过系列文章的深入探讨,我们可以更全面地理解Golang自举的实现细节及其背后的逻辑。本文仅是这一过程的起点,后续将详细解析自举的关键组件和流程。
2024-11-29 23:45
2024-11-29 23:07
2024-11-29 23:01
2024-11-29 22:56
2024-11-29 22:07