Golang 基本使用。
一、统一思想:12 因素应用宣言 在深入学习 Go 语言之前,我们先来了解一下构建云原生应用的指导原则——12 因素应用宣言(The Twelve-Factor App)。这套方法论由 Heroku 平台的开发者总结,旨在帮助开发者构建可扩展、可维护、易部署的云原生应用。
基准代码 (Codebase) :一份基准代码,多份部署。使用版本控制系统(如 Git)管理代码,确保不同环境(开发、测试、生产)使用同一份代码的不同版本。
依赖 (Dependencies) :显式声明依赖关系。使用依赖管理工具(如 Go Modules)管理项目依赖,确保依赖的明确性和可重复性。
配置 (Config) :在环境中存储配置。将配置信息(如数据库连接、API 密钥)存储在环境变量中,而不是硬编码在代码中,提高应用的可移植性和安全性。
后端服务 (Backing Services) :把后端服务当作附加资源。将数据库、消息队列、缓存等后端服务视为可插拔的资源,通过 URL 或配置信息进行连接,方便应用的迁移和扩展。
构建,发布,运行 (Build, Release, Run) :严格分离构建和运行。将应用构建、发布和运行三个阶段分离,确保每个阶段的独立性和可重复性。
进程 (Processes) :以一个或多个无状态进程运行应用。将应用设计为无状态进程,方便水平扩展和故障恢复。
端口绑定 (Port Binding) :通过端口绑定提供服务。应用通过绑定端口对外提供服务,不依赖于特定的 Web 服务器或容器。
并发 (Concurrency) :通过进程模型进行扩展。利用进程或线程模型实现应用的并发,提高应用的吞吐量和响应速度。
易处理 (Disposability) :快速启动和优雅终止可最大化健壮性。应用应能够快速启动和优雅终止,方便部署、重启和故障恢复。
开发环境与线上环境等价 (Dev/Prod Parity) :尽可能地保持开发、预发布、线上环境相同。使用相同的操作系统、依赖和配置,减少环境差异导致的问题。
日志 (Logs) :把日志当作事件流。将应用的日志输出到标准输出(stdout),由外部系统(如日志收集器)进行处理和分析。
管理进程 (Admin Processes) :后台管理任务当作一次性进程运行。将后台管理任务(如数据库迁移、数据备份)作为一次性进程运行,与应用的主进程分离。
面试知识点:
什么是 12 因素应用宣言?它的核心原则是什么?
为什么要在环境中存储配置?这样做有什么好处?
如何理解应用的无状态性?无状态应用有什么优势?
二、Go 语言的诞生与设计哲学 1. 为什么需要 Go 语言? 在 Go 语言出现之前,开发者面临着一些挑战:
硬件发展与软件瓶颈 :硬件性能不断提升,但软件开发效率却没有同步提升。
现有语言的不足 :
C/C++ 等原生语言缺乏好的依赖管理,编译速度慢。
Java/C# 等语言过于庞大,启动速度慢,内存占用高。
现有语言对并发编程的支持不够友好,难以充分利用多核处理器。
Go 语言的出现,正是为了解决这些问题。
2. Go 语言的设计哲学 Go 语言的设计哲学可以用以下几个关键词概括:
Less is exponentially more (少即是多):Go 语言追求简洁,避免过度设计,减少不必要的复杂性。
Do Less, Enable More (做更少,成更多):Go 语言提供了一套精简但强大的工具集,让开发者能够更高效地完成工作。
面向工程 :Go 语言的设计目标是解决实际工程问题,而不是追求学术上的完美。
正交性 :Go 语言的特性之间相互独立,组合起来却能发挥强大的威力。
3. Go 语言的主要特性
编译型语言 :Go 语言是一种编译型语言,可以将代码编译成机器码,执行效率高。
静态类型 :Go 语言是一种静态类型语言,在编译时进行类型检查,可以减少运行时错误。
垃圾回收 :Go 语言内置垃圾回收机制,开发者无需手动管理内存,降低了开发难度。
并发编程 :Go 语言通过 goroutine 和 channel 提供了强大的并发编程支持,可以轻松编写高并发程序。
简洁的语法 :Go 语言的语法简洁明了,易于学习和使用。
丰富的标准库 :Go 语言提供了丰富的标准库,涵盖了网络编程、系统编程、数据处理等多个领域。
4. Go 语言不支持的特性 为了保持语言的简洁性和一致性,Go 语言有意不支持一些常见的特性:
函数重载和操作符重载 :避免代码的歧义和复杂性。
隐式类型转换 :减少潜在的错误和不确定性。
继承 :Go 语言使用组合来实现代码复用,而不是继承。
异常处理 :Go 语言使用显式的错误处理机制(error),而不是异常。
断言 :Go 语言鼓励开发者编写更健壮的代码,而不是依赖断言来捕获错误。
静态变量 :Go 语言不支持静态变量,避免全局状态带来的问题。
面试知识点:
Go 语言的设计目标是什么?它解决了哪些问题?
Go 语言有哪些主要的特性?这些特性有什么优势?
为什么 Go 语言不支持某些常见的特性(如继承、异常)?
三、Go 语言环境搭建与基础 1. 下载与安装
2. 环境变量配置
GOROOT :Go 语言的安装目录。
GOPATH :Go 语言的工作目录,用于存放项目代码、依赖包和可执行文件。
src
:存放项目源代码。
pkg
:存放编译后的包文件。
bin
:存放可执行文件。
GOOS :目标操作系统(如 linux、windows、darwin)。
GOARCH :目标处理器架构(如 amd64、arm64)。
GOPROXY :Go 模块代理,用于加速依赖包的下载。国内用户建议设置为 https://goproxy.cn
。
3. IDE 设置
推荐使用 VS Code,并安装 Go 插件。
其他可选的 IDE 包括:
Goland(JetBrains 出品,收费)
Vim、Sublime Text 等(需要配置相关插件)
4. 常用命令
go build
:编译 Go 程序。
-o
:指定输出文件名。
GOOS
和 GOARCH
环境变量可以用于交叉编译。
go run
:编译并运行 Go 程序。
go test
:运行测试。
./...
:运行当前目录及子目录下的所有测试。
-v
:显示详细的测试输出。
go vet
:静态代码检查,发现潜在的错误。
go fmt
:格式化 Go 代码。
go get
:下载并安装依赖包。
go mod
:Go 模块管理工具。
go doc
:查看文档。
go env
:查看 Go 环境变量。
5. 代码版本控制
推荐使用 Git 进行代码版本控制。
将代码托管到 GitHub、GitLab 等平台。
6. Golang Playground
面试知识点:
go build
和 go run
有什么区别?
go vet
可以检查出哪些类型的错误?
如何使用 Go Modules 管理项目依赖?
如何进行交叉编译?
四、Go 语言控制结构 1. if 语句 1 2 3 4 5 6 7 8 9 10 11 12 if condition1 { } else if condition2 { } else { }if v := x - 100 ; v < 0 { return v }
2. switch 语句 1 2 3 4 5 6 7 8 9 10 switch var1 {case val1: case val2: fallthrough case val3: f()default : }
3. for 循环 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 for i := 0 ; i < 10 ; i++ { sum += i }for sum < 1000 { sum += sum }for { if condition { break } }for index, char := range myString { }for key, value := range myMap { }for index, value := range myArray { }
面试知识点:
Go 语言中如何实现类似于 while 循环的功能?
for-range 循环遍历不同类型的数据时,有哪些需要注意的地方?
五、Go 语言常用数据结构 1. 变量与常量
常量 :使用 const
关键字定义。
变量 :使用 var
关键字定义。
1 2 const Pi = 3.14 var name string = "Go"
2. 变量定义与初始化
变量声明 :var identifier type
变量初始化 :var i, j int = 1, 2
短变量声明 :c, python, java := true, false, "no!"
(只能在函数内部使用)
3. 类型转换与推导
类型转换 :T(v)
将值 v
转换为类型 T
。
类型推导 :在声明变量时不指定类型,Go 编译器会根据右值的类型自动推导。
4. 数组
定义 :var identifier [len]type
特点 :相同类型、长度固定、连续内存。
1 myArray := [3 ]int {1 , 2 , 3 }
5. 切片 (slice)
定义 :var identifier []type
特点 :对数组的引用、动态长度、连续内存。
常用方法 :
append
:追加元素。
make
:创建切片。
切片操作:myArray[1:3]
1 2 mySlice := []int {1 , 2 , 3 } mySlice = append (mySlice, 4 )
6. make 和 new
new :返回指针地址。
make :返回第一个元素,可预设内存空间。
1 2 3 mySlice1 := new ([]int ) mySlice2 := make ([]int , 0 ) mySlice3 := make ([]int , 10 , 20 )
7. Map
声明 :var map1 map[keytype]valuetype
特点 :键值对、无序。
常用方法 :
make
:创建 Map。
delete
:删除键值对。
1 2 3 myMap := make (map [string ]string ) myMap["a" ] = "b" delete (myMap, "a" )
8. 访问 Map 元素 1 2 3 4 5 6 7 8 9 10 value, exists := myMap["a" ]if exists { println (value) }for k, v := range myMap { println (k, v) }
9. 结构体和指针 1 2 3 4 5 6 7 8 9 10 11 12 type MyType struct { Name string }func printMyType (t *MyType) { println (t.Name) }func main () { t := MyType{Name: "test" } printMyType(&t) }
通过 type … struct
关键字自定义结构体
Go 语言支持指针,但不支持指针运算
指针变量的值为内存地址
未赋值的指针为 nil
10. 结构体标签 1 2 3 4 5 6 7 8 9 10 11 type MyType struct { Name string `json:"name"` }func main () { mt := MyType{Name: "test" } myType := reflect.TypeOf(mt) name := myType.Field(0 ) tag := name.Tag.Get("json" ) println (tag) }
结构体中的字段除了有名字和类型外,还可以有一个可选的标签(tag)
使用场景:Kubernetes APIServer 对所有资源的定义都用 Json tag 和 protoBuff tag
NodeName string json:"nodeName,omitempty" protobuf:"bytes,10,opt,name=nodeName"
11. 类型别名 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type ServiceType string const ( ServiceTypeClusterIP ServiceType = "ClusterIP" ServiceTypeNodePort ServiceType = "NodePort" ServiceTypeLoadBalancer ServiceType = "LoadBalancer" ServiceTypeExternalName ServiceType = "ExternalName" )
面试知识点:
数组和切片有什么区别?
make
和 new
有什么区别?
如何判断一个 Map 中是否存在某个键?
结构体标签有什么作用?
课后练习 1.1
安装 Go
安装 IDE 并安装 Go 语言插件
编写一个小程序
给定一个字符串数组["I","am","stupid","and","weak"]
用 for
循环遍历该数组并修改为["I","am","smart","and","strong"]
答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" func main () { words := []string {"I" , "am" , "stupid" , "and" , "weak" } replacements := map [string ]string { "stupid" : "smart" , "weak" : "strong" , } for i, word := range words { if replacement, ok := replacements[word]; ok { words[i] = replacement } } fmt.Println(words) }
六、Go 语言函数 1. main 函数
每个 Go 程序都应该有一个 main
包。
main
包里的 main
函数是程序的入口。
1 2 3 4 5 package mainfunc main () { println ("Hello, world!" ) }
2. 参数解析
main
函数没有参数,不同于其他语言的[]string args
。
可以使用 os.Args
获取命令行参数。
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" "os" )func main () { args := os.Args fmt.Println("Arguments:" , args) }
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "flag" "fmt" )func main () { name := flag.String("name" , "world" , "specify the name you want to say hi" ) flag.Parse() fmt.Println("Hello," , *name) }
3. init 函数
init
函数会在包初始化时自动执行。
谨慎使用 init
函数,避免循环依赖和不可重复运行的问题。
1 2 3 4 5 6 7 package mainvar myVariable = 0 func init () { myVariable = 1 }
4. 返回值
多值返回 :函数可以返回多个值。
命名返回值 :可以给返回值命名,并在函数体中直接使用。
1 2 3 4 5 6 7 8 9 func swap (x, y string ) (string , string ) { return y, x }func split (sum int ) (x, y int ) { x = sum * 4 / 9 y = sum - x return }
5. 调用者忽略部分返回值 1 result, _ = strconv.Atoi(origStr)
6. 传递变长参数 1 func append (slice []Type, elems ...Type) []Type
7. 内置函数
函数名
作用
close
管道关闭
len
, cap
返回数组、切片、Map 的长度或容量
new
, make
内存分配
copy
, append
操作切片
panic
, recover
错误处理
print
, println
打印
complex
, real
, imag
操作复数
8. 回调函数 (Callback)
将函数作为参数传递给其他函数,并在其他函数内部调用执行。
1 2 3 4 5 6 7 8 9 10 11 func doOperation (y int , f func (int , int ) ) { f(y, 1 ) }func increase (a, b int ) { println ("increase result is:" , a+b) }func main () { doOperation(1 , increase) }
9. 闭包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func adder () func (int ) int { sum := 0 return func (x int ) int { sum += x return sum } }func main () { pos, neg := adder(), adder() for i := 0 ; i < 10 ; i++ { fmt.Println( pos(i), neg(-2 *i), ) } }
10. 方法
1 2 3 4 5 6 7 8 9 10 11 12 type Vertex struct { X, Y float64 }func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) }func main () { v := Vertex{3 , 4 } fmt.Println(v.Abs()) }
11. 传值还是传指针
Go 语言只有一种规则-传值
函数内修改参数的值不会影响函数外原始变量的值
可以传递指针参数将变量地址传递给调用函数,Go 语言会 复制该指针作为函数内的地址,但指向同一地址
思考:当我们写代码的时候,函数的参数传递应该用struct
还是pointer
?
如果需要修改参数的值,或者参数较大,传递指针更高效。
如果不需要修改参数的值,且参数较小,传递值更安全。
12. 接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 type Abser interface { Abs() float64 }type MyFloat float64 func (f MyFloat) Abs() float64 { if f < 0 { return float64 (-f) } return float64 (f) }type Vertex struct { X, Y float64 }func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) }func main () { var a Abser f := MyFloat(-math.Sqrt2) v := Vertex{3 , 4 } a = f a = &v fmt.Println(a.Abs()) }
13. 注意事项
Interface
是可能为 nil
的,所以针对 interface
的使用一定要预先判空,否则会引起程序 crash(nil panic)
Struct
初始化意味着空间分配,对 struct
的引用不会出现空指针
14. 反射机制
reflect.TypeOf()
返回被检查对象的类型
reflect.ValueOf()
返回被检查对象的值
1 2 3 4 5 6 myMap := make (map [string ]string , 10 ) myMap["a" ] = "b" t := reflect.TypeOf(myMap) fmt.Println("type:" , t) v := reflect.ValueOf(myMap) fmt.Println("value:" , v)
15. 基于 struct 的反射 1 2 3 4 5 6 7 8 9 10 11 12 13 myStruct := T{A: "a" } v1 := reflect.ValueOf(myStruct)for i := 0 ; i < v1.NumField(); i++ { fmt.Printf("Field %d: %v\n" , i, v1.Field(i)) }for i := 0 ; i < v1.NumMethod(); i++ { fmt.Printf("Method %d: %v\n" , i, v1.Method(i)) } result := v1.Method(0 ).Call(nil ) fmt.Println("result:" , result)
16. Go 语言中的面向对象编程
可见性控制
public
-常量、变量、类型、接口、结构、函数等的名称大写
private
-非大写就只能在包内使用
继承
多态
17. Json 编解码
Unmarshal
: 从 string
转换至 struct
1 2 3 4 5 6 7 8 func unmarshal2Struct (humanStr string ) Human { h := Human{} err := json.Unmarshal([]byte (humanStr), &h) if err != nil { println (err) } return h }
Marshal
: 从 struct
转换至 string
1 2 3 4 5 6 7 8 func marshal2JsonString (h Human) string { h.Age = 30 updatedBytes, err := json.Marshal(&h) if err != nil { println (err) } return string (updatedBytes) }
json
包使用 map[string]interface{}
和 []interface{}
类型保存任意对象
可通过如下逻辑解析任意 json
1 2 3 4 5 6 7 8 9 10 11 12 13 var obj interface {} err := json.Unmarshal([]byte (humanStr), &obj) objMap, ok := obj.(map [string ]interface {})for k, v := range objMap { switch value := v.(type ) { case string : fmt.Printf("type of %s is string, value is %v\n" , k, value) case interface {}: fmt.Printf("type of %s is interface{}, value is %v\n" , k, value) default : fmt.Printf("type of %s is wrong, value is %v\n" , k, value) } }
面试知识点:
Go 语言中如何实现函数的重载?
什么是闭包?闭包有什么作用?
Go 语言中的方法和普通函数有什么区别?
Go 语言中如何实现接口?接口和抽象类有什么区别?
什么是反射?反射有什么作用?
如何使用 encoding/json
包进行 JSON 编解码?
六、常用语法 1. 错误处理 Go 语言无内置 exceptio
机制,只提供 error
接口供定义错误
1 2 3 type error interface { Error() string }
可通过 errors.New
或 fmt.Errorf
创建新的 error
var errNotFound error = errors.New("NotFound")
通常应用程序对 error
的处理大部分是判断 error
是否为 nil
如需将 error
归类,通常交给应用程序自定义,比如 kubernetes
自定义了与 apiserver
交互的不同类型错误
1 2 3 4 5 6 7 8 9 10 type StatusError struct { ErrStatus metav1.Status }var _ error = &StatusError{}func (e *StatusError) Error() string { return e.ErrStatus.Message }
2. defer
函数返回之前执行某个语句或函数
等同于Java 和C# 的finally
常见的 defer
使用场景:记得关闭你打开的资源
defer file.Close()
defer mu.Unlock()
defer println("")
3. Panic 和 recover
panic
: 可在系统出现不可恢复错误时主动调用 panic
, panic
会使当前线程直接 crash
defer
: 保证执行并把控制权交还给接收到 panic
的函数调用者
recover
: 函数从 panic
或 错误场景中恢复
1 2 3 4 5 6 7 defer func () { fmt.Println("defer func is called" ) if err := recover (); err != nil { fmt.Println(err) } }()panic ("a panic is triggered" )
七. 多线程 1. 并发和并行
并发(concurrency)
并行(parallellism)
2. 协程
进程:
分配系统资源(CPU 时间、内存等)基本单位
有独立的内存空间,切换开销大
线程:进程的一个执行流,是 CPU 调度并能独立运行的的基本单位
同一进程中的多线程共享内存空间,线程切换代价小
多线程通信方便
从内核层面来看线程其实也是一种特殊的进程,它跟父进程共享了打开的文件和文件系统信息,共 享了地址空间和信号处理函数
协程
Go语言中的轻量级线程实现
Golang 在runtime
、系统调用等多方面对goroutine
调度进行了封装和处理,当遇到长时间执行 或者进行系统调用时,会主动把当前goroutine
的CPU (P)
转让出去,让其他goroutine
能被调度 并执行,也就是Golang
从语言层面支持了协程
3. Communicating Sequential Process
CSP
描述两个独立的并发实体通过共享的通讯channel
进行通信的并发模型。
Go 协程 goroutine
是一种轻量线程,它不是操作系统的线程,而是将一个操作系统线程分段使用,通过调度器实现协 作式调度。
是一种绿色线程,微线程,它与Coroutine
协程也有区别,能够在发现堵塞后启动新的微线程。
通道 channel
类似Unix
的Pipe
,用于协程之间通讯和同步。
协程之间虽然解耦,但是它们和Channel
有着耦合。
4. 线程和协程的差异
每个 goroutine
(协程) 默认占用内存远比 Java
、C
的线程少
线程/goroutine
切换开销方面,goroutine
远比线程小
线程:涉及模式切换(从用户态切换到内核态)、16个寄存器、PC
、SP
…等寄存器的刷新
goroutine
:只有三个寄存器的值修改-PC
/ SP
/ DX
.
GOMAXPROCS
5. 协程示例
1 2 3 4 for i := 0 ; i < 10 ; i++ { go fmt.Println(i) } time.Sleep(time.Second)
6. channel - 多线程通信
Channel
是多个协程之间通讯的管道
一端发送数据,一端接收数据
同一时间只有一个协程可以访问数据,无共享内存模式可能出现的内存竞争
协调协程的执行顺序
声明方式
var identifier chan datatype
操作符<-
示例
1 2 3 4 5 6 ch := make (chan int )go func () { fmt.Println("hello from goroutine" ) ch <- 0 }() i := <-ch
7. 通道缓冲
基于 Channel
的通信是同步的
当缓冲区满时,数据的发送是阻塞的
通过 make
关键字创建通道时可定义缓冲区容量,默认缓冲区容量为 0
下面两个定义的区别?
ch:= make(chan int)
ch:= make(chan int,1)
8. 遍历通道缓冲区 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ch := make (chan int , 10 )go func () { for i := 0 ; i < 10 ; i++ { rand.Seed(time.Now().UnixNano()) n := rand.Intn(10 ) fmt.Println("putting: " , n) ch <- n } close (ch) }() fmt.Println("hello from main" )for v := range ch { fmt.Println("receiving: " , v) }
9. 单向通道
只发送通道
只接收通道
Istio webhook controller
func (w *WebhookCertPatcher) runWebhookController(stopChan <-chan struct{}) {}
如何用: 双向通道转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var c = make (chan int )go prod(c)go consume(c)func prod (ch chan <- int ) { for { ch <- 1 } }func consume (ch <-chan int ) { for { <-ch } }
10. 关闭通道
通道无需每次关闭
关闭的作用是告诉接收者该通道再无新数据发送
只有发送方需要关闭通道
1 2 3 4 5 ch := make (chan int )defer close (ch)if v, notClosed := <-ch; notClosed { fmt.Println(v) }
11. select
当多个协程同时运行时,可通过 select
轮询多个通道
如果所有通道都阻塞则等待,如定义了 default
则执行 default
如多个通道就绪则随机选择
1 2 3 4 5 6 7 8 select {case v := <-ch1: ...case v := <-ch2: ...default : ... }
12. 定时器 Timer
time.Ticker
以指定的时间间隔重复的向通道 C
发送时间值
使用场景
1 2 3 4 5 6 7 8 timer := time.NewTimer(time.Second)select {case <-ch: fmt.Println("received from ch" )case <-timer.C: fmt.Println("timeout waiting from channel ch" ) }
13. 上下文 Context
超时、取消操作或者一些异常情况,往往需要进行抢占操作或者中断后续操作
Context
是设置截止日期、同步信号,传递请求相关值的结构体
1 2 3 4 5 6 type Context interface { Deadline() (deadline time.Time, ok bool ) Done() <-chan struct {} Err() error Value(key interface {}) interface {} }
用法
context.Background
context.TODO
context.WithDeadline
context.WithValue
context.WithCancel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 done := make (chan bool )go func () { for { select { case <-done: fmt.Println("done channel is triggerred, exit child go routine" ) return } } }()close (done) ctx, cancel := context.WithTimeout(context.Background(), time.Second)defer cancel()go process(ctx, 100 *time.Millisecond) <-ctx.Done() fmt.Println("main:" , ctx.Err())func process (ctx context.Context, duration time.Duration) { select { case <-time.After(duration): fmt.Println("process successfully" ) case <-ctx.Done(): fmt.Println("process cancelled" ) } }
通过 context
包, 可以取消 goroutine
的执行, 或者给 goroutine
设置 Deadline, 超时后 goroutine
会退出。
面试知识点:
Go 语言中如何实现并发编程?
goroutine
和线程有什么区别?
channel
的作用是什么?如何使用 channel
进行协程间通信?
如何实现一个有缓冲的 channel
?
如何使用 select
语句处理多个 channel
?
如何使用 context
包取消 goroutine
的执行?
课后练习 1.2
基于 Channel
编写一个简单的单线程生产者消费者模型
队列: 队列长度 10,队列元素类型为 int
生产者: 每 1 秒往队列中放入一个类型为 int
的元素,队列满时生产者可以阻塞
消费者: 每一秒从队列中获取一个元素并打印,队列为空时消费者阻塞
答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package mainimport ( "fmt" "time" )func producer (ch chan <- int ) { i := 0 for { time.Sleep(1 * time.Second) ch <- i fmt.Println("Produced:" , i) i++ } }func consumer (ch <-chan int ) { for { time.Sleep(1 * time.Second) i := <-ch fmt.Println("Consumed:" , i) } }func main () { ch := make (chan int , 10 ) go producer(ch) go consumer(ch) select {} }
八、Go Modules 1. 为什么需要 Go Modules 在 Go 1.11 版本之前,Go 语言的依赖管理一直是一个痛点。GOPATH
模式存在以下问题:
项目必须放在 $GOPATH/src
目录下 :限制了项目存放的位置,不够灵活。
没有版本控制 :无法指定项目依赖的特定版本,容易出现版本冲突。
无法处理 vendor 依赖 :无法将依赖包复制到项目内部,不利于项目的独立性和可移植性。
Go Modules 的出现,解决了这些问题,成为了 Go 语言官方推荐的依赖管理方式。
2. Go Modules 的主要特性
项目可以放在任何位置 :不再受限于 $GOPATH/src
目录。
版本控制 :可以指定项目依赖的特定版本,解决了版本冲突问题。
vendor 支持 :可以将依赖包复制到项目内部的 vendor
目录,提高了项目的独立性和可移植性。
模块代理 :可以通过设置 GOPROXY
环境变量,使用模块代理加速依赖包的下载。
3. Go Modules 的基本使用
初始化模块 :在项目根目录下执行 go mod init <module_name>
,创建 go.mod
文件。
添加依赖 :执行 go get <package_name>@<version>
,会自动更新 go.mod
和 go.sum
文件。
构建项目 :执行 go build
,会自动下载并构建依赖。
运行项目 :执行 go run
,会自动下载、构建并运行项目。
整理依赖 :执行 go mod tidy
,会移除未使用的依赖,并更新 go.mod
和 go.sum
文件。
vendor 依赖 :执行 go mod vendor
,会将依赖包复制到项目内部的 vendor
目录。
面试知识点:
Go Modules 解决了 GOPATH
模式的哪些问题?
go.mod
和 go.sum
文件有什么作用?
如何使用 Go Modules 添加、更新和删除依赖?
如何使用 vendor 依赖?
九、Go 语言与云原生 Go 语言的特性使其非常适合云原生应用开发:
高效的编译和执行速度 :Go 语言的编译速度快,生成的二进制文件小,启动速度快,非常适合云原生环境下的快速部署和弹性伸缩。
强大的并发编程支持 :Go 语言的 goroutine 和 channel 机制,可以轻松编写高并发程序,充分利用多核处理器,提高应用的吞吐量和响应速度。
简洁的语法和丰富的标准库 :Go 语言的语法简洁易学,标准库功能丰富,可以减少开发者的工作量,提高开发效率。
跨平台编译 :Go 语言支持交叉编译,可以方便地为不同的操作系统和处理器架构构建应用。
容器友好 :Go 语言生成的二进制文件不依赖于外部库,非常适合打包成 Docker 镜像,方便部署和管理。
Go 语言已经成为云原生领域的主流语言,许多知名的云原生项目都是用 Go 语言开发的,例如:
Docker :容器引擎。
Kubernetes :容器编排平台。
Istio :服务网格。
Etcd :分布式键值存储。
Prometheus :监控系统。