Go

Go escape analysis

September 20, 2023
Go, Escape Analysis
Note

The meaning of escapes to the heap is variables needs to be shared across the function stack frames [between main() and Println()] …

… So globally access variables must be moved to heap as it requires runtime. So the output line 11:2 shows the same as the data variable moved to the heap memory.

From

Vscode go cannot find GOROOT directory

September 13, 2023
Go, Vscode
Mark

今天发现在windows上的vscode一直提示找不到go:go: cannot find GOROOT directory: c:\msys64\mingw64\lib\go

强制设置了go.goroot也不行,直到查看了GOENV文件(C:\Users\xxx\AppData\Roaming\go\env)之后,才发现里面有一行:GOROOT=c:\msys64\mingw64\lib\go,可能是当时在msys2安装go的时候加上的。

去掉它就恢复正常了。

$ go env
set GOENV=C:\Users\xxx\AppData\Roaming\go\env
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOMODCACHE=C:\Users\xxx\go\pkg\mod
set GOOS=windows
set GOPATH=C:\Users\xxx\go
set GOPRIVATE=
set GOPROXY=https://goproxy.cn,https://goproxy.io,direct
set GOROOT=C:\Program Files\Go

应该是这样的,如果用go env -w 来设置goroot,那么这个值就会保存到GOENV对应的文件里,如果是$env:GOROOT=xxx的方式来设置则不会修改GOENV文件里的内容。这时候,如果vscode是优先从GOENV文件来获取GOROOT的话,就可能会导致与实际的GOROOT不一致。

所以,如果再遇到以上错误,除了echo $env:GOROOT 看一下环境变量值之外,也要看一下GOENV文件。

不同系统之间通过网络对接

September 13, 2023
Go, Vscode
Mark

不同系统之间通过网络对接

数据库事件 #

  1. 可以用个事件表来做,在事务执行过程中添加事件(确保事务完成时事件也存在)。

  2. 在事务提交之后,先尝试做一次事件,如果成功了就把事件状态置为成功;如果失败了也没关系,另外开定时器来扫表进行重试执行。 – 此时不影响正常业务执行

  3. 在事件处理事务里的网络请求里加入超时控制,确保事件不会执行太久,导致接口过慢。

  4. 网络请求支持幂等,防止事件处理事务请求成功了,但是事务挂了导致状态未变更,这种情况下会重复请求多次。

skip locked实现 #

-- 条件字段必须有索引(status, [name, status]),排序字段必须是主键(id),此时刚好是所要锁定的行

start transaction;
-- select * from w_event we where status in (1) order by create_time asc limit 1 for update skip locked; -- 引入create_time作为排序字段时,会将符合条件的行都锁住,`limit 1`不起作用
select * from w_event we where status in (1) order by id asc limit 1 for update skip locked; -- 使用主键字段作排序时,`limit 1`则起作用
select * from w_event we where name = '测试' and status in (1) order by id asc limit 1 for update skip locked; -- 如果有多个字段作为条件,需要建立组合索引
SELECT object_name, index_name, lock_type, lock_mode, lock_data FROM performance_schema.data_locks; -- 查看上锁情况
select * from w_event we where status in (2) order by id asc limit 1 for update skip locked;
select * from w_event we where status in (3) order by id asc limit 1 for update skip locked;
commit;

可打开两个线程来验证上述事务执行,可以看到,当满足条件的记录有两条或以上时,当事务1查到记录1后,事务1未提交时,事务2不会拿到记录1,而是会拿到记录2. 也就实现了有锁则获取下一批数据的效果。

...

Go Empty Struct

September 1, 2023
Go
Mark

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	type A struct{}
	type B struct{}
    // 结构体里的字段都是`Empty Struct`时,占用空间为0
	type S struct {
		A A
		B B
	}
	var s S
	fmt.Println(unsafe.Sizeof(s)) // prints 0
    // 如果是指针,占用空间为8
	fmt.Println(unsafe.Sizeof(&s)) // prints 8

	var x [1000000000]struct{}
    // 可以同时存储A和B类型元素
	x[0] = A{}
	x[1] = B{}
	fmt.Println(unsafe.Sizeof(x)) // prints 0
    // 地址一样
	fmt.Printf("%p, %p", &x[0], &x[1]) // 0x54e3a0, 0x54e3a0
}

See also

NATS

April 24, 2023
Go, NATS
NATS

是什么? #

Home, Github

NATS 是一个简单、安全和高性能的通信系统,适用于数字系统、服务和设备。

NATS 是一种允许以消息形式分段的数据交换的基础架构。

基于主题 #

发布者将消息发到主题;订阅者订阅主题,在有消息到来时消费该消息。

主题命名规则:

基本字符:a to z, A to Z and 0 to 9 (区分大小写,不能包含空白字符).

特殊字符: . (分割符,分割不同部分,每部分视为一个token); * 和 > (通配符,*表示匹配一个token,>表示匹配一或多个token).

保留主题名称: 以 $ 开头的用在系统内部 (如:$SYS, $JS, $KV …)

