20240220
问题
记录
- 如果某个包被多次导入的话,在执行的时候只会导入一次。当一个包被导入时,如果它还导入了其它的包,则先将其它的包包含进来,然后创建和初始化这个包的常量和变量,再调用包里的
init
函数,如果一个包有多个init
函数的话,调用顺序未定义(实现可能是以文件名的顺序调用),同一个文件内的多个init
则是以出现的顺序依次调用(init
不是普通函数,可以定义有多个,所以也不能被其它函数调用) - 要注意的是,在
main.main
函数执行之前所有代码都运行在同一个goroutine,也就是程序的主系统线程中。因此,如果某个init
函数内部用go关键字启动了新的goroutine的话,新的goroutine只有在进入main.main
函数之后才可能被执行到。 - Go语言中的函数可以有多个参数和多个返回值,参数和返回值都是以传值的方式和被调用者交换数据。在语法上,函数还支持可变数量的参数,可变数量的参数必须是最后出现的参数,可变数量的参数其实是一个切片类型的参数。
- 及时赋值中间变量
- func main() {
for i := 0; i < 3; i++ {
i := i // 定义一个循环体内局部变量i
defer func(){ println(i) } ()
}
}
func main() {
for i := 0; i < 3; i++ {
// 通过函数传入i
// defer 语句会马上对调用参数求值
defer func(i int){ println(i) } (i)
}
}
- 因为切片中的底层数组部分是通过隐式指针传递(指针本身依然是传值的,但是指针指向的却是同一份的数据),所以被调用函数是可以通过指针修改掉调用参数切片中的数据。除了数据之外,切片结构还包含了切片长度和切片容量信息,这2个信息也是传值的。如果被调用函数中修改了
Len
或Cap
信息的话,就无法反映到调用参数的切片中,这时候我们一般会通过返回修改后的切片来更新之前的切片。这也是为何内置的append
必须要返回一个切片的原因。
总结
20240221
问题
Csp, Actor 不都是通过消息传递吗有什么区别
Csp 的 channel 规定了传输的类型,如果希望传输多个类型的数据,还需要创建多个 channel。既细化了传输内容,也增加了复杂度。
Actor 希望解决的问题范围更广?万物皆是 actor?
关于并发模型 Actor 和 CSP - 割肉机 - 博客园 (cnblogs.com)
记录
- 通过接口的引入其他方法,从而实现接口工厂
- 并发模型; shared memory, csp, Actor
总结
20240225
记录
- buffers与cached不同:Buffers是存储在页缓存下的数据的磁盘块表示形式,Buffers包含驻留在页缓存(Page Cache)下的文件/数据的元数据(metadata),通常指的是目录项(dentries)和索引节点(inode)。而cached是用来给文件/数据做缓冲。更通俗一点说,cached里面存储的是数据,buffers里面存储的是数据在磁盘块表示形式。
- 查看cached和buffers大小:通过cat /proc/meminfo命令可以查看cached和buffers的大小,
问题
如果缓存会自动释放,为什么之前写缓冲区不会自己释放?
系统的会自己释放,如果是自己创建的缓冲区,需要自己管理,所以不会自己释放。如果启动了某个进程,且从进程获取返回值,应该注意该进程的返回值问题。
如果A进程先把数据写入BUFFER,此时宕机了,BUFFER还存在吗,数据会丢失吗
梳理
20240226
记录
首先,每个系统级线程都会有一个固定大小的栈(一般默认可能是2MB),这个栈主要用来保存函数递归调用时参数和局部变量。固定了栈的大小导致了两个问题:一是对于很多只需要很小的栈空间的线程来说是一个巨大的浪费,二是对于少数需要巨大栈空间的线程来说又面临栈溢出的风险。针对这两个问题的解决方案是:要么降低固定的栈大小,提升空间的利用率;要么增大栈的大小以允许更深的函数递归调用,但这两者是没法同时兼得的。相反,一个Goroutine会以一个很小的栈启动(可能是2KB或4KB),当遇到深度递归导致当前栈空间不足时,Goroutine会根据需要动态地伸缩栈的大小(主流实现中栈的最大值可达到1GB)。因为启动的代价很小,所以我们可以轻易地启动成千上万个Goroutine。
问题
线程和协程的区别
- 初始值大小
- 伸缩性
- 线程是内核调度的最小单位,协程是编程者控制的
- 切换时会保存寄存器上下文和栈
- 协程是非抢占式的,由编程者调度。线程是抢占 CPU 的。
总结
2024-02-27
记录
- sync/atomic提供更細类型的原子操作,例如atomic.AddUint64(&total, i)
- 原子操作配合互斥锁可以实现非常高效的单件模式。互斥锁的代价比普通整数的原子读写高很多,在性能敏感的地方可以增加一个数字型的标志位,通过原子检测标志位状态降低互斥锁的使用次数来提高性能。
- 通过 sync/once + lock 可以一定程度解决高并发下的性能
type singleton struct {}
var (
instance *singleton
initialized uint32
mu sync.Mutex
)
func Instance() *singleton {
if atomic.LoadUint32(&initialized) == 1 {
return instance
}
mu.Lock()
defer mu.Unlock()
if instance == nil {
defer atomic.StoreUint32(&initialized, 1)
instance = &singleton{}
}
return instance
}
var (
instance *singleton
once sync.Once
)
func Instance() *singleton {
once.Do(func() {
instance = &singleton{}
})
return instance
}