Go Cond 学习

TT
为等待 / 通知场景下的并发问题提供支持。Cond 通常应用于等待某个条件的一组 goroutine,等条件变为 true 的时候,其中一个 goroutine 或者所有的 goroutine 都会被唤醒执行。 说点人话吧…… cond 分析 我们来看一下 Cond 提供的方法 func NewCond(l Locker) *Cond {} // 创建一个 cond func (c *Cond) Wait() {} // 阻塞,等待唤醒 func (c *Cond) Signal() {} // 唤醒一个等待者 func (c *Cond) Broadcast() {} // 唤醒所有等待者 第一步:创建一个 cond c := sync.NewCond(&sync.Mutex{}) 第二步:将 goroutine 阻塞在 c 上 // 这里会有坑,下文再讨论 c.Wait() 第三步:唤醒 // 唤醒所有等待者 c.Broadcast() // 唤醒一个等待者 c.Signal() 这里再回过头去看 cond 的作用,应该清晰了不少,我们再结合一个例子来看一下。

Go sync.Once 学习

TT
Once 常常用来初始化单例资源,或者并发访问只需初始化一次的共享资源,或者在测试的时候初始化一次测试资源。 如何初始化单例 1. 定义 package 级别的变量 package test var pi = 3.1415926 2. 在 init() 函数中初始化 package test var pi float64 func init() { pi = 3.1415926 } 3. 在 main 函数中执行初始化逻辑 package test var pi float64 func initApp() { pi = 3.1415926 } func main() { initApp() } 以上三种方法都是线程安全的,后两种方法甚至可以提供定制化的初始化服务,但是它们有一个共同的缺点:不能实现延迟初始化。 延迟初始化 // 使用互斥锁保证线程(goroutine)安全 var mu sync.Mutex var pi float64 func getPI() float64 { mu.Lock() defer mu.Unlock() if pi != 0 { return pi } pi = 3.

Go noCopy 实现禁止拷贝

TT
话不多说,直接上码: // noCopy may be embedded into structs which must not be copied // after the first use. // See https://golang.org/issues/8005#issuecomment-190753527 // for details. type noCopy struct{} // Lock is a no-op used by -copylocks checker from `go vet`. func (*noCopy) Lock() {} func (*noCopy) Unlock() {} 意思就是说,如果你想禁止一个 struct 被拷贝,只需把 noCopy 嵌入即可,go vet 命令会做禁止拷贝的检查。 package main func main() { var t = TestNoCopy{} Test(t) } type noCopy struct{} func (*noCopy) Lock() {} func (*noCopy) Unlock() {} type TestNoCopy struct { noCopy } func Test(t TestNoCopy) {} 将上述代码保存为 main.

Go 定时任务