发布-订阅 #

Core NATS: 一个主题,存在一个发布者,多个订阅者。

消息会复制到多个订阅者。

请求-响应 #

A request is sent, and the application either waits on the response with a certain timeout, or receives a response asynchronously.

– 请求发出后,应用要不等待响应超时,要不就异步收到一个响应。

...

Go Generic Join

January 6, 2023
Go, Generic, Join
Join

// NestedJoin like nested loop join
func NestedJoin[J, K, R any](
	left []J,
	right []K,
	match func(J, K) bool,
	mapper func(J, K) R,
) []R {
	var r = make([]R, 0, len(left))

	for _, j := range left {
		for _, k := range right {
			if !match(j, k) {
				continue
			}
			r = append(r, mapper(j, k))
		}
	}

	return r
}

// HashJoin like hash join
func HashJoin[K comparable, LE, RE, R any](
	left []LE,
	right []RE,
	lk func(item LE) K,
	rk func(item RE) K,
	mapper func(LE, RE) R,
) []R {
	var r = make([]R, 0, len(left))

	rm := KeyBy(right, rk)

	for _, le := range left {
		k := lk(le)
		re := rm[k]
		r = append(r, mapper(le, re))
	}

	return r
}

Code From

...

泛型

May 30, 2022
Go, TypeScript
Generic

泛型 #

是什么? #

Type parameter, 类型参数。func Add[T Number](x, y T) (r T),其中的T就是类型参数,它被接口Number所约束。

type Number interface {
    int | float32
}

调用方除了可自行决定参数值之外,还可以自行决定参数类型。Add[int](1, 2),在调用时指定T的类型为int,同时传入参数值1,2必须是int类型。

这样使得代码更灵活,更有扩展性,同时更安全。

Go泛型 #

为什么? #

静态语言,类型固定,比如这个函数:func Add(x, y int) int就要求参数和结果都必须是整型。

那如果后来又需要一个浮点数的加法呢?

固定类型

那使用interface{}不也可以吗?

固定类型

试看:

// 准确的描述出了参数和返回值的类型,非常方便
func Add(x, y int) int

// 但也限制了Add函数的参数类型--只能接收`int`
// Add(0.1, 0.2) // can't do that

// 那怎么办呢?再写一个针对float64的呗
func AddFloat64(x, y float64) float64

AddFloat64(0.1, 0.2) // it's ok

// 如果还要支持其它类型呢?再加一个吗,每多一种类型,就多加一个。。。
func AddInt8(x, y int8) int8
func AddInt32(x, y int32) int32
func AddFloat32(x, y float32) float32
// more...

// emm.

// how about interface{}?
func AddAny(x, y interface{}) interface{} {
    switch x.(type) {
        case int:
        case int8:
        case int32:
        case float32:
        case float64:
        // more...
        default:
        panic("not support type")
    }
}
// interface{}表示可以接收任意类型的值,并且返回任意类型的值
// 换言之,参数的类型和返回值的类型没有必然联系--从签名看来,它们可以一样,也可以不一样
// 所以,使用interface{}不够安全。

func AddGeneric1[T any](x, y T) T // 看起来跟AddAny差不多,但是参数类型和返回值类型必然是相同的

// 但any并不一定支持+运算符,所以需要用更细粒度的约束
type Number interface {
    ~int|~int8|~int32|~float32|~float64
}
func AddGeneric2[T Number](x, y T) T // 通过Number约束,确保类型参数可加

// 泛型的存在,使得函数的类型集比any小,比int大;使得返回值和参数的类型能够动态联系。

func Map[T, E any](list []T, f func(T) E) []E {
    r := make([]E, len(list))
    for i := range list {
        r[i] = f(list[i])
    }
    return r
}

类型参数

...

Go enum

May 12, 2022
Go
Enum

Go enum #

Go是没有内置枚举类型的,那么,当需要使用枚举时,该怎么办呢?

枚举说白了,就是一连串互斥的值,每个值代表一样事物或一个类型。

比如,现在需要一个颜色枚举,可以这样定义:

const (
    Red = "Red" // 红色
    Blue = "Blue" // 蓝色
    Green = "Green" // 绿色
)

也有这样定义的:

type Color string // 定义一个特定类型

// 枚举常量均声明为该类型
const (
    Red     Color = "Red" // 红色
    Blue    Color = "Blue" // 蓝色
    Green   Color = "Green" // 绿色
)

这样做的好处是可以通过这个类型来更明显的标记出枚举字段来:

type Car struct {
    Name string
    Color Color // 颜色字段声明为Color类型,在阅读代码的时候就能知道这个字段正常的可选值范围
}

但是,上面的做法都需要面临一个问题,就是我需要一个返回全部枚举值的集合时,需要这样做:

func All() []Color {
    return []Color{
        Red,
        Blue,
        Green,
    }
}

func (color Color) Name() string {
    switch color {
    case Red:
        return "红色"
    case Blue:
        return "蓝色"
    case Green:
        return "绿色"
    }
    return ""
}

