Transformation
- Reasons for wanting to transition to Go engineering system in a short time
- Improve CRUD operations, no experience with self-developed frameworks
- Students who want to deepen their technical expertise, specializing and refining requirements
- Advance in engineering, possessing good development standards and management capabilities
The Importance of Engineering
Expectations for Senior Development
- Good code standards
- Deep understanding of underlying principles
- Familiarity with architecture
- Familiarity with k8s basic architecture
Expand knowledge breadth, knowledge depth, and standardized development system
Four major stages
- Go language fundamentals
- Microservice development (e-commerce project practical experience)
- Self-developed microservices
- Self-developed and then refactored
Domains
- Web Development - gin, beego, etc.
- Container Virtualization - docker, k8s, istio
- Middleware - etcd, tidb, influxdb, nsq, etc.
- Blockchain - Ethereum, Fabric
- 1xAR5 - go-zero, dapr, rpcx, kratos. dubbo-go,
Development of Related Environments
- Go installation official website
- goland, vscdoe
# go build hello.go
# ./hello
go run hello.go
In new versions of golang, when there are multiple main runtimes under the same package, select the run type file. Go does not recommend having two main files in the same directory; if multiple main files are needed, they should be placed in different folders.
Variables
package main
import "fmt"
// 全局变量 定义可以不使用
var name = "bobby"
var age = 19
var ok bool
func main() {
// go是静态语言 静态语言和动态语言相比变量差异很大
// 1. 变更定义 先定义后使用 类型声明了不能改变
// 定义变量
var name string = "hello world"
age := 1
// go 语言中变量定义的不使用是不行的,强制性的
fmt.Println(name, age)
// 多变量定义
var user1, user2, user3 = "booby1", "bobby2", 22
fmt.Println(user1, user2, user3)
// 注意变量先定义才能使用
// 是静态语言类型和赋值要一致
// 要求和规范性会更好
// 变量名不能冲突 同一个代码块中不能同名的
// 简洁定义 名:=值 不能用于全局定义
// 变量是有0值的
}
Variables
package main
import "fmt"
func main() {
// 常量 定义的时候就要指定值,不能修改
const PI float32 = 3.14159265358979323846 // 显式定义
const PI2 = 3.14159265358979323846 // 常量 大写多单词 用下划线
const (
UNKNOW = 1
FEMALE = 2
MALE = 3
)
//没有设置类型和值它会沿用前面的值
const (
x int = 16
y
s = "abc"
z
)
fmt.Println(x, y, z)
/**
常量类型只要以是bool 数值(整数、浮点)
不曾使用的常量,没有强制使用的
显示指定类型的时候,必须确保左右类型一致
*/
}
iota
Special constants
package main
import "fmt"
func main() {
// 匿名变量 定义一个变更不使用它
var _ int // 接收返回值进,点位符只接收哪个值
// iota 特殊常量,可以认为是一种可以被编译器修改的常量
const (
ERR1 = iota + 1
ERR2
ERR25 = "ha" // iota内部仍然增加计数
ERR3
ERR4 = iota
)
const (
ERRORNEW1 = iota // 从0开始计数
)
/*
如果中断了iota那么要显示的恢复,后续会自动递增 自增类型默认是int类型的
iota能简化const类型的定义
*/
fmt.Println(ERR1, ERR2, ERR3, ERR4)
}
Variable Scope
Personally, I think it's important to pay attention to the scope of code blocks and also the declaration method like variableName := value
Go's Basic Data Types
- Basic data types
- bool true false
- Numeric types
- Integers int8~64 uint8~64 (unsigned numbers)
- Floating-point numbers float32 float64
- Complex numbers
- byte (byte) uint8
- rune type int32
- Characters and string
package main
import "fmt"
func main() {
var a int8
var b int16
var c int32
var d int64
var ua uint8
var ub uint16
var uc uint32
var ch3 byte
c = 'a' // 字符类型
var int_32 rune // 也是字符
int_32 = '中' // 即有中文也有英文
fmt.Println(int_32)
fmt.Print("c=%c", ch3)
var str string
str = "i am bobby"
fmt.Println(str)
}
Conversions between various types
var str string
str = "i am bobby"
fmt.Println(str)
// 字符串转数字
var istr = "12"
myint, err := strconv.Atoi(istr)
if err != nil {
fmt.Println("convert error")
}
fmt.Println(myint)
var myi = 32
mstr := strconv.Itoa(myi)
fmt.Println(mstr)
// 字符串转float32 转换bool
mFloat, error := strconv.ParseFloat("3.1415", 64)
if error != nil {
fmt.Println("convert error")
}
fmt.Println(mFloat)
parseBol, err := strconv.ParseBool("true")
if err != nil {
fmt.Println("convert error")
}
fmt.Println(parseBol)
// 基本类型转字符串
boolStr := strconv.FormatBool(true)
fmt.Println(boolStr)
floatStr := strconv.FormatFloat(3.1415, 'E', -1, 64)
fmt.Println(floatStr)
Operators
What collection types does Go language provide?
package main
import "fmt"
func main() {
// 数组 slice map list
// 数组 var name [count]int
//var course1 [3]string // course1是类型 只有3个元素的数组类型
//var course2 [4]string
//course1[0] = "og"
//course1[1] = "grpc"
//course1[2] = "gin"
////[]strig 和 [3]string 这是两种不同的类型
//fmt.Println("%T \r\n", course1)
//fmt.Println("%T \r\n", course2)
//
//for _, value := range course1 {
// fmt.Printf("value=%s\n", value)
//}
// 初始化
course1 := [3]string{"go", "grpc", "gin"}
//course1 := [3]string{2:"gin"}
// course3:=[...]string{"go","grpc","gin"}
for _, value := range course1 {
fmt.Printf("value=%s\n", value)
}
// 数组元素长度及内容一样,可以直接用等于判断
// 多维数组
var courseInfo [3][4]string
courseInfo[0] = [4]string{"go", "1h", "bobby", "go体系课"}
courseInfo[1] = [4]string{"grpc", "2h", "bobby1", "grpc入门"}
courseInfo[2] = [4]string{"gin", "2h", "bobby2", "gin高级开发"}
for i := 0; i < len(courseInfo); i++ {
for j := 0; j < len(courseInfo[i]); j++ {
fmt.Print(courseInfo[i][j] + "")
}
fmt.Println()
}
}
Slices
package main
import "fmt"
func main() {
// 理解为动态的array 弱化数组的概念 切片的本质存储和数组是有区别
var courses []string
fmt.Printf("%T \r\n", courses)
// 这个方法很特别 添加元素
courses = append(courses, "go")
courses = append(courses, "grpc")
courses = append(courses, "gin")
fmt.Println(courses[1])
// 初始化 3种 1:从数组直接创建 2:使用string{} 3:make
allCourses := [5]string{"go", "grpc", "gin", "mysql", "elasticsearch"}
courseSlice := allCourses[0:2] // 左闭右开的区间 python的语法
fmt.Println(courseSlice)
courseSlice1 := []string{"go", "grpc", "gin", "mysql", "elasticsearch"}
fmt.Println(courseSlice1)
courseSlice2 := make([]string, 3)
courseSlice2[0] = "C"
fmt.Println(courseSlice2)
// 如何访问切片的元素 访问单个(类似数组,不能超长度) 访问多个allCourses[start:end] 前闭后开,如果只有start(到结束)
// 如果没有start有end (从end之前的元素)
// 没有【:] 相当于复制了一份
cSlice1 := []string{"go", "grpc"}
cSlice2 := []string{"mysql", "es", "gin"}
c12 := append(cSlice1, cSlice2[1:]...)
fmt.Println(c12)
}
package main
import (
"fmt"
"strconv"
"unsafe"
)
func printSlice(data []string) {
data[0] = "java"
for i := 0; i < 10; i++ {
data = append(data, strconv.Itoa(i))
}
}
// 定义slice会通过结构体去定义
type slice struct {
array unsafe.Pointer
len int
cap int
}
func main() {
//go的slice在函数 参数传递的时候是值传递还是引用传递,值传递,效果又呈现出引用的效果(不完全是)
course := []string{"go", "grpc", "gin"}
printSlice(course)
fmt.Println(course)
}
deferhas the ability to modify return valueserror,panic,recoverare Go language's error handling concepts. A function might err, and the developer of the function needs a return value to tell the caller whether it succeeded, requiring us to handle this error.
Go designers believe this error must be handled, defensive programming.
func A() (int,error){
panic("this is an panic") // panic会导致程序的退出,平时开发中不要随便使用,一般我人在哪里用到, 我们一个服务启动的过程中
return 0,errors.New("this is an error")
}
Structs
package main
type Person struct {
name string
age int
address string
height float32
}
type Person1 struct {
name string
age int
}
type Student struct {
//// 第一种嵌套方式
//p Person1
//第二种方式 访问的方式可以直接.name .age 但初始化时不能 类似覆盖的方式
Person1
score float32
}
func main() {
// 如何初始化结构体
p1 := Person{"bobby1", 18, "七星高照", 1.80}
p2 := Person{name: "bobby2", height: 1.90}
var persons []Person
persons = append(persons, p1)
persons = append(persons, p2)
persons = append(persons, Person{
name: "bobby3",
})
// 匿名结构体
address := struct {
province string
city string
address string
}{
province: "北京市",
city: "通州区",
address: "万寿路",
}
// 结构体的嵌套
s := Student{
p: Person1{
name: "bob",
age: 18,
},
score: 95.6,
}
s.p.name = "zzz"
}
// 结构体绑定方法
// func(s StructType)funcName(param1 paramType,....)(returnTypes1....){.....}
Pointers
// 第一种初始化方式
ps := &Person{}
// 第二种初始化方式
var emptyPerson Person
pi := &emptyPerson
// 第三种初始化方式
var pp = new(Person)
fmt.Println(pp.name)
// 初始化两个关键字,map、channel、slice 初始化推荐使用 make 方法
// 指针初始化推荐使用 new 函数,指针要初始化否则会出现 nil pointer
// map 必须初始化
Pointer passing to swap two values
package main
import "fmt"
// 通过指针交换两个值
func swap(a, b *int) {
*a, *b = *b, *a
}
func main() {
x, y := 10, 20
fmt.Printf("交换前: x = %d, y = %d\n", x, y)
// 调用 swap 函数交换值
swap(&x, &y)
fmt.Printf("交换后: x = %d, y = %d\n", x, y)
}
nil in Go Language
/*
不同类型的数据零值不一样
bool false
numbers 0
string ""
pointer nil
slice nil
map nil
channel、interface、function nil
struct 默认值不是 nil,默认值是具体字段的默认值
*/
package main
import "fmt"
type Person struct {
name string
age int
}
func main() {
// struct每一个值都等于才相当
p1 := Person{
name: "bobby",
age: 18,
}
p2 := Person{
name: "bobby",
age: 18,
}
if p1 == p2 {
fmt.Println("yes")
}
}
Go Duck
// Go语言的接口,鸭子类型,php,python
// Go语言中处处都是interface,到处都是鸭子类型 duck typing
/*
当看到一只鸟走起来像鸭子,游泳起来像鸭子,叫起来也像鸭子,那么这只鸟就是鸭子
动词,方法,具备某些方法
*/
// 主要是声明方法的定义
type Duck interface {
// 方法的申请
Gaga()
Walk()
Swimming()
}
type pskDuck struct {
legs int
}
func (pd *pskDuck) Gaga(){
fmt.Println("嘎嘎")
}
func (pd *pskDuck) Walk(){
fmt.Println("嘎嘎")
}
func (pd *pskDuck) Swimming (){
fmt.Println("嘎嘎")
}
Go Language Package Organization
Different package names are not allowed in the same folder. Imports use paths, and when used, it's `packageName + className`, or alias referencing; `.` imports everything into the current package for use. During initialization, `func init(){}` will be used. `go.mod` is automatically maintained. `gin-gonic/gin` new versions all use Go modules `go list -m all`
go list -m --version 包名 可以查看版本
go get 指定包名
go mod tidy # go mod help 来查看
# go install 安装
# go get -u 升级到Latest的将要版本或修定版本
# # go get 会修改go.mod文件的
# go get github.com/go-redis/redis/v8@version
Code Standards
1. Code Standards
Naming
- Use camelCase, for example:
myVariable. - Package names should be lowercase words, without underscores or mixed case.
- Interface names should end with
-er, for example:Reader,Writer.
Formatting
- Use the
gofmttool to unify code format. - Maintain consistent code style, such as indentation, spaces, and line breaks.
Comments
- Use line comments
//to add explanations for functions, methods, and complex logic. - Package-level comments should be placed before the package declaration.
2. Structs and Interfaces
Structs
- Try to use lowercase field names unless they need to be exported.
- Use constructor functions to initialize structs.
Interfaces
- Define small and simple interfaces.
- Use interfaces to meet needs, rather than defining large and comprehensive interfaces.
3. Error Handling
Error Checking
- Always check for errors returned by functions.
- Use
errors.Neworfmt.Errorfto create error messages.
Error Handling
- Errors should be handled as early as possible to avoid delayed checking.
- When an error is unrecoverable, use
log.Fatalto log and exit. - Try to provide context information to aid debugging.
Why Code Standards?
- Code standards are not mandatory, but some subtle standards for different languages should still be followed.
- Code standards are mainly to facilitate the formation of a unified code style within the team, improving code readability and consistency.
1. Code Standards
1.1 Naming Conventions
Package Names
- Try to keep consistent with the directory.
- Try to use meaningful and short package names.
- Do not conflict with standard library names.
- Package names should be all lowercase.
File Names
- Use
user_name.go; if there are multiple words, snake_case can be used.
Variable Names
- Snake_case: suitable for Python, PHP.
- CamelCase: suitable for Java, C, Go.
userNameUserName-
un string //unad userNameAndDesc -
Some specialized naming, such as
URLVersion. - For
booltypes, use prefixes likeHas,is,can,allow.
1.2 Struct Naming
- Use camelCase, for example
User.
1.3 Interface Naming
- Interface naming is basically similar to struct naming.
Unit Testing
Multithreading
// python, java, php 多线程编程、多进程编程,多线程和多进程存在的问题主要是耗费内存
// 内存、线程切换、web2.0,用户级线程,绿程,轻量级线程,协程,asyncio-python php-swoole java - netty
// 内存占用小(2k)、切换快,go语言的协程,go语言诞生之后就只有协程可用 goroutine
// 主协程退出,在其中启动的协程就会死掉
package main
import (
"fmt"
"time"
)
func asyncPrint() {
fmt.Println("bobby")
}
func main() {
// 主死随从
//go asyncPrint()
// 1. 闭包 2. for循环的问题
for i := 0; i < 100; i++ {
go func(i int) {
for {
time.Sleep(time.Second)
fmt.Println(i)
}
}(i)
}
fmt.Println("main goroutine")
time.Sleep(2 * time.Second)
}
Understanding GMP
wg (wait group)
Although we can use the main thread to `sleep` to ensure the main goroutine doesn't die, the main goroutine doesn't necessarily know how long to sleep. How do child goroutines notify the main goroutine that they have finished? How does the main goroutine know that the children have finished?
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
// 我要监控多少个goroutine执行结束
wg.Add(100)
for i := 0; i < 100; i++ {
go func(i int) {
defer wg.Done()
fmt.Println(i)
//wg.Done() // 调用了要调用一次done
}(i)
}
// 等到100都执行完
wg.Wait()
fmt.Println("all done")
// waitgroup主要用于goroutine的执行等待 add方法要和done要和Done方法配套
}
How goroutines use locks
package main
import (
"fmt"
"sync"
)
//锁 资源竞争
var total int
var wg sync.WaitGroup
var lock sync.Mutex // 只要是同一把锁就没有问题
func add() {
defer wg.Done()
for i := 0; i < 100000; i++ {
lock.Lock()
total += 1
lock.Unlock()
}
}
func sub() {
defer wg.Done()
for i := 0; i < 100000; i++ {
lock.Lock()
total -= 1
lock.Unlock()
}
}
func main() {
wg.Add(2)
//两个同时运行时,因为不是原子操作它会产生资源竞争,导致结果不一致
go add()
go sub()
wg.Wait()
fmt.Println("done i=", total)
}
// 如果权权是加一或者减一操作
// 可以使用atomic.AddInXX
Read-Write Locks
Above, we have learned about mutual exclusion (essentially serializing parallel content). Using locks will definitely affect performance. Even when designing locks, parallelism should be ensured as much as possible. We have two groups of goroutines, one for writing and one for reading. In most web systems, there are many more reads than writes, such as detail pages. Although there are multiple goroutines, a careful analysis reveals that read goroutines should be concurrent with each other, while reads and writes should be serial.
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var num int
var rwLocak sync.RWMutex
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
// 负责写数据
rwLocak.Lock() // 加写锁 写锁会防止 别的写锁获取和读锁获取
defer rwLocak.Unlock()
num = 12
}()
// 因为不能保证 写先执行(因为还没讲到goroutine之间的通信)我们先sleep一下吧
time.Sleep(time.Second)
// 读取的goroutine
go func() {
defer wg.Done()
rwLocak.RLock() // 加读锁 ,读锁不会阻止别人的读
defer rwLocak.RUnlock()
fmt.Println(num)
}()
wg.Wait()
}
Communication between goroutines
package main
import "fmt"
func main() {
/*
不要通过共享内存来通信,而要通过通信来实现内存共享
php python java 多线程编程的时候,丙从此goroutine之间通信最常用的方式是一个全局可以提供消息的机制
python-queue java生产者消费者
channel 在加上语法糖让使用channel更加简单
*/
var msg chan string //可以理解是一个通道 还要定义通道的类型
// 它底层是数组来完成,所以要初始一下
msg = make(chan string, 1) // channel的初始化值为0时,你放的值会阻塞 deadlock
// msg = make(chan string, 0) // 无缓冲
msg <- "bobby" // 放值 右边的值放到这个channel中
data := <-msg // 拿值
fmt.Println(data)
}
Buffered and Unbuffered Channels
- Unbuffered: Suitable for notifications where B needs to know immediately if A has completed.
- Buffered: Suitable for communication between producers.
// 消息传递,消息过滤
// 信号广播
// 事件订阅和广播
// 任务分发
// 结果汇总
// 并发控制
// 同步和异步
package main
import (
"fmt"
"time"
)
func main() {
var msg chan int
msg = make(chan int, 2)
go func(msg chan int) {
for data := range msg {
fmt.Println(data)
}
fmt.Println("all done")
}(msg)
msg <- 1 // 放值channel中
msg <- 2
close(msg) // 关闭这个通道
d := <-msg
fmt.Println(d) // 已经关闭的channel可以继续取值,但不能再放值了
msg <- 3 // 已经关闭的channel不能再放值了
time.Sleep(time.Second * 10)
}
Unidirectional Channels
package main
import (
"fmt"
"time"
)
func producer(out chan<- int) {
for i := 0; i < 10; i++ {
out <- i * i
}
close(out)
}
func consumer(in <-chan int) {
for num := range in {
fmt.Printf("num=%d\r\n", num)
}
}
func main() {
// 默认情况下,channel是双向的
// 但是我们经常一个channel做为一个参数时,不希望往里写数据
// 单向channel
//var ch1 chan int // 双向的
//var ch2 chan<- float64 // 单向的只能写入float64
//var ch3 <-chan int // 单向的,只能读取数据
//c := make(chan int, 3)
//var send chan<- int = c // send-only
//var read <-chan int = c // rec-oney
//send <- 1
//<-send //这样就不行了,只能写不能读
//<-read
// 不能将单向的转成普通的channel
c := make(chan int)
go producer(c)
go consumer(c)
time.Sleep(time.Second * 10)
}
A Common Interview Question
package main
import (
"fmt"
"time"
)
var number, latter = make(chan bool), make(chan bool)
func printNum() {
i := 1
for {
// 我怎么去做到,应该此片,等待另一个goroutine来通知我
<-number
fmt.Printf("%d%d", i, i+1)
i += 2
latter <- true
}
}
func printLetter() {
i := 0
str := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
for {
// 我怎么去做到,应该此片,等待另一个goroutine来通知我
<-latter
if i >= len(str) {
return
}
fmt.Print(str[i : i+2])
i += 2
number <- true
}
}
func main() {
/*
使用两个goroutine交替打印序列,一个goroutine打印数字,另外一个goroutine打印字母,最终效果如下:
12AB34CD56EF78GH910IJ1112KL1314MN1516OP1718QR1920ST2122UV2324WX2526YZ2728
*/
go printNum()
go printLetter()
number <- true
time.Sleep(time.Second * 100)
}
Monitoring
package main
import (
"fmt"
"sync"
"time"
)
var done bool
var lock sync.Mutex
func go1() {
time.Sleep(time.Second)
lock.Lock()
defer lock.Unlock()
done = true
}
func go2() {
time.Sleep(time.Second * 2)
lock.Lock()
defer lock.Unlock()
done = true
}
func main() {
// 类似于switch 但select的功能和我们操作系统linux里面提供的io的select poll epoll类似
// select 主要作用于多个channel
// 现在有需求,我们现在有两个goroutine都在执行,但是我们在主的goroutine中,当某一个执行完成了,这个时间我会立以知道
go go1()
go go2()
for {
time.Sleep(time.Millisecond * 10)
if done {
fmt.Println("done")
return
}
}
}
Messaging is more recommended for notification than shared variables.
context
One of the most frequently used scenarios in concurrent programming.
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
var stop bool
// 我们新的需求,我可以主动退出监控程序
// 共享变量
func cupInfo() {
defer wg.Done()
for {
if stop {
break
}
time.Sleep(2 * time.Second)
fmt.Println("CPU的信息 ")
}
}
func main() {
// 为什么使用context
// 有一个goroutine监控cpu的信息
wg.Add(1)
go cupInfo()
time.Sleep(6 * time.Second)
stop = true
wg.Wait()
fmt.Println("监控完成")
}
// 演进
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
// var stop bool
//var stop = make(chan struct{})
// 我们新的需求,我可以主动退出监控程序
// 共享变量
func cupInfo(ctx context.Context) {
defer wg.Done()
for {
select {
case <-ctx.Done():
fmt.Println("退出cpu监控")
return
default:
time.Sleep(2 * time.Second)
fmt.Println("CPU的信息 ")
}
}
}
func main() {
// 为什么使用context
// 有一个goroutine监控cpu的信息
//var stop = make(chan struct{})
wg.Add(1)
ctx, cancel := context.WithCancel(context.Background())
go cupInfo(ctx)
time.Sleep(6 * time.Second)
//stop <- struct{}{}
cancel()
wg.Wait()
fmt.Println("监控完成")
}
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
// var stop bool
//var stop = make(chan struct{})
// 我们新的需求,我可以主动退出监控程序
// 共享变量
func cupInfo(ctx context.Context) {
defer wg.Done()
for {
select {
case <-ctx.Done():
fmt.Println("退出cpu监控")
return
default:
time.Sleep(2 * time.Second)
fmt.Println("CPU的信息 ")
}
}
}
func main() {
// 为什么使用context
// 有一个goroutine监控cpu的信息
//var stop = make(chan struct{})
wg.Add(1)
//ctx, cancel := context.WithCancel(context.Background())
ctx, _ := context.WithTimeout(context.Background(), 6*time.Second)
go cupInfo(ctx)
//time.Sleep(6 * time.Second)
//stop <- struct{}{}
//cancel()
wg.Wait()
fmt.Println("监控完成")
}
主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/6733