一、简介
本篇文章会介绍go 开发小技巧。
二、go 开发技巧
2.1 Semaphore
type Semaphore chan struct{}func NewSemaphore(maxCount int) Semaphore {return make(chan struct{}, maxCount)
}func (s Semaphore) Acquire() {s <- struct{}{}
}func (s Semaphore) tryAcquire() bool{select {case s <- struct{}{}:default:return false}return true
}func (s Semaphore) Release() {<-s
}
2.2 singleflight
有点类似react的useMemo hook,会缓存函数结果
type SingleFlight struct {m map[string]*call
}type call struct {sync.Onceres any
}func newSingleFlight() *SingleFlight {return &SingleFlight{m: make(map[string]*call),}
}func (sf *SingleFlight) Do(key string, fn func() (any, error)) (any, error) {if sf.m[key] != nil {return sf.m[key].res, nil}ca := &call{}var err errorca.Once.Do(func() {if res, e := fn(); e == nil {ca.res = reserr = esf.m[key] = ca}})return ca.res, err
}
demo
func main() {var sf = newSingleFlight()var wg sync.WaitGroupvar t = time.Now()for i := 0; i < 10; i++ {wg.Add(1)go func() {res, _ := sf.Do("longFunc", func() (any, error) {time.Sleep(5 * time.Second)return 5, nil})fmt.Println(res)wg.Done()}()}wg.Wait()fmt.Println(time.Since(t))
}
2.3 once
once 可以用来处理只需要之心一次的结果
var (once sync.Onceinstance *Config
)func GetConfig() *Config {once.Do(func() {instance = loadConfig()})return instance
}
2.4 error group
err group 可以在调用线程获取并发执行goroute 的错误
func main() {urls := []string {"https://blog.devtrovert.com","https://example.com",}var g errgroup.Groupfor _, url := range urls {url := url // safe before Go 1.22g.Go(func() error {return fetch(url)})}if err := g.Wait() ; err != nil {log.Fatal(err)}
}
2.5 Pool
Pool是对象池,可以复用对象
type Pool[T any] struct {internal sync.Pool
}func NewPool[T any](newF func() T) *Pool[T] {return &Pool[T]{internal: sync.Pool{New: func() interface{} {return newF()},},}
}func (p *Pool[T]) Get() T {return p.internal.Get().(T)
}func (p *Pool[T]) Put(v T) {p.internal.Put(v)
}
2.6 error
1. 自定义error的粒度是类型,例如参数类型错误,可重试错误。
2.wrap或join。
func readConfig(path string) error {return fmt.Errorf("read config: %w", ErrNotFound)
}func main() {err := readConfig("config.json")if errors.Is(err, ErrNotFound) {fmt.Println("config file not found")}
}
func main() {var errs = make([]error, 30)var g sync.WaitGroupfor i := 0; i < 10; i++ {g.Add(1)j := igo func(i int) {errs = append(errs, errors.New(fmt.Sprintf("hello, %d", i)))defer g.Done()}(j)}g.Wait()fmt.Println(errors.Join(errs...))
}
join 将多个错误连接
2.7 defer
测量函数执行时间
func main() {defer TrackTime(time.Now()) // <--- THIStime.Sleep(500 * time.Millisecond)
}func TrackTime(pre time.Time) time.Duration {elapsed := time.Since(pre)fmt.Println("elapsed:", elapsed)return elapsed
}// elapsed: 501.11125ms
2.8 实现接口判断
interface
type Buffer interface {Write(p []byte) (n int, err error)
}type StringBuffer struct{}
判断StringBuffer 是否实现Buffer
// syntax: var _ <interface> = (*type)(nil)
var _ Buffer = (*StringBuffer)(nil)
2.9 worker pool
package tasksimport "sync"type Worker struct {maxWorker intch chan func()wg sync.WaitGroupresMap map[string]chan any
}func NewWorker(maxWorker int) *Worker {w := &Worker{maxWorker: maxWorker,ch: make(chan func()),resMap: make(map[string]chan any),}for i := 0; i < maxWorker; i++ {go func() {for f := range w.ch {w.done(f)}}()}return w
}func (w *Worker) Go(f func()) {w.wg.Add(1)w.ch <- f
}func (w *Worker) Wait() {w.wg.Wait()close(w.ch) // 关闭通道
}func (w *Worker) done(f func()) {defer w.wg.Done()f()
}func (w *Worker) submit(f func() (res any), key string) chan any {w.resMap[key] = make(chan any)funcWrapper := func() {var res = f()w.resMap[key] <- resclose(w.resMap[key] // 关闭通道}w.Go(funcWrapper)return w.resMap[key]
}
2.10 ConcurrentMap
type ConcurrentMap struct {innerMap map[string]anyrwLock sync.RWMutex
}func (currMap *ConcurrentMap) Get(key string) any {currMap.rwLock.RLock()defer currMap.rwLock.RUnlock()return currMap.innerMap[key]
}func (currMap *ConcurrentMap) Put(key string, value any) {currMap.rwLock.Lock()defer currMap.rwLock.Unlock()currMap.innerMap[key] = value
}func (currMap *ConcurrentMap) PutIfAbsent(key string, supplier func() any) {currMap.rwLock.Lock()defer currMap.rwLock.Unlock()if value := currMap.innerMap[key]; value != nil {return}currMap.innerMap[key] = supplier()
}
func NewCurrMap() *ConcurrentMap {return &ConcurrentMap{innerMap: make(map[string]any),}
}func (currMap *ConcurrentMap) getOrDefault(key string, supplier func() any) any {currMap.rwLock.RLock()defer currMap.rwLock.RUnlock()if value := currMap.innerMap[key]; value != nil {return value}return supplier()
}