当在定义处新增值时,AllName也要同步添加,对于开发人员来说,非常容易遗漏。

...

Go1.18 comparable

April 22, 2022
Go
Comparable

Go 1.18 预定义接口类型 #

先看一个提案: proposal: spec: permit values to have type “comparable” – 允许值拥有comparable类型,我的理解是,现在的comparable只能用作泛型里的类型参数的约束,不能像普通类型那样使用,如下:

type Set[E comparable] []E // 可以用做类型参数的约束

// 使用go1.18编译,报错:interface is (or embeds) comparable
var A comparable // 变量不可以使用`comparable`类型

那么,结合例子就能更好地理解这个提案了。

这个提案的主要目的就是让例子里的var A comparable成立,也就是允许comparable作为变量的类型,跟其它普通的接口类型(var E error)一样。

// proposal: spec: permit values to have type "comparable"

// As part of adding generics, Go 1.18 introduces a new predeclared interface type comparable. That interface type is implemented by any non-interface type that is comparable, and by any interface type that is or embeds comparable. Comparable non-interface types are numeric, string, boolean, pointer, and channel types, structs all of whose field types are comparable, and arrays whose element types are comparable. Slice, map, and function types are not comparable.
// -- 作为泛型的一部分,Go1.18引入了一个新的预定义接口类型:`comparable`。这个接口类型由任何可比较的非接口类型实现,和任何是`comparable`或内嵌了`comparable`的接口类型实现。可比较的非接口类型有:数值、字符串、布尔、指针、字段类型均是可比较的管道或结构体类型、元素是可比较的数组类型。切片、映射、函数类型均是不可比较的。

// In Go, interface types are comparable in the sense that they can be compared with the == and != operators. However, interface types do not in general implement the predeclared interface type comparable. An interface type only implements comparable if it is or embeds comparable.
// 在Go里面,接口类型是可比较的意味着它们可以用`==`和`!=`操作符进行比较。但是,接口类型一般来说没有实现预定义接口类型`comparable`。一个接口类型只有它是或内嵌了`comparable`时才实现了`comparable`。

// Developing this distinction between the predeclared type comparable and the general language notion of comparable has been confusing; see #50646. The distinction makes it hard to write certain kinds of generic code; see #51257.
// 出现的两个问题:[怎么在文档里说明哪些接口实现它了呢?](https://github.com/golang/go/issues/50646), [any是任意类型的意思,那必然比comparable大吧](https://github.com/golang/go/issues/51257)
// 突然想到:如果我要把一个变量表示为不可比较的,怎么样可以用`comparable`来表示呢,`!comparable`?

// For a specific example, you can today write a generic Set type of some specific (comparable) element type and write functions that work on sets of any element type:
// 
// type Set[E comparable] map[E]bool
// func Union[E comparable](s1, s2 Set[E]) Set[E] { ... }
// 
// But there is no way today to instantiate this Set type to create a general set that works for any (comparable) value. That is, you can't write Set[any], because any does not satisfy the constraint comparable. You can get a very similar effect by writing map[any]bool, but then all the functions like Union have to be written anew for this new version.

// We can reduce this kind of problem by permitting comparable to be an ordinary type. It then becomes possible to write Set[comparable].

// As an ordinary type, comparable would be an interface type that is implemented by any comparable type.
// 作为一个普通类型,`comparable`是一个可以被任意`comparable`类型实现的接口类型。

// Any comparable non-interface type could be assigned to a variable of type comparable.
// -- 任何可比较的非接口类型可以被分配到类型为`comparable`的变量。
// A value of an interface type that is or embeds comparable could be assigned to a variable of type comparable.
// -- 接口类型是或内嵌了`comparable`的值可以被分配到类型为`comparable`的变量。
// A type assertion to comparable, as in x.(comparable), would succeed if the dynamic type of x is a comparable type.
// 类型断言,如`x.(comparable)`,当x的动态类型是一个`comparable`类型时可以成功。
// Similarly for a type switch case comparable.
// 对`type switch`来说类似。
type C interface {
    comparable
}

var c C

func main() {
    var A comparable

    var a int

    if v, ok := a.(comparable); ok {

    }

    switch a.(type) {
    case comparable:

    }
}

反射的Comparable #

func ReflectComparable(v interface{}) bool {
	typ := reflect.TypeOf(v)

	// Comparable reports whether values of this type are comparable.
	// Even if Comparable returns true, the comparison may still panic.
	// For example, values of interface type are comparable,
	// but the comparison will panic if their dynamic type is not comparable.
	// -- 即使返回true,也有可能panic。
	// 比如:接口类型的值是可比较的,但如果它们的动态类型是不可比较的,就会panic
	return typ.Comparable()
}

go/types的Comparable #

func TypesComparable() bool {
	t := types.NewChan(types.SendOnly, &types.Basic{})

	return types.Comparable(t)
}

更新 #

使comparable仅在类型集里没有任何一个不可比较类型时正确,否则依然在编译时可通过,但运行时panic

...