TT
说起 Go 的定时任务,不得不学习一波 robfig/cron 包,github地址 1. 使用 Demo 1.1 每秒钟执行一次 package main import ( "fmt" "time" "github.com/robfig/cron/v3" ) func main() { job := cron.New( cron.WithSeconds(), // 添加秒级别支持,默认支持最小粒度为分钟 ) // 每秒钟执行一次 job.AddFunc("* * * * * *", func() { fmt.Printf("secondly: %v\n", time.Now()) }) job.Run() // 启动 } cron 表达式格式可以自行百度,这里不再赘述。 需要强调的是,cron 默认支持到分钟级别,如果需要支持到秒级别,在初始化 cron 时,记得 cron.WithSeconds() 参数。 1.2 每分钟执行一次 // 每分钟执行一次 job.AddFunc("0 * * * * *", func() { fmt.Printf("minutely: %v\n", time.Now()) }) 1.

Go 数据结构对齐

TT
理解的不深,把自己得到的结论以及参考的博文记录了下来,日常开发中注意 struct 字段的排序,记住这么多也足够了。 什么是数据结构对齐 维基百科:数据结构对齐是代码编译后在内存的布局与使用方式。包括三方面内容:数据对齐、数据结构填充(padding)与包入(packing)。 挺模糊的定义,我就简单理解为:数据在内存中的存储位置,不是那么单纯地一个挨一个,或是为了性能,或是为了安全,产生了一系列分布规则。 为什么要进行对齐 要解释为什么需要对齐,需要先说明一下 CPU 访问内存的方式:以字长(machine word)为单位访问,而非以字节(Byte)为单位访问。 也就是说 32 位 CPU 以 32 位(4字节)为单位访问内存,64 位 CPU 以 64 位(8字节)为单位访问内存。 知道了 CPU 访问内存的方式,如果不进行内存对齐,会增加 CPU 访问内存的次数,严重时会触发总线错误 32 位操作系统下,a、b 各占 3 字节,结合 CPU 的访问方式来看,在内存未对齐时,若访问 b 元素,需要进行两次内存访问。内存对齐后,a、b 各占 4 字节,此时访问 b 元素,只需要一次内存访问。 Go 内存对齐 对齐保证 可以通过 unsafe 包的 Alignof() 方法查看对齐保证,示例如下: var i8 int8 = 1 align := unsafe.Alignof(i8) var i32 int32 = 1 align = unsafe.Alignof(i32) type alignment guarantee bool, byte, uint8, int8 1 uint16, int16 2 uint32, int32 4 float32, complex64 4 arrays depend on element types structs depend on field types other types size of a native word 表格数据来源 概括来说就是:

Go 指针

TT
普通指针:*T 对于任意类型 T,它所对应的指针类型就是 *T var i int var ip *int var s string var sp *string Go 是强类型,不同类型对应的 *T 不可相互转换、不可相互赋值、不可做比较、普通指针不可参与计算。 万能指针:unsafe.Pointer unsafe.Pointer 与 *T 的关系,就好比 interface{} 和 T 的关系,也就是说 unsafe.Pointer 可以承载任意类型的 *T,它们之间可以互相转换,这就赋予了开发者直接操作指定内存的权力(效果不明显,配合 uintptr 服用效果最佳) var i int = 1 pointer := unsafe.Pointer(&i) // *int -> pointer p := (*int)(pointer) // pointer -> *int *p = 2 fmt.Println(i) // 2 unsafe.Pointer 提供的操作 // Pointer(*T) 将 *T 转化为 Pointer,也是结构体对应的内存的开始地址 type Pointer *ArbitraryType // Sizeof(T) 返回 T 占用字节数 func Sizeof(x ArbitraryType) uintptr // Offsetof 返回结构体成员在内存中的位置离结构体起始处的字节数,所传参数必须是结构体的成员 func Offsetof(x ArbitraryType) uintptr 魔幻指针:uintptr uintptr 解除了指针无法参与计算的封禁。官方对其定义为:

如何通过“功能选项”来设计友好的 API

TT
场景假设 你作为公司的顶梁柱,要编写一个服务器组件 阶段一:上手就撸版本 type Server struct { listener net.Listener } func (s *Server) Addr() net.Addr func (s *Server) Shutdown() func NewServer(addr string) (*Server, error) { l, err := net.Listen("tcp", addr) if err != nil { return nil, err } srv := Server{listener: l} go srv.run() return &srv, nil } 优点 三分钟即可上线,啥都不管,无脑 NewServer() 就完事了 缺点 一切皆为默认,想要定制是不可能的 阶段二:巨多参数版本 新的需求来了: 我要限制最大连接数 我要设置超时时间 我要设置 tls 证书 我要我还要…… 那就增加点参数,整个可配置版吧 // NewServer returns a new Server listening on addr.

Go Mutex 源码浅析 & Mutex 演变历程

TT
文章的内容参考了极客时间专栏《Go 并发编程实战课》,结合自己的理解写了一篇总结。 作者将 Mutex 的演进划分成了 4 个阶段: 初版 使用一个 key 字段标记是否持有锁,以及等待该锁的 goroutine 数量 源码下载地址 部分代码片段如下: package sync import "runtime" // CAS操作,当时还没有抽象出atomic包 func cas(val *uint32, old, new uint32) bool // 互斥锁的结构,包含两个字段 type Mutex struct { key uint32; // 锁是否被持有的标识,0:锁未被持有;1:锁被持有,且没有其它等待者;n:锁被持有,同时还有 n-1 个竞争者 sema uint32; //信号量专用,用以阻塞/唤醒goroutine } // 保证成功在val上增加delta的值 func xadd(val *uint32, delta int32) (new uint32) { for { v := *val; nv := v+uint32(delta); if cas(val, v, nv) { return nv; } } panic("unreached"); } // 请求锁 func (m *Mutex) Lock() { if xadd(&m.

Go go:linkname 是个啥

TT
问题发现 今天阅读 golang 源码时,看到一个函数 runtime_canSpin 大概意思是判断能否进行自旋,想查看它的实现,idea 跳转进去以后在 sync/runtime.go 文件里只看到了函数的定义 // Active spinning runtime support. // runtime_canSpin reports whether spinning makes sense at the moment. func runtime_canSpin(i int) bool 并没有函数的实现,同目录下也没有找到汇编代码的实现,那函数的具体实现在哪里? 找答案 clone 了 golang 源码,全局搜索 runtime_canSpin,在 runtime/proc.go 文件找到了如下的代码块: // Active spinning for sync.Mutex. //go:linkname sync_runtime_canSpin sync.runtime_canSpin //go:nosplit func sync_runtime_canSpin(i int) bool { // sync.Mutex is cooperative, so we are conservative with spinning. // Spin only few times and only if running on a multicore machine and // GOMAXPROCS>1 and there is at least one other running P and local runq is empty.

Go Context

TT
文中例子参考:https://www.flysnow.org/2017/05/12/go-in-action-go-context.html 1. 使用 chan + select 控制 goroutine 停止 package main import ( "fmt" "time" ) func main() { stop := make(chan bool) go func() { for { select { case value1, ok1:= <-stop: fmt.Println(value1, ok1) fmt.Println("监控退出,停止了...1") return default: fmt.Println("goroutine监控中...1") time.Sleep(2 * time.Second) } } }() go func() { for { select { case value2, ok2:= <-stop: fmt.Println(value2, ok2) fmt.Println("监控退出,停止了...2") return default: fmt.Println("goroutine监控中...2") time.Sleep(2 * time.Second) } } }() time.