Golang 开发基础技术分享

Summary: Author: 张亚飞 | Read Time: 3 minute read | Published: 2021-07-17
Filed under Categories: GolangTags: Note,

Golang 语言开发基础

Go(又称 Golang)是 Google 开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。Go 被誉为是未来的服务器端编程语言。

具有以下特点:

  1. 部署简单
  2. 并发性好
  3. 执行性能好

本次分享定位为Golang开发基础入门技能

Golang 常用命令及包管理工具

  • 运行
go run main.go
  • 测试
go test main_test.go
  • 构建
go build main.go

包管理工具 go mod

go mod 是 Golang官方的包管理工具,其它的还有 glide

go.mod

module go_test

go 1.16

require (
	github.com/cenkalti/backoff/v4 v4.1.0 // indirect
	github.com/dgrijalva/jwt-go v3.2.0+incompatible
	github.com/fatih/color v1.10.0 // indirect
	github.com/gin-contrib/gzip v0.0.3 // indirect
	github.com/gin-gonic/gin v1.7.2
	github.com/rdegges/go-ipify v0.0.0-20150526035502-2d94a6a86c40
	github.com/zyfmix/go_tools v0.0.0-20210716055526-7e7023f46e52
	go.uber.org/zap v1.18.1
	gorm.io/gorm v1.20.12
)

replace gorm.io/gorm => ../../../Web/Work/gorm

一些指令的特殊含义

  • require
  • replace
  • retract
  • indirect

常用命令

go mod tidy
go get -u ./...
go get package@vn
go list ./...
go list -m all

Golang 特殊方法 init

不可以被调用 先于 main 函数执行

执行顺序为: 变量初始化->init()->main()

package main

import (
	"fmt"
)

var T int64 = ic()

func init() {
	fmt.Println("init in main.go")
}

func ic() int64 {
	fmt.Println("calling a()")
	return 2
}

func main() {
	fmt.Println("calling main")

	//undefined: init
	//init()
}

main.go

Golang 基础数据类型

  • int8
  • float32
  • array
  • slice
  • map
  • channel
  • func
  • string
  • array
  • slice
  • rune …

数组、切片和哈希占用的内存空间都是连续的,编译器会在编译阶段对代码进行优化

循环控制

for: 经典的三段式循环语法

package main

import "fmt"

func main() {
	for i := 0; i < 5; i++ {
		fmt.Printf("id:%d", i)
	}
}

range: 是一个语法糖,最终都会在编译阶段优化为三段式循环,可以循环迭代 array,slice,map,channel 等集合类型

使用 range 循环动态 slice

package main

import "fmt"

func main() {
	arr := []int{1, 2, 3}
	for _, v := range arr {
		arr = append(arr, v)
	}
	fmt.Println(arr)
}

Golang 功能特性

不支持泛型,表达能力弱,一个方法不同参数要写几套,太过冗余

package tools

func ContainInt(intSlice []int, searchInt int) bool {
	for _, value := range intSlice {
		if value == searchInt {
			return true
		}
	}
	return false
}
package tools

func ContainInt64(intSlice []int64, searchInt int64) bool {
	for _, value := range intSlice {
		if value == searchInt {
			return true
		}
	}
	return false
}

单元测试用法

使用 *_test.go 结尾,单元测试写法

func TestXxx(*testing.T)

使用 ErrorFail 或相关方法来发出失败信号

运行单元测试

go test -v fibonacci_test.go
go test -v -short fibonacci_test.go

goroutine协程

进程和线程都是由系统cpu进行调度.协程是用户态的轻量级线程,由用户自己调度控制,协程默认占用内存小(2kb),而线程(8mb),协程切换开销远比线程小. 使用 go 关键字就可以开一个协程

package main

import "fmt"

func main() {
	go func() {
		fmt.Println("----------")
	}()
}

channel通道

协程消息通讯方式,阻塞式的

ch <- v // 发送值v到Channel ch中
v := <-ch // 从Channel ch中接收数据,并将数据赋值给v

示例:

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int, 0)
	go func(ch chan int) {
		for {
			select {
			case i := <-ch:
				fmt.Println("receive ", i)
				time.Sleep(time.Second)
			case <-time.After(1000 * time.Millisecond):
				fmt.Println("receive timeout", time.Now().Unix())
			}
		}
	}(ch)

	ch <- 1
	time.Sleep(5 * time.Second)
}

使用 _,ok 判断 channel 是否关闭 可以增加一个返回值判断通道是否关闭或是否为空值

x, ok := <-ch

使用 range 从单个 channel 读取数据

package main

import "fmt"

func main() {
	fc := make(chan int, 10)
	fc <- 1
	for i := range fc {
		fmt.Println(i)
	}
}

使用for-range读取channel,这样既安全又便利,当channel关闭时,for循环会自动退出

使用 select 从多个 channel 读取数据

package main

