golang
使用golang开发,导包的时候,需要配置go包代理,这里用七牛云的,稳定 七牛云 - Goproxy.cn
windows开发vscode开发环境
1,配置go mod的环境变量,以及go代理
系统环境变量里
GO111MODULE配置成on
GOPROXY配置成https://goproxy.cn
用户变量里设置gopath 比如GOPATH:C:\Program Files\Go
如果升级删掉重装,记得把之前的目录文件也删掉(报错,windows10 go: no such tool "compile")

2,有了go代理就可以安装vscode的提示下载各种工具和包了
VSCode 必须安装以下插件:
首先你必须安装 Golang 插件,然后再给 Go 安装工具包。
在 VS Code 中,使用快捷键:ctrl+shift+P,然后键入:go:install/update tools,将所有 16 个插件都勾选上,然后点击 OK 即开始安装。
修改默认配置的方法:
在 Preferences -> Setting 然后输入 go,然后选择 setting.json,填入你想要修改的配置
在底层,Go使用Git来获取指定版本的依赖模块
gomod/goproxy/gosumdb/GOPRIVATE
我们可以通过GOPROXY来控制代理,以及通过GOPRIVATE控制私有库不走代理
- 设置GOPRIVATE来跳过私有库,比如常用的Gitlab或Gitee,中间使用逗号分隔:
go env -w GOPRIVATE=*.gitlab.com,*.gitee.com
GOPRIVATE=*.domain.cc //一般domain.cc是你公司私有git仓库的域名地址,这样就可跳过proxy的检查
如果你的代码仓库或者模块是私有的,那么它的校验值不应该出现在互联网的公有数据库里面,但是我们本地编译的时候默认所有的依赖下载都会去尝试做校验,这样不仅会校验失败,更会泄漏一些私有仓库的路径等信息,我们可以使用GONOSUMDB这个环境变量来设置不做校验的代码仓库, 它可以设置多个匹配路径,用逗号相隔.
举个例子:
GONOSUMDB=*.corp.example.com,rsc.io/private
这样的话,像 "http://git.corp.example.com/xyzzy", "http://rsc.io/private", 和 "http://rsc.io/private/quux"这些公司和自己的私有仓库就都不会做校验了。
3,
创建一个module的仓库, git clone git@git.xxx.com:mmgithub123/mysqltaskdirdemo.git
cd mysqltaskdirdemo
创建go module ,使用命令go mod init module_name(mysqltaskdirdemo)
然后使用go mod tidy 可以下载相应的包(都是从GOPROXY=https://goproxy.cn 里下载的)
4,在 go env的输出里
GOMODCACHE=C:\Users\admin\go\pkg\mod
GOMODCACHE字段定义了所有包的源码位置,go mod tidy 就是把代码下载到这个位置
5,还有一个go.sum文件 用hash code校验代码包的正确性,防止代码被改动过
6,vendor的概念
go mod vendor 命令与go mod tidy相对应。go mod tidy是下载到GOMODCACHE指定的工作空间目录。而go mod vendor是直接把代码下载到项目源码目录里
7,包
同一个目录的go文件,包名必须相同,要么都是main,要么和目录名相同。该目录下所有文件的函数可以相互自由调用。不需要引用。
github.com/ucloud/sdk-go/cloud/request
github.com/ucloud/sdk-go module
cloud/request 目录
request 包名
module/目录/ 包名
Golang 中 int()初始化函数
在Golang 程序执行时导入包语句会自动触发包内部 init()函数的调用
init()函数没有参数也没有返回值
init()函数在程序运行时自动被调用执行,不能在代码中主动调用它
包初始化执行的顺序:
全局声明=====>init()======>main()
-----------------------------------------------------------------------------------------------------------------------------------
字符串,数组,切片,map
字符串本质是byte数组
输出:
97
98
99
0
false
3
true
实际上,当map的key不存在的时候,它的返回值为这个类型的默认返回值
golang数据类型:
整型int,默认值0
浮点型float,默认值0.00000
布尔型bool,默认值false
字符串string,默认值""
数组array,默认值空数组
切片slice,默认值nil
指针pointer,默认值nil
结构体struct,根据结构体内部的基础数据类型进行初始化赋值
接口interface,nil
映射map,根据map元素的基础数据类型初始化
管道channel
函数func
数组:
数组的元素类型和数组的大小都固定了。
数组的定义,及定义二维数组
func main() {
array := [4]int{1, 2, 4, 4} 四个int类型元素的数组
}
创建数组和创建切片非常相似,如果你在 [] 指定了值,那么创建的是一个数组,反之就是一个切片。
func main() {
slice := []int{1, 2, 4, 4} 四个int类型元素的切片
}
切片还可以使用make进行定义
func main(){
slice := make([]int, 4)
}
切片:
数组有一个致命的缺陷,那就是大小固定,这个特性并不能满足我们平时的开发需求,所以 Go 的切片由此诞生。
从已有的数组和切片中,切新的切片:
s := arr[startIndex:endIndex]
将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。
s := arr[startIndex:]
默认 endIndex 时将表示一直到arr的最后一个元素。
s := arr[:endIndex]
默认 startIndex 时将表示从 arr 的第一个元素开始。
container
list
Golang标准库——container - 简书 (jianshu.com)
语法:
for,if,等语句里定义的临时变量,作用域只在if,for这些语句之内,
所以想取得下标i,一边在for语句之上先定义,这样:
i:=0
for ;i<len(array);i++{
if array[i] == 1
break
}
fmt.println(i) //这里就能取到i了,如果直接for i:=0;i<len(array);i++{} 这里i取不到
-----------------------------------------------------------------------------------------------------------------------------------------------
编程思想:
要会利用循环及其条件,来进行多逻辑,多分支控制,典型的就是二叉树的栈迭代遍历(可以做出类似于递归的效果),如下:
这里root变量的定义以及使用到for循环里,就牢牢把握住了树这个结构的特点
list方式实现栈
---------------------------------------------------------------------------------------------------------------------------------------------------
日志包
go.uber.org/zap Zap是Uber开源的Go高性能日志库,其优势在于实时写结构化日志(Structured Logging)到文件具有很好的性能
类http连接池,请求失败重试
github.com/hashicorp/go-retryablehttp
http 包
比如还需要在请求的 Header 中增加一些字段:
client := &http.Client{
Timeout: 5 * time.Second,
}
req, _ := http.NewRequest("GET", "https://golang.org", nil)
req.Header.Add("User-Id", "userid123456")
resp, _ := client.Do(req)
defer resp.Body.Close()
-----------------------------------------------------------------------------------------------------------
select
select 语句只能与通道联用,它一般由若干个分支组成。每次执行这种语句的时候,一般只有一个分支中的代码会被运行。
当监听的通道没有状态是可读或可写的, select 是阻塞的;只要监听的通道中有一个状态是可读或可写的,则 select 就不会阻塞,而是进入处理就绪通道的分支流程。如果监听的通道有多个可读或可写的状态, 则 select 随机选取一个处理。
select 的特点是只要其中有一个 case 已经完成,程序就会继续往下执行,而不会考虑其他 case 的情况。
为了在使用 select 选择器时不陷入阻塞状态,可以在 select 代码块中添加 default 关键字,当 case 条件全部都不满足时,默认进入 default 分支,执行完 default 分支的代码后,退出 select 选择器。
--------------------------------------------------------------------------------------------------------------------------------
close
如果通道没有被关闭,当从通道中没有读取到信息时,读取操作将会产生程序阻塞。所以使用 close 函数的目的是关闭不会再写入数据的通道,告诉通道读取方,所有数据发送完毕
---------------------------------------------------------------------------------------------------
waitgroup
close
select
buffered channel
我们最好用 先统一 Add ,再并发 Done ,最后 Wait 这种标准方式,来使用 WaitGroup 值。 尤其不要在调用 Wait 方法的同时,并发地通过调用 Add
方法去增加其计数器的值,因为这也有可能引发 panic 。
https://blog.csdn.net/wohu1104/article/details/105846372/
1. 无缓冲的通道
无缓冲的通道(unbuffered channel)是指在接收前没有能力保存任何值的通道。
这种类型的通道要求发送 goroutine 和接收 goroutine 同时准备好,才能完成发送和接收操作。
如果两个 goroutine 没有同时准备好,通道会导致先执行发送或接收操作的 goroutine 阻塞等待。这种对通道进行发送和接收的交互行为本身就是同步的。其中任意一个操作都无法离开另一个操作单独存在。
下图展示两个 goroutine 如何利用无缓冲的通道来共享一个值。
- 两个
goroutine都到达通道,但两者都没有开始执行发送或者接收。 - 左侧的
goroutine将它的手伸进了通道,这模拟了向通道发送数据的行为。这时,这个goroutine会在通道中被锁住,直到交换完成。 - 右侧的
goroutine将它的手放入通道,这模拟了从通道里接收数据。这个goroutine一样也会在通道中被锁住,直到交换完成。 - 进行交换。
- 右侧的
goroutine拿到数据。 - 两个
goroutine都将它们的手从通道里拿出来,这模拟了被锁住的goroutine得到释放。两个goroutine现在都可以去做别的事情了。

图:使用无缓冲的通道在 goroutine 之间同步, 摘自 《Go 语言实战》
package main
import (
"runtime"
)
func main() {
c := make(chan struct{})
go func(i chan struct{}) {
sum := 0
for i := 0; i <= 10000; i++ {
sum += i
}
println("sum is :", sum)
// 写通道
c <- struct{}{}
}(c)
//NumGoroutine 可以返回当前程序的 goroutine 数目
println("NumGoroutine=", runtime.NumGoroutine())
// 读取通道 c, 通过通道进行同步等待
<-c
}
无缓冲通道需要发送和接收配对。否则会被阻塞,直到另一方准备好后被唤醒。
package main
import "fmt"
func main() {
data := make(chan int) // 数据交换队列
exit := make(chan bool) // 退出通知
go func() {
for d := range data { // 从队列迭代接收数据,直到 close 。
fmt.Println(d)
}
fmt.Println("recv over.")
exit <- true // 发出退出通知。
}()
data <- 1 // 发送数据。
data <- 2
data <- 3
close(data) // 关闭队列。
fmt.Println("send over.")
<-exit // 等待退出通知。
}
输出:
1
2
3
send over.
recv over.
2. 有缓冲的通道
在无缓冲通道的基础上,为通道增加一个有限大小的存储空间形成带缓冲通道。带缓冲通道在发送时无需等待接收方接收即可完成发送过程,并且不会发生阻塞,只有当存储空间满时才会发生阻塞。同理,如果缓冲通道中有数据,接收时将不会发生阻塞,直到通道中没有数据可读时,通道将会再度阻塞。
有缓冲的通道(buffered channel)是一种在被接收前能存储一个或者多个值的通道。
这种类型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也会不同。
只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。
这导致有缓冲的通道和无缓冲的通道之间的一个很大的不同:
-
无缓冲的通道保证进行发送和接收的
goroutine会在同一时间进行数据交换; -
有缓冲的通道没有这种保证。
在下图中可以看到两个 goroutine 分别向有缓冲的通道里增加一个值和从有缓冲的通道里移除一个值。
- 右侧的
goroutine正在从通道接收一个值。 - 右侧的
goroutine独立完成了接收值的动作,而左侧的goroutine正在发送一个新值到通道里。 - 左侧的
goroutine还在向通道发送新值,而右侧的goroutine正在从通道接收另外一个值。这个步骤里的两个操作既不是同步的,也不会互相阻塞。 - 所有的发送和接收都完成,而通道里还有几个值,也有一些空间可以存更多的值。

图:使用有缓冲的通道在 goroutine 之间同步数据,摘自 《Go 语言实战》
有缓冲通道例子
package main
import (
"runtime"
)
func main() {
c := make(chan struct{})
ci := make(chan int, 100)
go func(i chan struct{}, j chan int) {
for i := 0; i <= 10; i++ {
ci <- i
}
close(ci)
// 写通道
c <- struct{}{}
}(c, ci)
//NumGoroutine 可以返回当前程序的 goroutine 数目
println("NumGoroutine=", runtime.NumGoroutine())
// 读取通道 c, 通过通道进行同步等待
<-c
// 此时ci 通道已经关闭,匿名函数启动的goroutine 已经退出
println("NumGoroutine=", runtime.NumGoroutine())
// 但是通道 ci 还可以继续读取
for v := range ci {
println("v is :", v)
}
}
异步方式也就是有缓冲的通道通过判断缓冲区来决定是否阻塞。
- 缓冲区已满,发送被阻塞;
- 缓冲区为空,接收被阻塞;
通常情况下,异步 channel 可减少排队阻塞,具备更高的效率。但应该考虑使用指针规避大对象拷贝,将多个元素打包,减小缓冲区大小等。
为什么Go语言对通道要限制长度而不提供无限长度的通道?
我们知道通道( channel )是在两个 goroutine 间通信的桥梁。使用 goroutine 的代码必然有一方提供数据,一方消费数据。当提供数据一方的数据供给速度大于消费方的数据处理速度时,如果通道不限制长度,那么内存将不断膨胀直到应用崩溃。
因此,限制通道的长度有利于约束数据提供方的供给速度,供给数据量必须在消费方处理量+通道长度的范围内,才能正常地处理数据。
package main
import "fmt"
func main() {
data := make(chan int, 3) // 缓冲区可以存储 3 个元素
exit := make(chan bool)
data <- 1 // 在缓冲区未满前,不会阻塞。
data <- 2
data <- 3
go func() {
for d := range data { // 在缓冲区未空前,不会阻塞。
fmt.Println(d)
}
exit <- true
}()
data <- 4 // 如果缓冲区已满,阻塞。
data <- 5
close(data)
<-exit
}
缓冲区是内部属性,并非类型构成要素。
var a, b chan int = make(chan int), make(chan int, 3)
除用 range 外,还可用 ok-idiom 模式判断 channel 是否关闭。
for {
if d, ok := <-data; ok {
fmt.Println(d)
} else {
break
}
}
向 closed channel 发送数据引发 panic 错误,接收立即返回零值。而 nil channel,无论收发都会被阻塞。
// 这个示例程序展示如何使用
// 有缓冲的通道和固定数目的
// goroutine来处理一堆工作
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
const (
numberGoroutines = 4 // 要使用的goroutine的数量
taskLoad = 10 // 要处理的工作的数量
)
// wg用来等待程序完成
var wg sync.WaitGroup
// init初始化包,Go语言运行时会在其他代码执行之前
// 优先执行这个函数
func init() {
// 初始化随机数种子
rand.Seed(time.Now().Unix())
}
// main是所有Go程序的入口
func main() {
// 创建一个有缓冲的通道来管理工作
tasks := make(chan string, taskLoad)
// 启动goroutine来处理工作
wg.Add(numberGoroutines)
for gr := 1; gr <= numberGoroutines; gr++ {
go worker(tasks, gr)
}
// 增加一组要完成的工作
for post := 1; post <= taskLoad; post++ {
tasks <- fmt.Sprintf("Task : %d", post)
}
// 当所有工作都处理完时关闭通道
// 以便所有goroutine退出
close(tasks)
// 等待所有工作完成
wg.Wait()
}
// worker作为goroutine启动来处理
// 从有缓冲的通道传入的工作
func worker(tasks chan string, worker int) {
// 通知函数已经返回
defer wg.Done()
for {
// 等待分配工作
task, ok := <-tasks
if !ok {
// 这意味着通道已经空了,并且已被关闭
fmt.Printf("Worker: %d : Shutting Down\n", worker)
return
}
// 显示我们开始工作了
fmt.Printf("Worker: %d : Started %s\n", worker, task)
// 随机等一段时间来模拟工作
sleep := rand.Int63n(100)
time.Sleep(time.Duration(sleep) * time.Millisecond)
// 显示我们完成了工作
fmt.Printf("Worker: %d : Completed %s\n", worker, task)
}
}
输出:
Worker: 4 : Started Task : 2
Worker: 1 : Started Task : 1
Worker: 2 : Started Task : 3
Worker: 3 : Started Task : 4
Worker: 4 : Completed Task : 2
Worker: 4 : Started Task : 5
Worker: 2 : Completed Task : 3
Worker: 2 : Started Task : 6
Worker: 3 : Completed Task : 4
Worker: 3 : Started Task : 7
Worker: 3 : Completed Task : 7
Worker: 3 : Started Task : 8
Worker: 4 : Completed Task : 5
Worker: 4 : Started Task : 9
Worker: 1 : Completed Task : 1
Worker: 1 : Started Task : 10
Worker: 3 : Completed Task : 8
Worker: 3 : Shutting Down
Worker: 2 : Completed Task : 6
Worker: 2 : Shutting Down
Worker: 1 : Completed Task : 10
Worker: 1 : Shutting Down
Worker: 4 : Completed Task : 9
Worker: 4 : Shutting Down
在main函数的第31行,创建了一个string类型的有缓冲的通道,缓冲的容量是10。在第34行,给WaitGroup赋值为4,代表创建了4个工作 goroutine。之后在第35行到第37行,创建了4个 goroutine,并传入用来接收工作的通道。在第40行到第42行,将10个字符串发送到通道,模拟发给 goroutine 的工作。一旦最后一个字符串发送到通道,通道就会在第46行关闭,而main函数就会在第49行等待所有工作的完成。
第46行中关闭通道的代码非常重要。当通道关闭后,goroutine 依旧可以从通道接收数据,但是不能再向通道里发送数据。能够从已经关闭的通道接收数据这一点非常重要,因为这允许通道关闭后依旧能取出其中缓冲的全部值,而不会有数据丢失。从一个已经关闭且没有数据的通道里获取数据,总会立刻返回,并返回一个通道类型的零值。如果在获取通道时还加入了可选的标志,就能得到通道的状态信息。
在worker函数里,可以在第58行看到一个无限的for循环。在这个循环里,会处理所有接收到的工作。每个 goroutine 都会在第60行阻塞,等待从通道里接收新的工作。一旦接收到返回,就会检查ok标志,看通道是否已经清空而且关闭。如果ok的值是false,goroutine 就会终止,并调用第56行通过defer声明的Done函数,通知main有工作结束。
如果ok标志是true,表示接收到的值是有效的。第71行和第72行模拟了处理的工作。一旦工作完成,goroutine 会再次阻塞在第60行从通道获取数据的语句。一旦通道被关闭,这个从通道获取数据的语句会立刻返回,goroutine 也会终止自己。
3. WaitGroup
Go 语言中除了可以使用通道(channel)和互斥锁进行两个并发程序间的同步外,还可以使用等待组进行多个任务的同步,等待组可以保证在并发环境中完成指定数量的任务。sync.WaitGroup 类型(以下简称WaitGroup类型)是开箱即用的,也是并发安全的。
一般情况下,我会用这个方法来记录需要等待的 goroutine 的数量。相对应的,这个类型的 Done 方法,用于对其所属值中计数器的值进行减一操作。我们可以在需要等待的 goroutine 中,通过 defer 语句调用它。而此类型的 Wait 方法的功能是,阻塞当前的 goroutine ,直到其所属值中的计数器归零。如果在该方法被调用的时候,那个计数器的值就是 0,那么它将不会做任何事情。
goroutine 和 chan , 一个用于并发,另一个用于通信。没有缓冲的通道具有同步的功能,除此之外, sync 包也提供了多个 goroutine 同步的机制,主要是通过 WaitGroup 实现的。
WaitGroup 值中计数器的值不能小于 0,是因为这样会引发一个 panic 。

如果在一个此类值的 Wait 方法被执行期间,跨越了两个计数周期,那么就会引发一个 panic 。纵观上述会引发 panic 的后两种情况,我们可以总结出这样一条关于 WaitGroup 值的使用禁忌,
即:不要把增加其计数器值的操作和调用其Wait方法的代码,放在不同的 goroutine 中执行。换句话说,要杜绝对同一个WaitGroup 值的两种操作的并发执行。
我们最好用 先统一 Add ,再并发 Done ,最后 Wait 这种标准方式,来使用 WaitGroup 值。 尤其不要在调用 Wait 方法的同时,并发地通过调用 Add 方法去增加其计数器的值,因为这也有可能引发 panic 。
在 sync.WaitGroup (等待组)类型中,每个 sync.WaitGroup 值在内部维护着一个计数,此计数的初始默认值为零。
主要的接口如下:
type WaitGroup struct {
// contains filtered or unexported fields
}
// 添加等待信号
func (wg *WaitGroup) Add(delta int)
// 释放等待信号
func (wg *WaitGroup) Done()
// 等待
func (wg *WaitGroup) Wait()
WaitGroup用来等待多个goroutine完成;main goroutine调用Add设置需要等待goroutine的数目;- 每一个
goroutine结束时调用Done(); Wait()被main用来等待所有的goroutine完成;
sync.WaitGroup 内部拥有一个计数器,计数器的值可以通过方法调用实现计数器的增加和减少。当我们添加了 N 个并发任务进行工作时,就将等待组的计数器值增加 N。每个任务完成时,这个值减 1。同时,在另外一个 goroutine 中等待这个等待组的计数器值为 0 时,表示所有任务已经完成。
代码示例:
package main
import (
"net/http"
"sync"
)
var wg sync.WaitGroup
var urls = []string{
"http://www.baidu.com",
"http://www.sina.com",
"http://www.qq.com",
}
func main() {
for _, url := range urls {
// 为每一个 url 启动一个 goroutine,同时给 wg 加 1
wg.Add(1)
go func(url string) {
// 当前go routine 结束后给wg 计数减1, wg.Done() 等价于wg.Add(-1)
// defer wg.Add(-1)
defer wg.Done()
// 发送 http get 请求并打印 http 返回码
resp, err := http.Get(url)
if err == nil {
println(resp.Status)
}
}(url)
}
// 等待所有请求结束
wg.Wait()
}
或者不使用匿名函数,如下
package main
import (
"net/http"
"sync"
)
var wg sync.WaitGroup
var urls = []string{
"http://www.baidu.com",
"http://www.sina.com",
"http://www.qq.com",
}
func getURLStatus(url string) {
// 当前go routine 结束后给wg 计数减1, wg.Done() 等价于wg.Add(-1)
// defer wg.Add(-1)
defer wg.Done()
// 发送 http get 请求并打印 http 返回码
resp, err := http.Get(url)
if err == nil {
println(resp.Status)
}
}
func main() {
for _, url := range urls {
// 为每一个 url 启动一个 goroutine,同时给 wg 加 1
wg.Add(1)
go getURLStatus(url)
}
// 等待所有请求结束
wg.Wait()
}
4. select
select 是类 UNIX 系统提供的一个多路复用系统API, Go 语言借用多路复用的概念,提供了 select 关键字,用于多路监昕多个通道。
select 语句只能与通道联用,它一般由若干个分支组成。每次执行这种语句的时候,一般只有一个分支中的代码会被运行。
当监听的通道没有状态是可读或可写的, select 是阻塞的;只要监听的通道中有一个状态是可读或可写的,则 select 就不会阻塞,而是进入处理就绪通道的分支流程。如果监听的通道有多个可读或可写的状态, 则 select 随机选取一个处理。
select 的特点是只要其中有一个 case 已经完成,程序就会继续往下执行,而不会考虑其他 case 的情况。
select 的用法与 switch 语言非常类似,由 select 开始一个新的选择块,每个选择条件由 case 语句来描述。与 switch 语句相比, select 有比较多的限制,其中最大的一条限制就是每个 case 语句里必须是一个 IO 操作。结构如下:
select{
case 操作1:
响应操作1
case 操作2:
响应操作2
…
default:
没有操作情况
}
操作1、操作2:包含通道收发语句,请参考下表。
| 操 作 | 语句示例 |
|---|---|
| 接收任意数据 | case <- ch; |
| 接收变量 | case d := <- ch; |
| 发送数据 | case ch <- 100; |
在 Go 中,支持通信操作的类型只有 chan ,所以 select 中的 case 条件只能是对 chan 类型变量的读写操作。由于 chan 类型变量的读写操作可能会引起阻塞,为了在使用 select 选择器时不陷入阻塞状态,可以在 select 代码块中添加 default 关键字,当 case 条件全部都不满足时,默认进入 default 分支,执行完 default 分支的代码后,退出 select 选择器。
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("开始时间:", time.Now().Format("2006-01-02 15:04:05"))
select {
case <-time.After(time.Second * 2):
fmt.Println("2秒后的时间:", time.Now().Format("2006-01-02 15:04:05"))
}
}
输出结果:
开始时间: 2021-02-08 14-14-42
2秒后的时间: 2021-02-08 14:14:44
time.After函数返回一个通道类型的变量,然后在case中从这个通道中读取信息,如果没有协程给这个通道发送信息,那么case将会一直阻塞。在调用After函数时,传入了一个时长作为参数,意思是从调用After函数算起,到设定的时长后,有协程将会向这个通道发送一条消息。当通道收到消息后,这个case条件满足,这个case分支下的代码将会被执
如果没有任意一条 select 语句可以执行(即所有的通道都被阻塞),那么有如下两种可能的情况:
-
如果给出了
default语句,那么就会执行default语句,同时程序的执行会从select语句后的语句中恢复; -
如果没有
default语句,那么select语句将被阻塞,直到至少有一个通信可以进行下去;
package main
func main() {
ch := make(chan int, 1)
go func(chan int) { // go func(ch chan int) { 这样写也可以? 为啥?
for {
select {
case ch <- 0:
case ch <- 1:
}
}
}(ch)
for i := 0; i < 10; i++ {
println(<-ch)
}
}
输出结果:
1
1
0
1
0
1
0
1
0
1
如果需要同时处理多个 channel ,可使用 select 语句。它随机选择一个可用 channel 做收发操作,或执行 default case 。
package main
import (
"fmt"
"os"
)
func main() {
a, b := make(chan int, 3), make(chan int)
go func() {
v, ok, s := 0, false, ""
for {
select { // 随机选择可⽤用 channel,接收数据。
case v, ok = <-a:
s = "a"
case v, ok = <-b:
s = "b"
}
if ok {
fmt.Println(s, v)
} else {
os.Exit(0)
}
}
}()
for i := 0; i < 5; i++ {
select { // 随机选择可用 channel,发送数据。
case a <- i:
case b <- i:
}
}
close(a)
select {} // 没有可用 channel,阻塞 main goroutine。
}
输出:
a 0
a 1
a 2
a 3
b 4
在循环中使用 select default case 需要小心,避免形成洪水。
- 如果在
select语句中发现某个通道已关闭,那么应该怎样屏蔽掉它所在的分支?
在 case 中通过第二个参数判断 chan 是否关闭,如果关闭则通过 make(chan type) 来对关闭的 chan 置 nil ,当再次执行到 select 时,因为 chan 时 nil 会进入阻塞而不会进入候选分支。
package main
import (
"fmt"
"time"
)
func main() {
i := 0
c := make(chan int, 2)
c <- 1
c <- 2
close(c)
for {
select {
case value, ok := <-c:
if !ok {
c = make(chan int)
fmt.Println("ch is closed")
} else {
fmt.Printf("value is %#v\n", value)
}
default:
time.Sleep(1e9) // 等待1秒钟
fmt.Println("default, ", i)
i = i + 1
if i > 3 {
return
}
}
}
}
输出结果:
value is 1
value is 2
ch is closed
default, 0
default, 1
default, 2
default, 3
- 在
select语句与for语句联用时,怎样直接退出外层的for语句?
- 可以使用
goto加lable跳转到for外面; - 可以设置一个额外的标记位,当
chan关闭时,设置flag=true,在for的最后判断flag决定是否break;
5. 用 channel 实现信号量 (semaphore)
package main
import (
"fmt"
"sync"
)
func main() {
wg := sync.WaitGroup{}
wg.Add(3)
sem := make(chan int, 1)
for i := 0; i < 3; i++ {
go func(id int) {
defer wg.Done()
sem <- 1 // 向 sem 发送数据,阻塞或者成功。
for x := 0; x < 3; x++ {
fmt.Println(id, x)
}
<-sem // 接收数据,使得其他阻塞 goroutine 可以发送数据。
}(i)
}
wg.Wait()
}
输出:
2 0
2 1
2 2
0 0
0 1
0 2
1 0
1 1
1 2
6. 用 closed channel 发出退出通知
close 函数声明如下:
func close(c chan<- Type)
内置的 close 函数,只能用于 chan 类型变量。使用 close 函数关闭通道后,这个通道不允许被写入新的信息,但是关闭操作不会清除通道中已有的内容,不影响通道被读取。示例代码如下:
package main
import (
"fmt"
"time"
)
func write(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i * 10
time.Sleep(time.Second * 1)
}
close(ch)
}
func read(ch chan int) {
for {
if val, ok := <-ch; ok {
fmt.Println("从通道中读取值:", val)
} else {
// 通道被关闭
fmt.Println("通道已关闭,退出读取程序")
break
}
}
}
func main() {
var ch = make(chan int, 10)
go write(ch)
read(ch)
}
上边的通道读取操作是:
val,ok := <-ch
当通道被关闭后:
- 如果从通道中读取到信息,则
ok值为true,val是一个有效值; - 如果从通道中没有读取到信息,则
ok值为false,此时的val是脏数据,切勿将ok为false时的val值拿去使用,此时的val值是chan指定数据类型的默认值。
如果通道没有被关闭,当从通道中没有读取到信息时,读取操作将会产生程序阻塞。所以使用 close 函数的目的是关闭不会再写入数据的通道,告诉通道读取方,所有数据发送完毕。
package main
import (
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
quit := make(chan bool)
for i := 0; i < 2; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
task := func() {
println(id, time.Now().Nanosecond())
time.Sleep(time.Second)
}
for {
select {
case <-quit: // closed channel 不会阻塞,因此可用作退出通知。
return
default: // 执行正常任务。
task()
}
}
}(i)
}
time.Sleep(time.Second * 5) // 让测试 goroutine 运行一会。
close(quit) // 发出退出通知。
wg.Wait()
}
7. channel 传参或者作为结构成员
channel 是第一类对象,可传参 (内部实现为指针) 或者作为结构成员。
package main
import "fmt"
type Request struct {
data []int
ret chan int
}
func NewRequest(data ...int) *Request {
return &Request{data, make(chan int, 1)}
}
func Process(req *Request) {
x := 0
for _, i := range req.data {
x += i
}
req.ret <- x
}
func main() {
req := NewRequest(10, 20, 30)
Process(req)
fmt.Println(<-req.ret)
}
8. 并发总结
- 并发是指
goroutine运行的时候是相互独立的。 - 使用关键字
go创建goroutine来运行函数。 goroutine在逻辑处理器上执行,而逻辑处理器具有独立的系统线程和运行队列。- 竞争状态是指两个或者多个
goroutine试图访问同一个资源。 - 原子函数和互斥锁提供了一种防止出现竞争状态的办法。
- 通道提供了一种在两个
goroutine之间共享数据的简单方法。 - 无缓冲的通道保证同时交换数据,而有缓冲的通道不做这种保证。
----------------------------------------------------------------------------------------------------------------
context
![]()
- context.Background():可以简单理解我们知道这个上下文要去干什么
- context.TODO():可以简单理解我们不清楚要使用哪个上下文、或者还没有可用的上下文
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context {
return background
}
// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter). TODO is recognized by static analysis tools that determine
// whether Contexts are propagated correctly in a program.
func TODO() Context {
return todo
}
context.Backgrand() 其实就是根,root,context是一个树状结构。
context.Background()
context.Background() 返回一个空的Context
我们可以用这个 空的 Context 作为 goroutine 的root 节点(如果把整个 goroutine 的关系看作 树状)
使用context.WithCancel(parent)函数,创建一个可取消的子Context
函数返回值有两个:子Context Cancel 取消函数
例如:
ctx, cancel := context.WithCancel(context.Background())
package main
import (
“fmt”
“context”
“time”
)
func contextDemo(name string, ctx context.Context) {
for {
if ok {
fmt.Println(name, “will expire at:”, deadline)
} else {
fmt.Println(name, “has no deadline”)
}
time.Sleep(time.Second)
}
}
主函数创建了三个contexts:
三秒超时的timeoutContext;
没有过期时间的cancelContext;
由cancelContext产生的从现在开始4小时过期的deadlineContext;
然后,启动三个contextDemo的goroutine。它们并发执行且每秒打印一次message。
主函数通过读取timeoutContext的Done()来实现等待goroutine超时退出。一但三秒超时,main函数就调用cancelFunc取消cancelContext中的goroutine,同时cancelContext衍生出来的4小时过期的deadlineContext的goroutine也将退出。
func main() {
timeout := 3 * time.Second
deadline := time.Now().Add(4 * time.Hour)
timeOutContext, _ := context.WithTimeout(
context.Background(), timeout)
cancelContext, cancelFunc := context.withCancel(
context.Background())
deadlineContext, _ := context.WithDeadline(
cancelContext, deadline)
go contextDemo("[timeoutContext]", timeOutContext)
go contextDemo("[cancelContext]", cancelContext)
go contextDemo("[deadlineContext]", deadlineContext)
// Wait for the timeout to expire
<- timeOutContext.Done()
// This will cancel the deadline context as well as its
// child - the cancelContext
fmt.Println("Cancelling the cancel context...")
cancelFunc()
<- cancelContext.Done()
fmt.Println("The cancel context has been cancelled...")
// Wait for both contexts to be cancelled
<- deadlineContext.Done()
fmt.Println("The deadline context has been cancelled...")
}
下面是输出结果:
[cancelContext] has no deadline
[deadlineContext] will expire at: 2017-07-29 09:06:02.34260363
[timeoutContext] will expire at: 2017-07-29 05:06:05.342603759
[cancelContext] has no deadline
[timeoutContext] will expire at: 2017-07-29 05:06:05.342603759
[deadlineContext] will expire at: 2017-07-29 09:06:02.34260363
[cancelContext] has no deadline
[timeoutContext] will expire at: 2017-07-29 05:06:05.342603759
[deadlineContext] will expire at: 2017-07-29 09:06:02.34260363
Cancelling the cancel context…
The cancel context has been cancelled…
The deadline context has been cancelled…
输出结果不变。接下来是最佳实践章节,将介绍一些指导原则,以便于我们恰当地使用context数据传递。
最佳实践
围绕context数据传递的几个最佳实践:
避免在context中传递函数参数;
在全局变量中为context中的数据分配一个对应key;
包中应该为key定义一个不可导出的类型,以防止发生冲突;
包中定义的key应该为其在context存储的数据提供类型安全访问方法;
HTTP请求的Context
context的常用场景之一就是在HTTP请求间传递信息。这些信息可能包含请求ID、认证证书等。在GO1.7,标准库net/http利用了context的优势,并且已经标准化,直接在request中加入了对context的支持。
func (r *Request) Context() context.Context
func (r *Request) WithContext(ctx context.Context) *Request
现在,我们可以使用一种标准方式把从headers中获取到的requestId传递到最终的处理函数。WithRequestID() 处理函数从"X-Request-ID"头部导出requestID并从正在使用的context中衍生出一个带有requestID的context。然后把它传递给调用链的下一个处理函数。公共函数GetRequestID()为处理函数提供了访问RequestID的途径,包括定义在其他包的处理函数。
const requestIDKey int = 0
func WithRequestID(next http.Handler) http.Handler {
return http.HandlerFunc(
func(rw http.ResponseWriter, req *http.Request) {
// Extract request ID from request header
reqID := req.Header.Get(“X-Request-ID”)
// Create new context from request context with
// the request ID
ctx := context.WithValue(
req.Context(), requestIDKey, reqID)
// Create new request with the new context
req = req.WithContext(ctx)
// Let the next handler in the chain take over.
next.ServeHTTP(rw, req)
}
)
}
func GetRequestID(ctx context.Context) string {
ctx.Value(requestIDKey).(string)
}
func Handle(rw http.ResponseWriter, req *http.Request) {
reqID := GetRequestID(req.Context())
…
}
func main() {
handler := WithRequestID(http.HandlerFunc(Handle))
http.ListenAndServe("/", handler)
}
总结
基于Context的编程为我们提供了一套标准和良好支持的方法,它解决了两个常见的问题:goroutine的生命周期管理和信息传递。
以最佳实践为准,在合适的场景下使用contexts,你的编码能力将会大幅提升。
---------------------------------------------------------------------------------------------------------------------------
gorm
gorm约定大于配置
GORM 默认会使用名为ID的字段作为表的主键
表名默认:将驼峰的结构体名称用_下划线分隔,并在末尾增加复数s
//UserInfo -> user_infos
列名由字段名称进行下划线分割来生成
CreatedAt time.Time // column name is `created_at`
可以使用结构体tag指定列名:
type Animal struct {
AnimalId int64 `gorm:"column:beast_id"` // set column name to `beast_id`
Birthday time.Time `gorm:"column:day_of_the_beast"` // set column name to `day_of_the_beast`
Age int64 `gorm:"column:age_of_the_beast"` // set column name to `age_of_the_beast`
}
两个例子:
func payOrderTransaction() error { tx := models.DB().Begin() user := models.User{} user.ID = 1 order := models.Order{} tx.Set("gorm:query_option", "FOR UPDATE").First(&user) tx.Where("status=0").Order("RAND()").First(&order) if user.Balance >= order.Price { if order.ID > 0 && order.Status == 0 { //如果个人资金大于订单价格就支付 //这里有坑,在并发的情况下就会出问题,当两个请求同时走到了这里,就出现了刷单的情况 user.Balance = user.Balance - order.Price tx.Save(&user) order.Status = 1 tx.Save(&order) tx.Commit() return nil } else { tx.Rollback() return errors.New("重复支付订单") } } else { //抛出错误 tx.Rollback() return errors.New("个人账户金额小于订单金额") } }
/添加一个订单
func AddOneOrderTx(goodsId int64,buyNum int) (*model.Order, error) {
// 事务开始后,需要使用 tx 处理数据
tx := global.DBLink.Begin()
defer func() {
if r := recover(); r != nil {
fmt.Println("this is in defer recover")
tx.Rollback()
}
}()
if err := tx.Error; err != nil {
return nil,err
}
//添加order
order := model.Order{UserId: 1, SalePrice: "20.00"}
resultCO := tx.Debug().Create(&order) // 通过数据的指针来创建
if (resultCO.Error != nil) {
tx.Rollback()
return nil,resultCO.Error
}
/*
var z int = 0
var i int = 100 / z
fmt.Println("i:%i",i)
*/
//减库存
result := tx.Debug().Table("m_goods").Where("goodsId = ? and stock >= ?", goodsId,buyNum).Update("stock", gorm.Expr("stock - ?", buyNum))
if (result.Error != nil) {
tx.Rollback()
return nil,result.Error
}
if (result.RowsAffected <= 0){
tx.Rollback()
fmt.Println("减库存失败")
return nil,errors.New("减库存失败")
}
//添加订单商品
orderId := order.OrderId;
orderGoods := model.OrderGoods{OrderId:orderId,GoodsId:goodsId,BuyNum:buyNum}
resultCOG := tx.Debug().Create(&orderGoods) // 通过数据的指针来创建
if (resultCOG.Error != nil) {
tx.Rollback()
return nil,resultCOG.Error
}
//commit
fmt.Println("begin commit")
errCM := tx.Commit().Error
if (errCM != nil) {
fmt.Println("begin return1")
tx.Rollback()
return nil,errCM
}else {
fmt.Println("begin return2")
return &order,nil
}
}
在Tx中 开启事务Begin 直到commit ,如果中间涉及两个及以上不同的表操作,则在切换表是需要同时执行model 和 table 方法。否则,
将会遇到表名和字段不配的异常,如果恰巧误匹配上,
那简直是悲剧。
有这个坑吗?
-----------------------------------------------------------------------------------------------------------------------------
api使用速查:
0,errors包,有一个errorString结构(或者说对象),有一个New函数(也可以说是面向对象的set方法),有一个Error函数(也可以说是面向对象的get方法)
基本上,os包,io包,filepath包,http,都是一起联动使用的,从操作系统,文件系统,到输入输出,到文件路径,到网络。
一般是先os open os打开,然后到io的writer和reader,io的writer和reader给到网络或者其他文件系统,网络的另一端服务端通过io.copy()可以传递到不同的io及文件系统
1,filepath包,
顾名思义,专门处理文件路径相关的,比如获取相对路径,绝对路径,获取文件名,文件扩展名,获取目录名,合成组装文件名
使用自定义函数递归遍历目录:
filepath.WalkDir来实现文件和目录的浏览。
filepath.WalkDir需要输入待浏览的目录,以及一个回调函数:
回调函数原型:
type WalkDirFunc func(path string, d DirEntry, err error) error
回调函数的返回值控制着遍历流程。如果回调函数返回SkipDir,跳过当前目录,其他情况如果回调函数返回一个error,停止遍历并将此error上报。
回调函数的error参数,开发者可以自己控制,到底是略过这个错误,还是上报这个错误,如果上报这个错误,遍历就停止了,如果略过这个错误比如即使err != nil也返回nil,遍历继续(也相当于skipdir了?)。剩下用户就可以应用自己的业务逻辑了,比如下面例子里的计数,递归统计整个目录下有多少文件。
两种情况下,error参数是非nil的,os.Lstat调用出错,Readdirnames method方法出错。
type WalkDirFunc func(path string, d DirEntry, err error) error
fn将在传入的目录中的每个文件和子目录上被调用。下面是一个计算当前目录中所有文件数量的示例。
import (
"fmt"
"io/fs"
"path/filepath"
)
// example of counting all files in a root directory
func countFiles() int {
count := 0;
filepath.WalkDir(".", func(path string, file fs.DirEntry, err error) error {
if err != nil {
return err
}
if !file.IsDir() {
fmt.Println(path);
count++;
}
return nil;
});
return count;
}
2.2
io.Pipe() (*PipeReader, *PipeWriter)
reader writer交替运行。也就是说,Pipe适用于,产生了一条数据,紧接着就要处理掉这条数据的场景。而且因为其内部是一把大锁,因此是线程安全的。The client must close the response body when finished with it:
resp, err := http.Get("http://example.com/")
if err != nil {
// handle error
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
For control over HTTP client headers, redirect policy, and other settings, create a Client:
client := &http.Client{
CheckRedirect: redirectPolicyFunc,
}
resp, err := client.Get("http://example.com")
// ...
req, err := http.NewRequest("GET", "http://example.com", nil)
// ...
req.Header.Add("If-None-Match", `W/"wyzzy"`)
resp, err := client.Do(req)
// ...
For control over proxies, TLS configuration, keep-alives, compression, and other settings, create a Transport:
tr := &http.Transport{
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
DisableCompression: true,
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://example.com")
ListenAndServe starts an HTTP server with a given address and handler. The handler is usually nil, which means to use DefaultServeMux. Handle and HandleFunc add handlers to DefaultServeMux:
http.Handle("/foo", fooHandler)
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServe(":8080", nil))
More control over the server's behavior is available by creating a custom Server:
s := &http.Server{
Addr: ":8080",
Handler: myHandler,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())
所谓文件上传,就是用一个文件io,向http server发送请求,
ioutil包的方法
// Discard 是一个io.Writer 接口,调用它的Write方法将不做任何事情
// 并且始终成功返回
var Discard io.Writer = devNull(0)
// ReadAll读取r中的所有数据,返回读取的数据和遇到的错误
// 如果读取成功,则err返回nil,而不是EOF,因为ReadAll定义为读取
// 所有数据,所以不会把EOF当作错误处理
func ReadAll(r io.Reader) ([]byte, error)
// ReadFile读取文件中的所有数据,返回读取的数据和遇到的错误
// 如果读取成功,则err返回nil,而不是EOF
func ReadFile(filename string) ([]byte, error)
// WriteFile向文件中写入数据,写入前会清空文件。
// 如果文件不存在,则会以指定的权限创建文件
// 返回遇到的错误
func WriteFile(filename string, data []byte, perm os.FileMode) error
// ReadDir 读取指定目录中的所有目录和文件(不包括子目录)
// 返回读取到文件信息列表和遇到错误,列表是经过排序的。
func ReadDir(dirname string) ([]os.FileInfo, error)
// NopCloser 将r包装成一个ReadCloser类型,但Close方法不做任何事情
func NopCloser(r io.Reader) io.ReadCloser
// TempFile在dir目录中创建一个以prefix为前缀的临时文件,并将其以读写模式打开。返回创建的文件对象和遇到的错误
// 如果dir为空,则在默认的临时目录中创建文件(参加os.TempDir),多次调用会创建不通的临时文件,调用者可以通过f.Name()获取文件的完整路径
// 调用本函数所创建的临时文件,应该由调用者字节删除
func TempFile(dir, prefix string) (f *os.File, err error)
// TempDir功能通TempFile,只不过创建的是目录,返回目录的完整路径
func TempDir(dir, prefix string) (name string, err error)
6,golang yaml支持:
yaml package - gopkg.in/yaml.v3 - pkg.go.dev
序列化:把对象或者说结构体转化为字节序列,可以用yaml.Marshal
反序列化:把字符序列转化为对象或者说结构体,可以yaml.Unmarshal
---------------------------------------------------------------------------------------------------------
7,os/signal 包。处理操作系统信号包
信号和信号量,是操作系统里的概念,用处是用来同步进程的状态。响应的标准和api是:
go中的信号量
信号 值 动作 说明
SIGHUP 1 Term 终端控制进程结束(终端连接断开)
SIGINT 2 Term 用户发送INTR字符(Ctrl+C)触发
SIGQUIT 3 Core 用户发送QUIT字符(Ctrl+/)触发
SIGILL 4 Core 非法指令(程序错误、试图执行数据段、栈溢出等)
SIGABRT 6 Core 调用abort函数触发
SIGFPE 8 Core 算术运行错误(浮点运算错误、除数为零等)
SIGKILL 9 Term 无条件结束程序(不能被捕获、阻塞或忽略)
SIGSEGV 11 Core 无效内存引用(试图访问不属于自己的内存空间、对只读内存空间进行写操作)
SIGPIPE 13 Term 消息管道损坏(FIFO/Socket通信时,管道未打开而进行写操作)
SIGALRM 14 Term 时钟定时信号
SIGTERM 15 Term 结束程序(可以被捕获、阻塞或忽略)
SIGUSR1 30,10,16 Term 用户保留
SIGUSR2 31,12,17 Term 用户保留
SIGCHLD 20,17,18 Ign 子进程结束(由父进程接收)
SIGCONT 19,18,25 Cont 继续执行已经停止的进程(不能被阻塞)
SIGSTOP 17,19,23 Stop 停止进程(不能被捕获、阻塞或忽略) SIGTSTP 18,20,24 Stop 停止进程(可以被捕获、阻塞或忽略) SIGTTIN 21,21,26 Stop 后台程序从终端中读取数据时触发 SIGTTOU 22,22,27 Stop 后台程序向终端中写数据时触发
有些信号名对应着3个信号值,这是因为这些信号值与平台相关
SIGKILL和SIGSTOP这两个信号既不能被应用程序捕获,也不能被操作系统阻塞或忽略
package main
import (
"log"
)
func main() {
go func() {
log.Print("hello")
}()
}
你会发现这样什么东西都么得,main函数退出,导致go关键字开启的goroutine协程也关闭了。
稍微改造一下。
package main
import (
"log"
"time"
)
func main() {
go func() {
log.Print("hello")
}()
time.Sleep(4 * time.Second)
}
程序里当然不能这样写,要正经一点!
package main
import (
"log"
"os"
"os/signal"
"syscall"
)
func main() {
go func() {
log.Print("hello")
}()
quit := make(chan os.Signal)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
}
在go语言中:
附录:
linux signals
| Signal Name | Number | Description |
| SIGHUP | 1 | Hangup (POSIX) |
| SIGINT | 2 | Terminal interrupt (ANSI) |
| SIGQUIT | 3 | Terminal quit (POSIX) |
| SIGILL | 4 | Illegal instruction (ANSI) |
| SIGTRAP | 5 | Trace trap (POSIX) |
| SIGIOT | 6 | IOT Trap (4.2 BSD) |
| SIGBUS | 7 | BUS error (4.2 BSD) |
| SIGFPE | 8 | Floating point exception (ANSI) |
| SIGKILL | 9 | Kill(can't be caught or ignored) (POSIX) |
| SIGUSR1 | 10 | User defined signal 1 (POSIX) |
| SIGSEGV | 11 | Invalid memory segment access (ANSI) |
| SIGUSR2 | 12 | User defined signal 2 (POSIX) |
| SIGPIPE | 13 | Write on a pipe with no reader, Broken pipe (POSIX) |
| SIGALRM | 14 | Alarm clock (POSIX) |
| SIGTERM | 15 | Termination (ANSI) |
| SIGSTKFLT | 16 | Stack fault |
| SIGCHLD | 17 | Child process has stopped or exited, changed (POSIX) |
| SIGCONT | 18 | Continue executing, if stopped (POSIX) |
| SIGSTOP | 19 | Stop executing(can't be caught or ignored) (POSIX) |
| SIGTSTP | 20 | Terminal stop signal (POSIX) |
| SIGTTIN | 21 | Background process trying to read, from TTY (POSIX) |
| SIGTTOU | 22 | Background process trying to write, to TTY (POSIX) |
| SIGURG | 23 | Urgent condition on socket (4.2 BSD) |
| SIGXCPU | 24 | CPU limit exceeded (4.2 BSD) |
| SIGXFSZ | 25 | File size limit exceeded (4.2 BSD) |
| SIGVTALRM | 26 | Virtual alarm clock (4.2 BSD) |
| SIGPROF | 27 | Profiling alarm clock (4.2 BSD) |
| SIGWINCH | 28 | Window size change (4.3 BSD, Sun) |
| SIGIO | 29 | I/O now possible (4.2 BSD) |
| SIGPWR | 30 | Power failure restart (System V) |
关于2 9 15 这里写的不错:
https://www.baeldung.com/linux/sigint-and-other-termination-signals
How SIGINT Relates to SIGTERM, SIGQUIT and SIGKILL
Now that we understand more about signals, we can see how they relate to each other.
The default action for SIGINT, SIGTERM, SIGQUIT, and SIGKILL is to terminate the process. However, SIGTERM, SIGQUIT, and SIGKILL are defined as signals to terminate the process, but SIGINT is defined as an interruption requested by the user.
In particular, if we send SIGINT (or press Ctrl+C) depending on the process and the situation it can behave differently. So, we shouldn’t depend solely on SIGINT to finish a process.
As SIGINT is intended as a signal sent by the user, usually the processes communicate with each other using other signals. For instance, a parent process usually sends SIGTERM to its children to terminate them, even if SIGINT has the same effect.
In the case of SIGQUIT, it generates a core dump which is useful for debugging.
Now that we have this in mind, we can see we should choose SIGTERM on top of SIGKILL to terminate a process. SIGTERM is the preferred way as the process has the chance to terminate gracefully.
As a process can override the default action for SIGINT, SIGTERM, and SIGQUIT, it can be the case that neither of them finishes the process. Also, if the process is hung it may not respond to any of those signals. In that case, we have SIGKILL as the last resort to terminate the process.
-----------------------------------------------------------------------------------------------------------------------------------------------
conf/config.go
-------------------------------------------------
go包
解析ini配置,ini package - gopkg.in/ini.v1 - pkg.go.dev
类似Linux下的tail命令,tail package - github.com/hpcloud/tail - pkg.go.dev
kafka库,sarama package - github.com/Shopify/sarama - pkg.go.dev
dsn解析包,https://github.com/wjiec/gdsn
-----------------------------------------------------------------------------------------------------
go tips
获取时间
时间字符串转数字做运算,在转字符串,并结合字符串切割,提取日期字符串进行比较
判断字符串是否为空:
str := "" fmt.Println(str == "") fmt.Println(len(str ) == 0)
str == "" 和 len(str) == 0 都可以,而且编译成汇编之后代码是一样的,见https://www.jianshu.com/p/1180bdcb2e48,这个老哥专研精神不错。

浙公网安备 33010602011771号