Go的一些总结

基 本 概 念 \red{基本概念}

  • go跟在特定符号后的换行符会被转换成分号,所以go要求有一定格式
  • go编译器判断变量使用堆或栈内存时,并不从new var判断,是从引用树来判断。
  • 在往string迭代添加字符时,使用str+=char性能很慢(大量gc),尽量替换成strings.Join
  • string内容不可被修改,数组可以,但数组长度不能改变,切片都可以。
  • iota在多常量定义时非常好用
  • slice和vector类似,超出容量后2倍扩容。
  • go外层结构体变量会覆盖匿名成员中同名变量,同样外层方法会覆盖内层方法
  • 结构体的json tag在没有设定时,默认域名的markdown格式。
  • 同一个作用域的函数不能重名
  • 在打印自定义err时,使用fmt.Errorf()可以格式化输出,errors.New()只能输出特定字符串。
  • 动词"%*s"需要传入两个参数,数量a和重复元素b,可以格式化输出比如多个连续空格。
  • 类的nil指针也可以调用其方法,但用nil时,不能调用匿名成员的方法
  • go接口类型,成员内嵌和类型断言很好支持面向对象特性
  • 通道在容量满时再发送会阻塞,无缓冲通道发送会立即阻塞,尽量保证在bug时,chan可以关闭(defer),同样,可以通过关闭chan来向所有消费者goroutinue广播。
  • 单向通道类型保证了函数不会误操作管道
  • 对于chan,len的价值很低,变化很快。cap则有价值,在内存中如果无法提供该cap的chan,会导致死锁。
  • 尽管chan和队列很像,但不要用chan充当simple queue。chan与go调度器深度关联,有永久阻塞的风险。同理select用来监听chan,switch是简单的多分支。
  • goroutine 并发数量会受到,CPU核数,IO操作数,网络带宽,web服务本身的容量四个方面的限制,所以goroutine过度并发也不会提高性能,一般通过goroutine数量阈值来限制(令牌chan)。
  • go也存在三种竞态问题,死锁(资源互斥,占有且等待,不可抢占,循环等待),活锁(不同于死锁,可能自行解开),饿死(级别太低)
  • go中map不是并发安全的,但只读map是并发安全的(一般都会用上RWMutex,支持高并发读),如果需要频繁读写map,一般会增加一个唯一goroutine+chan来封装map,例如读,写,原子操作等。
  • 互斥锁的本质是一个容量为1的缓冲chan,同理defer+unlock是很好的习惯。
  • goroutine没有标识,go调度器通过GOMAXPROCS参数来确定使用多少个OS线程,一般这个值是CPU的核数。
  • 在运行时动态获取一个变量的类型和值,就叫反射。断言一般用于已经结构的变量,而反射用于未知结构的变量。反射很难调试,容易出错且性能开销大,谨慎使用。

闭 包 \red{闭包}

  • 闭包即函数变量,在将已声明的函数赋值给函数变量时,会完成函数内的计算,只是没有return而已。
  • 闭包的条件是用来赋值的函数不能是匿名函数,如例3
// 例1
func A() func() int {
	var x int
	fmt.Println("x",x)
	return func() int {
		x++
		return x*x
	}
}
func main(){
	f := A()
	fmt.Println("start")
	fmt.Println(f())  
	fmt.Println(f())   
}
//输出
/*
x 0
start
1
4
*/
// 例2
func B() func() int {
	var x int
	fmt.Println("Bx:",x)
	return func() int {
		x++
		return x*x
	}
}

func A() func() int {
	var x int
	fmt.Println("Ax:",x)
	f := B()
	fmt.Println("B",f()) //
	return func() int {
		x++
		return x*x
	}
}
func main(){
	f := A()
	fmt.Println("start")
	fmt.Println("A",f())  
	fmt.Println("A",f())   
}
// 输出
/*
Ax: 0
Bx: 0
B 1
start
A 1
A 4
*/
//例3
func main(){
	f := func() func() int {
		var x int
		fmt.Println("x", x)
		return func() int {
			x++
			return x * x
		}
	}
	fmt.Println("start")
	fmt.Println(f()())
	fmt.Println(f()())
}
// 输出
/*
start
x 0
1
x 0
1
*/

捕 获 迭 代 量 陷 阱 \red{捕获迭代量陷阱} :

  • 因为作用域的原因,for循环内部i被分配在栈中同一块内存,在每一次循环都更新这块内存。
  • 而不经过函数形参直接在函数内部使用i,其实是一种引用,引用的地址就是i在栈中那块地址。
