Go语言笔记
go语言官方文档 https://golang.google.cn/doc/
go tool
go tool compile --help
go语言的基本哲学理念:
不要通过共享内存来通信,二是通过通信来共享内存
通过共享内存来通信,则并发时需要对临界区域加锁;通过通信来共享内存则只需要保证通信管道的并发安全,即可保证系统的并发安全;
占位符 https://www.cnblogs.com/jxd283465/p/11765201.html
占位符 说明 举例 输出 %v 相应值的[默认格式]。 Printf("%v", people) {zhangsan}, %+v 打印结构体时,会[添加字段名] Printf("%+v", people) {Name:zhangsan} %#v 相应值的Go语法表示 Printf("#v", people) main.Human{Name:"zhangsan"} %T 相应值的[类型]的Go语法表示 Printf("%T", people) main.Human %% 字面上的百分号,并非值的占位符 Printf("%%") % %t Bool占位符 true 或 false。 Printf("%t", true) true
初始化与init() 函数
https://zhuanlan.zhihu.com/p/34211611
init函数在程序运行前注册,实现sync.Once的功能
init函数先于main函数之前执行,且不能被其他函数调用
不同包内的init函数按照导入的依赖关系自动决定执行顺序
各个文件的初始化顺序为:各个包内的变量初始化 -> 各个包的init函数 (按照依赖顺序)-> main()
同一个包和每个源文件都可以有多个init函数,包内的init函数执行顺序没有明确定义,不应依赖多个init的执行顺序。
import _ "net/http/pprof"
import . "fmt"
import f "fmt"
别名导入:
golang对没有使用的导入包会编译报错,但是有时我们只想调用该包的init函数,不使用包导出的变量或者方法,这时就采用下划线 _
导入
如果我们希望使用时可以省略包名fmt
,使用 .
导入
go build : 在当前目录编译生成可执行文件
go install : 用来安装可执行文件,然后完成 go build 功能,生成的exe文件放在【$GOPATH/bin】目录下;go install 的包需要指定版本号
go get: 将远程代码下载安装到【$GOPATH/src】 目录下,高版本不再支持安装二进制(就是不再生成exe文件,比如protoc-gen-go);向当前目录的go.mod追加依赖项;
go 安装入门:
- 下载最新go 安装包:https://studygolang.com/dl
- 配置环境变量
GOPATH
指定路径用于安装 go 依赖包- 命令行输入
go env
查看是否安装成功
go 的接口与内存分布
https://www.cnblogs.com/f-ck-need-u/p/9940845.html
golang RoadMap
https://www.golangroadmap.com/class/sql/2.html
查看引用程序依赖库
ldd /path/to/file
有缓存的 channel 与无缓存的 channel
ch1 := make(chan int) // 无缓存的channel
ch2 := make(chan int, 10) // 有缓存的channel,容量为10个int
无缓存的channel 只有当 receive 准备好之后,send 处才会执行,否则会阻塞住。
go语言 switch 与 select
switch
不需要写 break,默认会自动break。如果需要强制执行下一个case, 使用 fallthrough
。switch 的 case 可以是函数或表达式,只要所有case类型相同即可。
select
是随机选择一个可运行的case,如果没有case可运行,就会阻塞。如果加入default语句,default总是可运行的。case语句必须是对channel的IO操作,可以是读也可以是写。select
主要用于处理并发编程中通道之间异步通信的问题。
select 中的case条件是并发执行的,select会选在先操作成功的条件去执行,如果多个同时返回,会随机选择一个。空的select{}
会引起死锁。可以使用time.After(1*time.Second)
做一个超时的case取代default子句。
【Golang 快速入门】基础语法 + 面向对象
【Golang 快速入门】高级语法:反射 + 并发
go语言关键数据结构
数组:x := [3]int{1,2,3}
定长为数组,作为函数参数传递时,采用值传递。
slice:x := []int{1,2,3}
数组的切片,是指向数组的指针,作为函数参数传递时,采用引用传递。可以通过 append 方法进行自动扩容,append 方法会返回一个新地址。注意避坑,当 append 数据不超过切片的容量时,会修改同一个切片内的内容,扩容后,两个切片不再引用同一片地址。
x := [6]int{1, 2, 3, 4, 5, 6}
y := []int{7, 8, 9} // 当 s1+y 长度不超过x, append会直接覆盖x内的值,x与s2还是同一个地址
// y := []int{7,8,9,10} // 当 s1+y 长度超过x,append会扩容,x与s2 地址不同,且x还是原始值。
s1 := x[0:3]
s2 := append(s1, y...)
fmt.Println(x)
fmt.Println(s2)
fmt.Printf("%p\n", &x)
fmt.Printf("%p\n", s2)
map:链地址法hash表 + 8元素bucket, 渐进式rehash过程。
map的range遍历过程,多次调用的结果是不同的。因为内部做了随机处理。
map的遍历过程是通过遍历桶实现的,因此遍历时可以删除或增加元素不会报错。但是遍历的过程不是每次都相同的,如果遍历的过程中数据数据写入未遍历过的桶,就会在本次遍历过程中访问到。如果加入的是已经遍历过的桶,就不会在本次遍历过程中访问到。因此写入和删除时遍历map的结果数量是不确定的。
由于 syncMap内部存储都是k,v interface{}
,可以考虑利用反射提供类型检查:
type ConcurrentMap struct {
m sync.Map
keyType reflect.Type
valueType reflect.Type
}
func NewConcurrentMap(keyType, valueType reflect.Type) (*ConcurrentMap, error) {
if keyType == nil {
return nil, errors.New("nil key type")
}
if !keyType.Comparable() {
return nil, fmt.Errorf("incomparable key type: %s", keyType)
}
if valueType == nil {
return nil, errors.New("nil value type")
}
cMap := &ConcurrentMap{
keyType: keyType,
valueType: valueType,
}
return cMap, nil
}
func (cMap *ConcurrentMap) Delete(key interface{}) {
if reflect.TypeOf(key) != cMap.keyType {
return
}
cMap.m.Delete(key)
}
chan:channel 内部维护了一个互斥锁,来实现线程安全
rune: 用来区分字符值和整数值的结构,rune的实质是int32的别名,但在go语言内作为字符单位用来处理字符串。相当于go语言的char。
go默认字符编码为UTF-8,UTF-8为可变长度编码,每个中文一般占 3 字节。因此字符串修改需要转换为[]rune
处理, 不可以直接对string进行slice切片。
使用range 遍历字符串是按照rune正确地迭代,使用for-i遍历字符串是按照字节遍历的。
ss := "go算法"
fmt.Printf("%v len:%v\n", ss, len(ss)) // go算法 len:8
rss := []rune(ss)
fmt.Printf("%v len:%v\n", rss, len(rss)) // [103 111 31639 27861] len:4
rss[0] = '我'
ss = string(rss)
fmt.Printf("%v len:%v\n", ss, len(ss)) // 我o算法 len:10
range 返回的是值拷贝
range 返回的第二个参数是后面迭代对象内的值拷贝,且迭代时原地拷贝复用空间(不改变对象地址)
基本类型:
go 语言中,int
在32位机器上占4个字节,在64位机器上占8个字节。i := 32
在64位机器上的类型为int64
通过math.MaxInt
可以获得对应的int范围最大值,其他的系统函数strconv.Itoa
strconv.Atoi
默认使用的都是int
类型,返回int64
类型
m := make(map[string]*student)
stus := []student{
{name: "aaa", age: 18},
{name: "bbb", age: 23},
{name: "ccc", age: 28},
}
for _, stu := range stus {
// stu 是一个临时变量,只会分配一次空间,因此取地址是固定的,指向它自己的空间。
// 后面的值直接 值拷贝覆盖 stu空间的值,所以最终stu的值是最后一次迭代的对象的值。
m[stu.name] = &stu // aaa/bbb/ccc --> ccc 错误的迭代
m[stu.name] = stu[i] // 正确的迭代。虽然还是产生了值拷贝;更好的方式是将stus声明为指针数组,这样遍历时只会产生指针拷贝
}
成员变量的大小写问题:
Go提倡成员变量首字母大写,首字母大写被视为public,外部包可以访问。首字母小写的成员变量视为private, 外部包无法访问, 且进行json,xml, gob等序列化时会被忽略。
main() 主程序退出时 未执行完的 goroutine 会被强行中断导致任务没有正常结束
可使用 sync.WaitGroup
等待所有协程执行完之后再结束main
var wg sync.WaitGroup
wg.Add(1)
go func(pwg *sync.WaitGroup) {
defer pwg.Done()
time.Sleep(1 * time.Second)
fmt.Println("go end")
}(&wg)
wg.Wait()
fmt.Println("main end")
go 的反射包 reflect
用来比较无法使用==
的变量,采用值相等的比较方式reflect.DeepEqual(a,b)
new(T) 与 make(T)
new(T) 和 make(T) 都在堆上分配内内存
new(T) 为每个类型T分配内存,并返回*T 的指针,适用于值类型的结构体,相当于 &T{}
make(T) 返回一个类型为T的初始值,它仅适用于go内部的引用类型:slice, map, channel
p := new([]int) // *p == nil; with len and cap 0
fmt.Println(p) // &[]
v := make([]int, 10, 50) // v is initialed with len 10, cap 50
fmt.Println(v) // [0 0 0 0 0 0 0 0 0 0]
interface 与 struct
interface是由两个部分组成,[动态类型] 和 [动态值]
func myFunc(arg interface{}){
t: = arg.(type) // 获arg的动态类型
v, ok := arg.(string) // 类型断言,检查arg是否是指定的string类型
}
interface 指向的对象天生是指针传递,map,slice,channel本质都是引用类型,map采用值传递取值也是无法寻址的
m := map[string]Data{"x": {"one"}}
// m["x"].Name = "two" // cannot assign to struct field m["x"].Name in map
v := m["x"]
v.Name = "two" // m["x"]={one}
m["x"] = v // m["x"]={two}
通过Go的强制类型转换,在编译期检查接口是否实现。
type MoveInterface interface{
Move()
}
type StructName struct{
}
func (s *CatStruct) Move(){
}
// 因为interface 是指针类型,因此要转成对应结构体的指针
// 强制赋值以检查对象是否实现指定接口
var _ MoveInterface = (*CatStruct)(nil)
关于定义别名:
type MyLocker sync.Locker
type MyMutex sync.Mutex
type MyMutex2 struct{
sync.Mutex
}
由于sync.Locker 为 interface 类型,因此MyLocker 继承其Lock()和Unlock()方法
sync.Mutex 为 struct 类型,MyMutex 没有保留方法集。而使用匿名字段组合的 MyMutex2 拥有 sync.Mutex 的方法集。
defer 返回时执行语句
defer 是在声明它的函数结束并返回时执行。
defer 函数在声明时求值,而不是在调用时求值。如果defer使用了闭包,闭包引用了外部变量,则是在调用时求值。
多个 defer 语句,最后定义的先执行。
defer 中可以对返回值进行重新赋值。
函数发生panic时,只能保证当前goroutine中的 defer 一定会执行,其他goroutine的defer代码不保证。
断言语句失败时,会返回目标类型的“零值”,因此断言时不要使用已定义默认值的变量,会被覆盖为“零值”。
golang 并发包
atomic
golang 并发 atomic
atomic 结构体提供的原子方法Load...
,Store...
,Add...
,Swap...
,CompareAndSwap...
(CAS),Value
等
如果一个操作不涉及资源竞争,只涉及状态的读写判定,则不需要使用复杂的互斥锁Mutex,只需要使用 atomic这种基于CPU指令的原语。
atomic比Mutex的效率高是因为 atomic 是通过 cpu原子指令修改内存,并通过内存屏障保证数据的可见性。不涉及内核的上下文切换。
而Mutex的使用涉及多次atomic原子操作以及用户态和内核态的线程切换,还有线程调度的开销。
Go的数据结构与算法库
gods 实现了Java常用的数据结构
go-datastructures 高性能与线程安全的GO数据结构
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 using1174@foxmail.com
文章标题: Go语言笔记
文章字数: 2,957
本文作者: Jun
发布时间: 2022-04-02, 18:14:00
最后更新: 2022-05-16, 20:33:07
原始链接: http://yoursite.com/2022/04/02/Go语言笔记/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。