标准库之unsafe

1. Go中对指针的限制 Go 的指针不能进行数学运算。 不同类型的指针不能相互转换。 不同类型的指针不能使用 == 或 != 比较。只有在两个指针类型相同或者可以相互转换的情况下,才可以对两者进行比较。另外,指针可以通过 == 和 != 直接和 nil 作比较。 不同类型的指针变量不能相互赋值。 使用unsafe包,可以一定程度上打破这些限制,那么为什么要打破这些限制。请看下文。 2. unsafe.Pointer unsafe.Pointer的定义 type ArbitraryType int type Pointer *ArbitraryType unsafe 包提供了 2 点重要的能力: 任何类型的指针和 unsafe.Pointer 可以相互转换。 uintptr 类型和 unsafe.Pointer 可以相互转换。 pointer 不能直接进行数学运算,但可以把它转换成 uintptr,对 uintptr 类型进行数学运算,再转换成 pointer 类型。利用这两个对象的相互转换,就可以打破上述4个限制。 // uintptr 是一个整数类型,它足够大,可以存储 type uintptr uintptr 还有一点要注意的是,uintptr 并没有指针的语义,意思就是 uintptr 所指向的对象会被 gc 无情地回收.而 unsafe.Pointer 有指针语义,可以保护它所指向的对象在“有用”的时候不会被垃圾回收。 3. 利用unsafe获取slice和map的长度 slice和map的长度都存储在其内部变量中,因此我们先来看这两个结构体定义: // runtime/slice.go type slice struct { array unsafe....

March 4, 2022 · 2 min · 李昌

静态代码检查: golangci-lint

1. 简介 golangci-lint 是对golang进行静态代码检查的工具。其具有以下特性: 速度非常快:golangci-lint 是基于 gometalinter 开发的,但是平均速度要比 gometalinter 快 5 倍。golangci-lint 速度快的原因有三个:可以并行检查代码;可以复用 go build 缓存;会缓存分析结果。 可配置:支持 YAML 格式的配置文件,让检查更灵活,更可控。 IDE 集成:可以集成进多个主流的 IDE,例如 VS Code、GNU Emacs、Sublime Text、Goland 等。 linter 聚合器:1.41.1 版本的 golangci-lint 集成了 76 个 linter,不需要再单独安装这 76 个 linter。并且 golangci-lint 还支持自定义 linter。 最小的误报数:golangci-lint 调整了所集成 linter 的默认设置,大幅度减少了误报。 良好的输出:输出的结果带有颜色、代码行号和 linter 标识,易于查看和定位。 2. 安装 # 安装 go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.41.1 # 检查是否安装成功 golangci-lint version # 输出 golangci-lint 版本号,说明安装成功 golangci-lint has version v1.44.0 built from (unknown, mod sum: "h1:YJPouGNQEdK+x2KsCpWMIBy0q6MSuxHjkWMxJMNj/DU=") on (unknown) 3....

February 9, 2022 · 2 min · 李昌

channel的行为

1. nil channel 接收 接收goroutine阻塞 发送 发送个goroutine阻塞 2. 向无缓冲channel发送消息 接受队列有goroutine 接收端将收到消息 接收队列无goroutine 发送goroutine将阻塞 已有发送goroutine阻塞 发送goroutine将阻塞 3. 从无缓冲channel接收消息 无发送goroutine 接收端阻塞 有发送goroutine 收到消息 4. 向有缓冲channel发送消息 队列未满 正常发送 队列已满 发送端阻塞 5. 从有缓冲channel接收消息 队列中有消息 正常接收 队列中无消息 接收端阻塞 6. 对close channel的操作 向closed channel发送 panic...

January 2, 2022 · 1 min · 李昌

golang中的tag