// 例1
func main(){
	numList := []int{1,2,3}
	fList := make([]func()int,0)
	for _,i := range numList {
		fList = append(fList, func() int {
			return i*i
		})
	}
	for _,f := range fList {
		fmt.Println(f())
	}
}
// 输出
/*
9
9
9
*/
// 例2
func main(){
	numList := []int{1,2,3}

	fList := make([]func()int,0)

	for _,i := range numList {
		f := func() int {
			fmt.Println("f",i)
			return i*i
		}
		fList = append(fList, f)
	}
	fmt.Println("start")
	for _,f := range fList {
		fmt.Println(f())
	}
}
// 输出
/*
start
f 3
9
f 3
9
f 3
9
*/

捕 获 p a n i c \red{捕获panic} panic:

为什么要捕获panic呢?因为要在goroutine里panic的时候捕获堆栈信息

func main(){
	defer func() {
		if p := recover() ; p == "A"{
			fmt.Println("panic")
		}
	}()
	panic("A")
}
// 输出
// panic

并 发 锁 \red{并发锁} :

  • sync.Mutex互斥锁
  • sync.RWMutex读写锁,当读上锁是共享锁,写上锁是互斥锁
  • sync.Once初始化锁,包含一个bool和互斥锁,bool来标记是否初始化完成,lock来保护初始化变量。

重 复 抑 制 \red{重复抑制}

在goroutine并发调用慢函数时,需要对慢函数缓存返回结果。但是在慢函数第一次计算过程中,需要对慢函数进行互斥,否则可能会有多个慢函数都在“第一次执行”,这中场景即重复抑制(duplicate suppression)。
常用操作是,并发+重复抑制(chan广播实现)+非阻塞

type result struct {
	value interface{}
	err   error
}

type Func func(key string)(interface{}, error)

type entry struct {
	res result
	ready chan struct{}
}

func New(f Func)*Memo {
	return &Memo{f: f, cache: make(map[string]*entry)}
}

type Memo struct {
	f Func
	mu sync.Mutex
	cache map[string]*entry
}

func (memo *Memo) Get(key string)(value interface{}, err error){
	memo.mu.Lock()
	e := memo.cache[key]
	if e == nil {
		e = &entry{ready:make(chan struct{})}
		memo.cache[key] = e
		memo.mu.Unlock()
		e.res.value, e.res.err = memo.f(key)
		close(e.ready) // 广播
	}else{
		memo.mu.Unlock()
		<-e.ready
	}
	return e.res.value, e.res.err
}

g o r o u t i n e   V S   线 程 \red{goroutine\ VS\ 线程} goroutine VS 线

  • 内存:
    thread:线程在操作系统中有固定大小栈内存(注意,是栈内存),通常为2MB,用于保存函数调用期间的局部变量。
    goroutine:在生命周期开始只有2KB,但最大可以扩到1GB

  • 调度:
    thread:线程通过OS内核来调度,一般定期隔几毫秒会通过 硬 件 时 钟 \red{硬件时钟} 触发。
    goroutine:使用Go自己的调度器,把m个goroutine映射到n个线程上,同时go的调度器并不通过硬件时钟触发,而通过特定go语言结构触发,比如goroutine在阻塞时会被切走。

  • 映射关系:
    休眠或阻塞的goroutine不会被映射到线程上,而阻塞在I/O或其他非go语言的模块上会占用一个独立的OS线程,但这个线程不计算在GOMAXPROCS内。

  • 标识:
    thread:程序员可以拿到thread唯一标识
    goroutine:设计所然,程序员拿不到唯一标识

管 道 \red{管道}

写管道:
写管道
读管道:
写管道
关闭channel时会把recvq中的G全部唤醒,本该写入G的数据位置为nil。把sendq中的G全部唤醒,但这些G会
panic。

m a p \red{map} map

每个哈希表的实现对负载因子容忍程度不同,比如Redis实现中负载因子大于1时就会触发rehash,而Go则在在负载 因子达到6.5时才会触发rehash,因为Redis的每个bucket只能存1个键值对,而Go的bucket可能存8个键值对, 所以Go可以容忍更高的负载因子。

i t o a \red{itoa} itoa
iota代表了const声明块的行索引(下标从0开始)

d e f e r 延 迟 函 数 的 参 数 \red{defer延迟函数的参数} defer:
在传入时就已经确定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值