Go 语言基础知识
什么是 Go 语言
Go是一门 并发支持 、垃圾回收 的 编译型 系统编程语言,旨在创造一门具有在静态编译语言的 高性能 和动态语言的 高效开发 之间拥有良好平衡点的一门编程语言。
一些设计思想
- 不要通过共享内存来通信,要通过通信来共享内存
Go 的主要特点
- 类型安全 和 内存安全
- 以非常直观和极低代价的方案实现 高并发
- 高效的垃圾回收机制
- 快速编译(同时解 决C语言中头文件 太多的问题)
- 为多核计算机提供性能提升的方案
- UTF-8编码支持
Go 代码的一般格式
- -通过 import 关键字来导入其它 非 main 包
- -通过 const关键字来进行常量的定义
- -通过在函数体外部使用 var关键字来进行全局变量的声明与赋值
- -通过 type 关键字来进行结构(struct)或接口(interface)的声明
- -通过 func关键字来进行函数的声明
、
可见行规则:Go语言中,使用 大小写 来决定该 常量、变量、类型、接口、结构或函数 是否可以被外部包所调用
Go 语言的数据类型
- 布尔型:bool、1 字节l
- 整型:int/uint、根据平台可能是 32 位或者是 64 位
- 8 位整型:int8/uint8、1 字节
- 字节型:byte、1 字节
- 16 位整型:int16/uint16、2 字节
- 浮点型:float32/float64、4/8 字节
- 复数:complex64/complex128、8/16字节
- 其他值类型:array、struct、string
- 引用类型:slice、map、chan
- 接口类型:interface
- 函数类型:func(引用类型)
Golang 的 rune 类型
// int32的别名,几乎在所有方面等同于int32// 它用来区分字符值和整数值(就是用在字符串)
byte 等同于int8,常用来处理ascii字符
rune 等同于int32,常用来处理unicode或utf-8字符
Golang 的 string 类型
- string 类型使用的 utf8采用变长字节存储(英文字母是单字节存储,中文是3个字节存储)
- len 会返回 string 的字节数,string 是字节的集合,for-range 时下标可能不连续
- string 拼接,不要循坏使用 +,可以考虑使用 strings.Join 函数
Golang 的 channel 类型
使用 make 创建有无缓冲区的管道 ch1 := make(channel string,5)
使用场景:消息传递、同步异步、并发控制、结果汇总
type hchan struct { qcount uint // 环形队列长度,即已有元素个数 ¥ dataqsiz uint // 环形队列容量,即可容纳元素个数 ¥ buf unsafe.Pointer // 环形队列地址(底层使用数组实现)¥ elemsize uint16 // 元素大小 closed uint32 // 标识关闭状态(关闭时会唤醒队列里的 channel) elemtype *_type // 元素类型 sendx uint // 下一次写下标位置 recvx uint // 下一次读下标位置 recvq waitq // 读等待队列(阻塞时加到这个队列)¥ sendq waitq // 写等待队列(阻塞时加到这个队列)¥ lock mutex // 互斥锁 ¥ }
阻塞场景
- 向无缓冲的队列写数据或者读数据
- 读缓冲区无数据,写缓冲区已满
panic 场景
- 关闭已经关闭或者为 nil 的 channel
- 向已经关闭的 channel 写数据
Golang 的 slice 类型
type slice struct { array unsafe.Pointer // 指向底层数组 len int // 切片长度 cap int // 底层数组容量 }
Go语言中的 slice 陷阱:如何避免常见的错误
- 在函数里修改切片元素的值,原切片的值也会改变(如果在函数内发生扩容,函数外的值不会改变)
- 调用 append 方法追加元素,如果切片的容量不够会引起切片扩容(内存分配和性能问题)
- 谨慎使用多个切片共享一个数组,会出现写冲突
- 当切片容量小于 1024 时,以 2 倍的规则扩容,否则以 1.25 倍的规则扩容
Golang 的 Map 类型
- 不是线程安全的,避免并发的读写 Map,或者使用读写锁 或者 sync.map 解决这个问题
- 使用了链地址法解决 Hash 冲突
- 使用了增量扩容解决负载因子大于 6.5(键数量/Bucket数量)过高的问题
Golang 数据类型的特点
数组:
- 数组是值,将一个数组赋值给另一个,会拷贝所有的元素(如果你给函数传递一个数组,其将收到一个数组的拷贝,而不是它的指针)
- 数组的大小是其类型的一部分。类型[10]int和[20]int是不同的。
切片:
切片持有对底层数组的引用,如果你将一个切片赋值给另一个,二者都将引用同一个数组(如果函数接受一个切片参数,对切片的元素所做的改动,对于调用者是可见的,好比是传递了一个底层数组的指针)
字典:
和切片类似,map持有对底层数据结构的引用。如果将map传递给函数,其对map的内容做了改变,则这 些改变对于调用者是可见的。
Golang 使用 defer 技巧
用于延迟函数的调用,常用于释放锁或其他资源
- 多个defer出现的时候,它是一个“栈”的关系,也就是先进后出(一般写在函数开头捕获异常)
- return 之后的语句先执行,defer 可以修改 return 的返回值
- defer 函数的参数在执行时就已经确认了
- 单个函数不能有过多 defer,影响执行的机制,导致效率下降
Golang panic 介绍
对于一些危险的操作,比如数组越界,会抛出 panic,提前结束程序执行,panic 的退出方式相对于 os.Exit() 比较优雅,支持使用 defer 和 recover 解决 panic
Golang recover 介绍
- recover 必须要位于 defer 函数中
- recover可以清除本函数产生的 panic,让上游函数以为一切正常执行
Go 变量作用域
花括号来控制变量的作用域,花括号中的变量是单独的作用域,同名变量会覆盖外层
Golang 控制结构
select 的特点
golang 提供的多路 I/O 复用机制 和 linux 的 select 机制类似
- select 的每个 case 只能操作一个管道,根据管道的特性区分是否阻塞
- 全部 case 都阻塞时陷入阻塞
- 多个 case 不阻塞时,随机执行一个 case
- 存在 default 时,永远不会阻塞
Golang 并发控制
一、使用 channel 控制协程同步
- 实现简单,需要创建大量的协程
二、使用 WaitGroup 控制协程同步(实现原理信号量机制)
三、使用 Context 上下文控制子协程
- 方便进行子协程的控制
四、使用 Mutex 控制并发
type Mutex struct { state int32 //32 位数字,低 3 位分别表示 3 种状态:唤醒状态、上锁状态、饥饿状态,剩下的位数则表示当前阻塞等待的 goroutine 数量。 sema uint32 //信号量 }
- 类似于操作系统的 PV 原语操作,通过控制信号量 S 处理共享资源的抢占
- 三种模式
- 正常模式
- 饥饿模式(解决一直被新来的 G 占用资源,队列里的 G 占有不了资源的情况)
- 自旋模式(通过信号量唤起 G 比较耗资源,G 空转 CPU 去占有资源)
- 其他细节
- 多次 unlock 会出发 panic