import (
	"context"
	"time"
	"fmt"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)

	foo := make(chan int, 10)
	go func() {
		select {
		case <-ctx.Done():
			fmt.Printf("select -- ctx.done,at: %d", time.Now().Unix())
		case fv, ok := <-foo:
			fmt.Printf("select -- fv:%d,ok: %v,at: %d", fv, ok, time.Now().Unix())
		case <-time.After(time.Second):
			fmt.Printf("select -- timeout,at: %d\n", time.Now().Unix())
		}
	}()

	time.Sleep(3 * time.Second)
	cancel()
}

注意:使用 select 从多个 channel 读取信息时,没有读取优先级,如果通道都有消息读取的顺序是随机的

实现一个非阻塞读的方法 并发控制或限流: 实现一个简易的令牌桶

main.go

reflect反射

通过反射,可以拿到运行时对象的信息,包括类型、值、方法等信息

package main

import (
	"fmt"
	"reflect"
)

func main() {
	caches := make(map[string]interface{}, 0)
	caches["data_ids"] = 1.3

	cacheTypes := reflect.TypeOf(caches["data_ids"])
	cacheVal := reflect.ValueOf(caches["data_ids"])

	fmt.Printf("cacheTypes kind is %+v\n", cacheTypes.Kind())
	fmt.Printf("cacheVal type is: %+v\n", cacheVal.Type())
	fmt.Printf("cacheVal kind is: %v\n", cacheVal.Kind() == reflect.Float64)
	fmt.Printf("cacheVal value is: %f\n", cacheVal.Float())
	fmt.Printf("cacheVal value is: %s\n", cacheVal.String())
}

panic异常恢复

panic 可以中断程序的运行,但是可以通过 recover() 异常恢复,相当于 try,catch…

package main

import (
	"fmt"
	"strconv"
)

func main() {
	cache, err := Parse("r3")
	if err != nil {
		fmt.Printf("err:%#v\n", err)
	}
	fmt.Println(fmt.Printf("cache:%v", cache))
}

type Cache struct {
	id uint64
}

func Parse(input string) (s Cache, err error) {
	defer func() {
		if p := recover(); p != nil {
			err = fmt.Errorf("internal error: %v", p)
		}
	}()

	id, err := strconv.ParseUint(input, 10, 64)
	if err != nil {
		panic(err)
	}

	return Cache{
		id: id,
	}, nil
}

Json 库

  • Json 序列化非标准格式,如何在解析 Json 时让 int 字段类型兼容 float 数据格式,而序列化后统一为标准 int 格式
type MUIntType struct {
	val uint
}

func (m *MUIntType) UnmarshalJSON(value []byte) error {
	var raw interface{}
	err := json.Unmarshal(value, &raw)
	if err != nil {
		fmt.Printf("[err]err:%s\n", err)
		return err
	}
	
	switch raw.(type) {
	case string:
		fmt.Printf("[#][string]:%s\n", raw)
		return nil
	case float64:
		fmt.Printf("[#][float64]:%s\n", raw)
		return nil
	default:
		fmt.Printf("[#][UnknownType]:%T\n", raw)
		break
	}
	
	return json.Unmarshal(value, &m.val)
}

func (m MUIntType) MarshalJSON() ([]byte, error) {
	return json.Marshal(m.val)
}

tests/data/data_test.go

  • 使用 Json 处理解析时间及空值处理
package main

import (
	"time"
	"fmt"
)

var TimeLayout = "2006-01-02 15:04:05"

type JsonTime time.Time

func (jt JsonTime) MarshalJSON() ([]byte, error) {
	var stamp = fmt.Sprintf("\"%s\"", time.Time(jt).Format(TimeLayout))
	return []byte(stamp), nil
}

tests/data/data_test.go

Golang 遇到的坑

  • for-range循环指针重复问题
package main

import "fmt"

func main() {
	slices := []int{1, 2, 3, 4, 5, 6}
	pointers := make([]*int, 0)

	fmt.Println()
	for _, sv := range slices {
		fmt.Print(sv, ",")
		pointers = append(pointers, &sv)
	}

	fmt.Println()
	for _, pv := range pointers {
		fmt.Print(*pv, ",")
	}
	fmt.Println()
}

tests/range/range_test.go

解析Json时使用 json.RawMessage 定义原始类型

循环引用问题

good => order; order => good import cycle not allowed

runs/cycle/main.go

模块化设计,让开发者考虑不同模块的功能划分与解耦 禁止循环引入会让编译变的更高效

  • 解决方案

尽量不要划分很多独立相互依赖的包,扁平化

使用独立的 interface 声明

功能结构划分清晰:

route,controllers,handlers,middlewares,libs

比较好的学习资料

其它

runtime运行时

context上下文

内存泄漏问题

内存逃逸分析

Golang 其它服务

GRPC

CGO编程

interface接口

实现 fmt.Stringer 接口的类型都可以自动自动调用该方法

package fmt

type Stringer interface {
	String() string
}

Comments

Cor-Ethan, the beverage → www.iirii.com