1. tag的基本介绍 字段标签可以存储元信息,这些元信息可以使用反射来访问。通常这些元信息用来提供一个字段如何从一种格式编码至另一种格式的相关信息(或是数据应如何在数据库中存储等)。但实际上标签可以存储任何你想要的元信息,无论是你自己使用还是由另一个包使用。 就像reflect.StructTag文档中提到的那样,字段标签通常是由空格分割的key:"value"列表,例如: type User struct { Name string `json:"name" xml:"name"` } 其中的key通常表示后面"value"所对应的包,例如json这个key将被encoding/json这个包使用。 如果需要在"value"中传递多个值,那么通常使用,逗号来分割,例如: Name string `json:"name,omitempty" xml:"name"` 值为破折号通常代表在处理时忽略该字段,例如在json中代表不要序列化这个字段 2. 例子:获取自定义tag 我们可以使用反射包来获取结构体字段的值。首先我们需要获取结构体的Type,然后查询字段,可以使用Type.Field(i int)或者Type.FieldByName(name string)。这些方法返回一个代表结构体字段的StructField值和一个代表tag的类型为StructTag的StructField.Tag值。 前面我们提到,字段标签通常是由空格分割的key:"value"列表,如果你的确是这么做的,你可以使用StructTag.Get(key string)这个方法来获取这个key对应的value。如果你不是这么做的,Get()方法可能不能解析key:"value"对并找到你想要的标签。如果你没有遵循字段标签通常是由空格分割的key:"value"列表,那么你可能需要实现自己的解析逻辑。 go1.7中添加了StructTag.Lookup()方法,这个方法的行为类似于Get(),但其将不包含给定键的标签与将空字符串与给定键相关联的标签区分开来。 来看下面这个例子: type User struct { Name string `mytag:"MyName"` Email stirng `mytag:"MyEmail"` } u := User{"Bob", "bob@cc.com"} t := reflect.TypeOf(u) for _ fieldName := range []string{"Name", "Email"} { field, found := t.FieldByName(fieldName) if !found { continue } fmt.Printf("\nField: User.%s\n", fieldName) fmt.Printf("\tWhole tag value : %q\n", field....

September 14, 2021 · 2 min · 李昌

ORM之sqlc

1. 安装sqlc go get github.com/kyleconroy/sqlc/cmd/sqlc 2. 基本使用 建立基本项目结构 mkdir sqlc-demo cd sqlc-demo go mod init sqlc-demo 在sqlc-demo中建立如下目录结构: . ├── db │ ├── queries │ ├── schema │ └── sqlc └── go.mod 其中query中存储查询语句,schema中存储数据库表结构,sqlc中存储生成的代码。 基本表结构 sqlc-demo/db/schema/table.sql CREATE TABLE "accounts" ( "id" bigserial PRIMARY KEY, "owner" varchar NOT NULL, "balance" bigint NOT NULL, "currency" varchar NOT NULL, "created_at" timestamptz NOT NULL DEFAULT (now()) ); CREATE TABLE "entries" ( "id" bigserial PRIMARY KEY, "account_id" bigint NOT NULL, "amount" bigint NOT NULL, "created_at" timestamptz NOT NULL DEFAULT (now()) ); CREATE TABLE "transfers" ( "id" bigserial PRIMARY KEY, "from_account_id" bigint NOT NULL, "to_account_id" bigint NOT NULL, "amount" bigint NOT NULL, "created_at" timestamptz NOT NULL DEFAULT (now()) ); 配置文件 sqlc-demo/sqlc....

August 22, 2021 · 3 min · 李昌

依赖注入:wire包的使用

官方教程,写的很好,我就不多说了 Let’s learn to use Wire by example. The Wire guide provides thorough documentation of the tool’s usage. For readers eager to see Wire applied to a larger server, the guestbook sample in Go Cloud uses Wire to initialize its components. Here we are going to build a small greeter program to understand how to use Wire. The finished product may be found in the same directory as this README....

July 6, 2021 · 9 min · 李昌

5种goroutine池的实现之对比

1. wazsmwazsm/mortar(★74) 简单介绍 创建一个容量为 N 的池, 在池容量未满时, 每塞入一个任务(生产任务), 任务池开启一个 worker (建立协程) 去处理任务(消费任务)。 当任务池容量赛满,每塞入一个任务(生产任务), 任务会被已有的 N 个 worker 抢占执行(消费任务),达到协程限制的功能。但worker创建后不会回收,除非将整个pool撤销。 结构 type Task struct { Handler func(v ...interface{}) // 函数签名 Params []interface{} // 参数 } // Pool task pool type Pool struct { capacity uint64 // 池的容量,自行制定 runningWorkers uint64 // 正在运行的worker status int64 // 池的状态 chTask chan *Task // 任务队列,worker从中获取任务 PanicHandler func(interface{}) // 自定义的PanicHandler,防止因某个goroutine发生panic而导致服务崩溃。 sync.Mutex // 全局锁 } 核心代码...

June 19, 2021 · 13 min · 李昌

golang中context包的使用

context包定义了Context类型,这个类型在API边界即进程中传递截止日期、同步信号,请求值等相关信息。 1. 对context包的介绍 在服务器的传入请求中应包含context,而对服务器的传出调用应接收一个context。它们之间的调用链必须包含context,或是衍生的WithCancel, WithDeadline, WithTimeout, WithValue。当一个WithCancel Context被“cancel”,那么当前context所派生的所有context也都将被取消。 WithCancel, WithDeadline, WithTimeout接收一个Context对象(父对象),并返回其父对象的一个携带有cancel/deadline/timeout的一个拷贝(子对象)。调用CancelFunc会取消其子对象及子对象的子对象等,删除父对象对子对象的引用,并停止所有关联的计时器。未能调用CancelFunc将造成父对象结束前或计时器被触发前子对象的泄露。使用go vet工具可以检查所有控制流路径上是否都使用了CancelFunc 使用context的程序应遵循以下规则,以使各个包之间的接口保持一致,并启用静态分析工具来检查上下文传播: 不要将context存储在结构类型中,而是将context明确传递给需要它的每个函数。Context应该是第一个函数,通常命名为ctx。 func DoSomething(ctx context.Context, arg Arg) error { // ...use ctx... } 不要传递一个值为nil的context,即使一个函数允许这样做。如果你不确定Context的作用那就请传递context.TODO。 只在进程和API间传递请求范围数据时使用context值,不要用于将可选参数传递给函数。 同样的Context可以传递给运行在不同goroutine中的函数,Context是线程安全的。 2. Context接口 type Context interface { Done() <-chan struct{} Err() error Deadline() (deadline time.Time, ok bool) Value(key interface{}) interface{} } Context是一个接口,其定义非常的简单,只包含4个方法: Done() <-chan struct{} Done()方法将一个channel作为取消信号返回给持有context的函数,当该channel被关闭(即Done()被调用),这些函数应该立即停止其工作并返回。 Err() error Err()返回一个Error,说明为什么取消context。如果Done()没有被调用,那么Err返回nil。 Deadline() (deadline time.Time, ok bool) Deadline()方法返回持有这个context的函数的预期结束时间。如果并没有设置deadline,那么返回的ok将被设置为false。...

May 23, 2021 · 3 min · 李昌

Golang中反射reflect的基本使用

在计算机领域,反射是指一类应用,它们能够自描述和自控制。也即是说,这类应用通过采用某种机制来实现对自己行为的描述和监测,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。 反射(reflect)让我们能在运行期探知对象的类型信息和内存结构,这从一定程度上弥(mi)补了静态语言在动态行为上的不足。 反射(reflect)是在计算机程序运行时,访问,检查,修改它自身的一种能力,是元编程的一种形式。 Go语音提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法和它们支持的内在操作,但是在编译时并不知道这些变量的具体类型。这种机制被称为反射。反射也可以让我们将类型本身作为第一类的值类型处理。 1. 为何我们需要反射? fmt.Fprintf函数提供字符串格式化处理逻辑,它可以对任意类型的值格式化并打印,甚至支持用户自定义的类型。 让我们也来尝试实现一个类似功能的函数。为了简单起见,我们的函数只接收一个参数,然后返回和fmt.Sprint类似的格式化后的字符串。我们实现的函数名也叫Sprint。 这里我们使用switch类型分支来对不同的类型进行处理。 func Sprint(x interface{}) string { type stringer interface { String() string } switch x := x.(type) { case stringer: return x.String() case string: return x case int: return strconv.Itoa(x) // ...similar cases for int16, uint32, and so on... case bool: if x { return "true" } return "false" default: // array, chan, func, map, pointer, slice, struct return "?...

May 22, 2021 · 4 min · 李昌

野生Goroutine带来的问题及对应解决方案

一、野生goroutine的问题 引言: 毋庸置疑,golang原生的goroutine极大降低了程序员使用并发编程的代价,程序员们不需要再去关心如何实现接口、如何继承Thread类等多余的操作,只需要简简单单的go, 就可以开启一个并发的协程。但这种简单的使用方式同时也带来一些问题,这些goroutine不再受我们控制了,它们在运行时可能会发生任何错误或意外,而我们无法得知或去处理这些意外。我们将启动后不再受主进程控制的goroutine称为野生goroutine,本节将介绍野生goroutine存在的一些问题并介绍一些简单的解决方法。 1. goroutine中panic无法恢复 正常的函数中panic的recover import ( "fmt" ) func main(){ defer func(){ if err := recover(); err != nil{ fmt.Println(err) } }() var bar = []int{1} fmt.Println(bar[1]) } reflect: slice index out of range goroutine中panic的恢复 func main(){ defer func(){ if err := recover(); err != nil { // 在这里使用recover(),不能捕获panic fmt.Println("catch you, bad guy") } }() go func(){ fmt.Println("I'm in a goroutine") panic("come to catch me") }() time....

May 19, 2021 · 17 min · 李昌