一、基本技能
1.1、go环境配置
- Linux系统
vim /etc/profile
export GOROOT=/usr/local/go
export GOBIN=$GOROOT/bin
export PATH=$PATH:$GOROOT/bin:$GOBIN
go.mod
export GO111MODULE=on
export GOSUMDB=off
export GOPROXY=***
- Windows系统
go env -w GOPROXY=http://goproxy.***.com
go env -w GO111MOODULE=on
go env -w GOSUMDB=off
1.2、goland使用
- goland函数注释风格设置:File->Settings->Editor->Live Templates
1.3、go调试
- 代码同步,远程dlv debug: 安装 >> go get -u github.com/go-delve/delve/cmd/dlv
dlv debug xxx.go会先编译go源文件,同时执行attach命令进入调试模式,
该命令会在当前目录下生成一个名为debug的可执行二进制文件,退出调试模式会自动被删除。
dlv exec executable_file:直接从二进制文件启动调试模式。(go build出来的文件)
>> break main.main #设置一个main函数断点
>> bp #查看所有断点
>> exit #退出断点调 试
>>clear #清除单个断点;
>>clearall #清除所有断点。
>>list #打印当前断点位置的源代码,list后面加行号可以展示该行附近的源代码,如list test.go:10将会展示test.go文件第10行附近的代码,值得注意的是该行必须是代码行而不能是空行。
>> bt #打印当前栈信息。
>> frame # 切换栈。
>> regs #打印寄存器内容。
二、基础语法
2.1、GO的关键字:
包管理:package、import
变量:var、map、struct、interface、const、type
函数:func、return、defer
循环:if、else、switch、case、fallthought、default、for、break、continue、goto、range
并发:go、chan、select
a := []int{}
copy(a, []int{1, 2, 3})
fmt.Println(a) // >> [1 2 3]
fmt.Println(append(a, 1)) // >> [1 2 3 1]
b := []int{5}
copy(b, []int{1, 2, 3})
fmt.Println(b) // >> [1]
c := []int{5, 6, 7, 8}
copy(c, []int{1, 2, 3})
fmt.Println(c) // >> [1, 2, 3, 8]
2.2、GO的变量类型:
(1)整型
int(默认)、int8、int16、int32、int64、byte
uint、uint8、uint16、uint32、uint64
(2)浮点型:float32、float64(默认)
(3)复数:complex64、complex128(默认)
(4)数组:值拷贝、不同长度和不同类型的数组都是一个新类型
(5)切片:引用
(6)map:引用
(7)复合类型:interface、struct
2.3、GO的内置函数:append、cap、new、make、copy、len、delete、panic、recover
(1)append:向切片中添加一个或多个值
(2)cap:计算数组和切片已分配的内存空间
(3)new:传入一个结构体名,返回它的一个实例对象的一个引用
(4)make:为切片、map分配空间 ( make([]int, 3) make(map[int]int))
(5)copy:为切片进行值拷贝
(6)len:计算字符串、数组、切片、map当前已使用的空间个数
(7)delete:删除map中的一个key
(8)panic:恐慌、会从当前函数一直往调用该函数的那个函数扩散,直到遇到recover或者是扩散到main函数中
(9)recover:接收恐慌
三、GMP&GC
3。1、并发&并行&进程&线程&协程
- 并发 vs 并行:单核"假同时" vs 多核"真同时"
- 进程 vs 线程:独立办公室 vs 共享办公室的小工位
- 线程 vs 协程:系统强制调度 vs 用户自愿让出控制权
3.2、go协程调试GMP模型
- Goroutine:相当于OS的进程控制块(Process Control Block);它包含:函数执行的指令和参数,任务对象,线程上下文切换,字段保护,和字段的寄存器。
- M:对应物理线程。
- P:golang的协程调度器。P的数量可以通过GOMAXPROCS设置。
调度器设计策略
1、复用线程:
- work stealing机制:当本线程无可运行的Goroutine时,尝试从其他线程绑定的P队列偷取G,而不是消毁线程。
-
hand off机制:当本线程因为Goroutine进行系统调用阻塞时,线程释放绑定的P,把P转移给其它空闲的线程执行。
2、抢占:在goroutine中要等待一个协程主动让CPU才执行下一个协程;在GO中,一个goroutine最多占用CPU 10ms, 防止其他goroutine被锁死。
3、利用并行:利用GOMAXPROCS设置P数量,最多有GPMAXPROCS个线程分布在多个CPU上同时执行。
4、全局G队列:当M执行work stealing从其它P的本地队列中偷不到G时,它可以从全局列队获取G.
runtime
golang 的 runtime 核心功能包括以下内容:协程(goroutine)调度、垃圾回收(GC)、内存分配
使得 golang 可以支持如 pprof、trace、race 的检测
- 1、内存泄漏:申请了内存,没有及时释放内存。
- 2、野指针:内存释放后还在使用,导致未定义的行为。
注:go不支持C++对指针偏移的操作。可以支持GC
3.3、channel是解决gorotine之间的通信而设计来的。
- channel创建:(带缓冲的,不带缓冲的两种)
make创建了一个长度为3的带缓冲的channel,channel在底层是一个hchan结构体,位于src/runtime/chan.go里。 - channel是goroutine安全,是通过mutex实现的。在不同的goroutine之间存储和传输值 - 提供FIFO语义(buffered channel提供,通过循环队列实现的)
- 可以让goroutine block/unblock channel的通信:在goroutinue之间传递数据,是通过仅共享hchan+数据拷贝实现的。
- Channel阻塞是通过goroutine自己挂起,唤醒goroutinue是通过对方goroutine唤醒实现的。
协程
- 三个协和交替打印
package main
import (
"fmt"
"time"
)
func RunA(count int, ch1, ch2 chan int) {
for i := 0; i < 10; i++ {
<-ch1
fmt.Println(i)
ch2 <- i
}
}
func RunB(count int, ch2, ch3 chan int) {
for i := 0; i < 10; i++ {
<-ch2
fmt.Printf("%c\n", 'a'+byte(i))
ch3 <- i
}
}
func RunC(count int, ch1, ch3 chan int) {
for i := 0; i < 10; i++ {
<-ch3
fmt.Printf("%c\n", 'A'+byte(i))
ch1 <- i
}
}
func main() {
fmt.Println("Hello World")
count := 10
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
ch3 := make(chan int, 1)
// 启动多个goroutine
go RunA(count, ch1, ch2)
go RunB(count, ch2, ch3)
go RunC(count, ch1, ch3)
time.Sleep(time.Second * 10)
}
四、常用技能
4.1、文件操作
package main
import (
"fmt"
"os"
)
func main() {
// 创建一个文件
file, err := os.Create("test.txt")
if err != nil {
fmt.Println("创建文件失败")
return
}
defer file.Close()
// 写入文件
_, err = file.WriteString("Hello World")
if err != nil {
fmt.Println("写入文件失败")
return
}
// 读取文件
file, err = os.Open("test.txt")
if err != nil {
fmt.Println("打开文件失败")
return
}
defer file.Close()
content, err := ioutil.ReadAll(file)
if err != nil {
fmt.Println("读取文件失败")
return
}
fmt.Println(string(content))
}
4.2、字符串操作
package main
import (
"fmt"
"strings"
)
func main() {
s := "Hello World"
fmt.Println(s[0:5]) // >> Hello
fmt.Println(s[:5]) // >> Hello
fmt.Println(s[5:]) // >> World
fmt.Println(s[5:10]) // >> World
fmt.Println(strings.ToUpper(s)) // >> HELLO WORLD
fmt.Println(strings.ToLower(s)) // >> hello world
fmt.Println(strings.Repeat(s, 3)) // >> Hello WorldHello WorldHello World
}
4.3、指针
package main
import "fmt"
func main() {
var a int = 10
var b *int = &a
fmt.Println(*b) // >> 10
c := 10
d := &c
fmt.Println(*d) // >> 10
}
4.4、标准库函数
字符串slice处理
package main
import (
"fmt"
"sort"
"strings"
)
func main() {
s := []string{"Hello", "World"}
// 判断字符串是否包含子字符串
fmt.Println(strings.Contains(s[0], "Hello")) // >> true
fmt.Println(strings.Contains(s[0], "World")) // >> false
// 字符串的长度和大小
fmt.Println(len(s)) // >> 2
fmt.Println(cap(s)) // >> 2
fmt.Println(len(s[0])) // >> 5
// 字符串排序
sort.Slice(s, func(i, j int) bool {
return s[i] > s[j] // 降序
})
fmt.Println(s) // >> [World Hello]
}
4.5、类型转换
package main
import (
"fmt"
"strconv"
)
func main() {
// 字符串转整型
a := "100"
b, err := strconv.Atoi(a)
if err != nil {
fmt.Println("转换失败")
return
}
fmt.Println(b) // >> 100
// 字符串转浮点型
e, err := strconv.ParseFloat(a, 64)
if err != nil {
fmt.Println("转换失败")
return
}
fmt.Println(e) // >> 100
// 字符串转int64
f, err := strconv.ParseInt(a, 10, 64)
if err != nil {
fmt.Println("转换失败")
return
}
fmt.Println(f) // >> 100
// 整型转字符串
c := 100
d := strconv.Itoa(c)
fmt.Println(d) // >> 100
// 浮点型转字符串
g := 100.123
h := strconv.FormatFloat(g, 'f', 2, 64)
fmt.Println(h) // >> 100.12
// int64转字符串
i := int64(100)
j := strconv.FormatInt(i, 10)
fmt.Println(j) // >> 100
}
4.6、随机数
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
// 生成随机数种子,不然每次都会随机成0
rand.Seed(time.Now().UnixNano())
fmt.Println(rand.Intn(100)) // >> 66
// 生成0到(n-1)的随机整数 int32
fmt.Println(rand.Int31n(100)) // >> 66
// 生成0到(n-1)的随机整数 int64
fmt.Println(rand.Int63n(100)) // >> 66
}
4.7、WaitGroup并发
在 sync.WaitGroup(等待组)类型中,每个 sync.WaitGroup 值在内部维护着一个计数,此计数的初始默认值为零。
跟java的CountdownLatch差不多,也是阻塞等待所有任务完成之后再继续执行。
简单使用就是在创建一个任务的时候wg.Add(1),
任务完成的时候使用wg.Done()来将任务减一。
使用wg.Wait()来阻塞等待所有任务完成。
- (wg *WaitGroup) Add(delta int)
- (wg *WaitGroup) Done()
- (wg *WaitGroup) Wait()
package main
import (
"sync"
)
type httpPkg struct{}
func (httpPkg) Get(url string) {}
var http httpPkg
func main() {
var wg sync.WaitGroup
var urls = []string{
"http://www.golang.org/",
"http://www.google.com/",
"http://www.somestupidname.com/",
}
for _, url := range urls {
// Increment the WaitGroup counter.
wg.Add(1)
// Launch a goroutine to fetch the URL.
go func(url string) {
// Decrement the counter when the goroutine completes.
defer wg.Done()
// Fetch the URL.
http.Get(url)
}(url)
}
// Wait for all HTTP fetches to complete.
wg.Wait()
}
4.8、接口请求读取
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
// 创建一个GET请求
req, err := http.NewRequest("GET", "https://www.baidu.com", nil)
if err != nil {
fmt.Println("创建请求失败")
return
}
// 发送请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("发送请求失败")
return
}
defer resp.Body.Close()
// 读取响应体
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("读取响应体失败")
return
}
result := struct{
Code int `json:"code"`
Data string `json:"data"`
}{}
fmt.Println(string(body))
if err != json.Unmarshal(body, &result) {
fmt.Println("解析响应失败")
return
}
fmt.Println(result)
}
五、常用三方工具
5.1、swagger-py-codegen
- 使用openapi-generato生成golang代码框架
- openapi-generator-cli generate -i api.yaml -g python -o ./generated-api --config config.yaml
- OpenAPI Generator 使用 Mustache 模板引擎来生成代码。你可以创建自定义模板来覆盖默认行为。创建一个自定义模板目录,例如custom-templates。
- openapi-generator-cli generate -i api.yaml -g python -o ./generated-api -t ./custom-templates
- 支持通过钩子(hooks)在生成代码的不同阶段执行自定义脚本。这可以用于更复杂的自定义需求。
说明:
openapi-generator-cli generate -i api.yaml -g python-flask -o ./generated-server -t ./custom-templates在这个命令中:
• -i api.yaml指定了 OpenAPI 规范文件。
• -g python-flask指定了生成代码的目标语言或框架。
• -o ./generated-server指定了生成代码的输出目录。
• -t ./custom-templates指定了自定义模板的目录。示例自定义假设你想在生成的 Flask 服务器端代码中添加一个自定义的中间件来处理请求日志。
5.2、质量工具
• delve 本地代码调试工具
• goconvey是一款针对Golang的测试框架,可以管理和运行测试用例,同时提供了丰富的断言函数,并支持很多Web界面特性。
• goleak 本地排查内存泄露的工具
• go-wrk Go接口压测工具
• golint 代码风格检查
5.2、gomongo
package main
import (
"io/ioutil"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/bson"
db.mgo.Query(db.name,bson.M{"agent_id_list":bson.M{"$in":agentId}},opt)
db.mgo.Query("TestQuery",bson.M{"index":bson.M{"$lt":9,"$gt":7}})
func main() {
// 范围查找:
db.getCollection('server_zone').find({"zone_name":{"$in":["TEST","TEST1"]}})
// 并列查找:
db.getCollection('server_zone').find({"$and":[{name:"lin"}, {age:{"$gt":18}}]})
// 或查找:
db.getCollection('server_zone').find({"$or":[{"zone_name":"TEST"},{"zone_name":"TEST1"}]})
// 模糊查找组:
db.getCollection('server_zone').find({"zone_name":{"$regex":"TEST","$options":"i"}})
// 排序:sort
db.stu.find().sort({ age:1 }) 1正序 -1倒序
// 选取:limit
db.stu.find().limit(2) 选取两条数据
// 跳过:skip
db.stu.find().skip(2) 跳过前两条数据
// 选择中间两条 or 跳过前N条
db.stu.find().skip(0).limit(2).sort({ age:-1 })
// 分页 优先级:先排序 - 跳过 - 选取
var page = 1
var num = 2
var sk = (page-1) * num
db.stu.find().skip(sk).limit(num).sort({ age:-1 })
//查找最新一条记录:
db.getCollection('record').find({"tenant":"test"}).sort({_id:-1}).limit(1)
//模糊查找组:
db.getCollection('server_zone').find({"zone_name":{"$regex":"TEST","$options":"i"}})
}
5.4、GIN框架
Ø Engine: 用来初始化一个gin对象实例,在该对象实例中主要包含了一些框架的基础功能,比如日志,中间件设置,路由控制(组),以及handlercontext等相关方法.
Ø Router: 【字典树】用来定义各种路由规则和条件,并通过HTTP服务将具体的路由注册到一个由context实现的handler中
Ø Context: Context是框架中非常重要的一点,它允许我们在中间件间共享变量,管理整个流程,验证请求的json以及提供一个json的响应体. 通常情况下我们的业务逻辑处理也是在整个Context引用对象中进行实现的.
Ø Bind: 在Context中我们已经可以获取到请求的详细信息,比如HTTP请求头和请求体,但是我们需要根据不同的HTTP协议参数来获取相应的格式化数据来处理底层的业务逻辑,就需要使用Bind相关的结构方法来解析context中的HTTP数据
结构体参数绑定:
https://github.com/go-playground/validator 源码
https://godoc.org/github.com/go-playground/validator 文档示例1: 启动一个gin服务
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
fmt.Println("Listening and serving HTTP on :8080")
r.Run(":8080")
}
- 示例2:参数处理
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 参数处理
r.GET("/user/:id", func(c *gin.Context) {
id := c.Param("id") // 请求参数
user := c.Query("username") // 查询参数 /user/search?username=少林&address=北京
address := c.Query("address")
c.JSON(http.StatusOK, gin.H{
"id": id,
"username": user,
"address": address,
})
})
r.POST("/user", func(c *gin.Context) {
body,_ := ioutil.ReadAll(c.Request.Body)
fmt.Println(string(body))
c.JSON(http.StatusOK, gin.H{
"message": "post",
})
})
fmt.Println("Listening and serving HTTP on :8080")
r.Run(":8080")
}
六、golang学习建议
不要通过共享内存进行通信,通过通信共享内存
并发不是并行
管道用于协调;互斥量(锁)用于同步
接口越大,抽象就越弱
利用好零值
空接口 interface{} 没有任何类型约束
Gofmt 的风格不是人们最喜欢的,但 gofmt 是每个人的最爱
允许一点点重复比引入一点点依赖更好
系统调用必须始终使用构建标记进行保护
必须始终使用构建标记保护 Cgo
Cgo 不是 Go
使用标准库的 unsafe 包,不能保证能如期运行
清晰比聪明更好,反射永远不清晰
错误是值
不要只检查错误,还要优雅地处理它们
设计架构,命名组件,(文档)记录细节
不要(在生产环境)使用 panic()
每个 package 实现单一的目的
显式处理错误
尽早返回,而不是使用深嵌套
让调用者处理并发(带来的问题)
在启动一个 goroutine 时,需要知道何时它会停止
避免 package 级别的状态
编写测试以锁定 package API 的行为
如果你觉得慢,先编写 benchmark 来证明
七、其它
- 反转链表
package main
import (
"fmt"
"time"
)
func reverselist(head *ListNode) *ListNode {
if head == nil {
return nil
}
var prev *ListNode = nil
current := head
for current != nil {
next := current.Next // 保存当前节点的下一个节点
current.Next = prev // 反转当前节点指针
prev = current // 移动 prev 到当前节点
current = next // 移动 current 到下一个节点
}
return prev
}
func main() {
head := &ListNode{1, nil}
head.Next = &ListNode{2, nil}
head.Next.Next = &ListNode{3, nil}
head.Next.Next.Next = &ListNode{4, nil}
head.Next.Next.Next.Next = &ListNode{5, nil}
fmt.Println(head)
fmt.Println(reverselist(head))
}