模块一:Go 语言特性
----------------------------
云原生很火,有台湾、海外的同学。
模块一:Go 语言特性
----------------------------
Martin Flower 企业应用架构模式,总结了12要素
I. 基准代码-一份基准代码,多份部署
II. 依赖-显式声明依赖关系--JAVA使用POM声明,GO用什么机制?
III. 配置-在环境中存储配置
IV. 后端服务-把后端服务当作附加资源
V. 构建,发布,运行-严格分离构建和运行
VI. 进程-以一个或多个无状态进程运行应用
VII. 端口绑定-通过端口绑定提供服务
VIII. 并发-通过进程模型进行扩展
IX. 易处理-快速启动和优雅终止可最大化健壮性
X. 开发环境与线上环境等价-尽可能的保持开发,预发布,线上环境相同
XI. 日志-把日志当作事件流
XII. 管理进程-后台管理任务当作一次性进程运行
----------------------------
目录:
1. 为什么需要另外一种语言?
2. Go 语言编译环境设置
3. 控制结构
4. Go 语言常用数据结构
5. Go 语言函数调用
6. 常用语法
7. 多线程
•深入理解 channel
•基于 channel 编写一个生产者消费者程序
----------------------------
1. 为什么需要 Go 语言
----------------------------
Go 语言的原则
Less is exponentially more
– Rob Pike, Go Designer
Do Less, Enable More
– Russ Cox, Go Tech Lead
----------------------------
为什么需要 Go 语言
其他编程语言的弊端。
•硬件发展速度远远超过软件。
•C 语言等原生语言缺乏好的依赖管理 (依赖头文件)。
•Java 和 C++ 等语言过于笨重。
•系统语言对垃圾回收和并行计算等基础功能缺乏支持。
•对多核计算机缺乏支持。--linux里面线程和进程差不多,开销比较大
Go 语言是一个可以编译高效,支持高并发的,面向垃圾回收的全新语言。
•秒级完成大型程序的单节点编译。
•依赖管理清晰。
•不支持继承,程序员无需花费精力定义不同类型之间的关系。
•支持垃圾回收,支持并发执行,支持多线程通讯。
•对多核计算机支持友好。
----------------------------
Go 语言不支持的特性
• 不支持函数重载和操作符重载
• 为了避免在 C/C++ 开发中的一些 Bug 和混乱,不支持隐式转换
• 支持接口抽象,不支持继承
• 不支持动态加载代码
• 不支持动态链接库
• 通过 recover 和 panic 来替代异常机制
• 不支持断言
• 不支持静态变量
如果你没做过其他语言的开发,
那么恭喜,以上大部分复杂的问题,在 Go 语言里不存在,你也无需关心。
----------------------------
Go语言特性衍生来源
C:基本语法和数据结构
JAVA:接口抽象
JAVA,C#:包定义
linbo:CSP模型
JAVAScript/ruby:多态支持
应用:Docker,kubernetes,istio
用Go语言改写以前的软件,CSP模型强大
阿里新的反向代理,也是使用GO语言改写的。
----------------------------
2.Go语言环境搭建
----------------------------
下载Go
-Go安装文件以及源代码
https://golang.google.cn/dl
-下载对应平台的二进制
dowload from golang.google.cn
sudo rm -rf /usr/local/go && tar -C /usr/local -xzf go1.18.1.linux-amd64.tar.gz
上次是1.18.1,现在已经是1.19版本了
$HOME/.bashrc
export PATH=$PATH:/usr/local/go/bin
$ go version
go version go1.18.1 linux/amd64
$ sudo rm -rf /usr/local/go
$ sudo tar -C /usr/local -xzf go1.19.linux-amd64.tar.gz
$ go version
go version go1.19 linux/amd64
$ which go
/usr/local/go/bin/go
-环境变量
-GOROOT
- go的安装目录
$HOME/.bashrc
export GOROOT=/usr/local/go/
$ echo $GOROOT
/usr/local/go/
用处:设置好这个变量,在IDE环境中查看go的源代码,可以跳转过来
代码在/usr/local/go/src目录下
-GOPATH
-src:存放源代码
-pkg:存放依赖包
-bin:存放可执行文件
export GOPATH=$HOME/go
echo $GOPATH
/home/???/go
-其他常用变量
-GOOS,GOARCH,GOPROXY
-国内用户建议设置goproxy: export GOPROXY=https://goproxy.cn
几个相关目录说明:
/usr/local/go
/usr/local/go/bin/go
echo $GOPATH
users/fmeng/go/src/github.com/cncamp/golang --放置go语言相关案例的地方
有依赖的包,按src下的路径写,如import github.com/cncamp
http://github.com/cncamp
----------------------------
IDE设置(VS Code)
-下载并安装 Visual Studio Code
https://code.visualstudio.com/download
-安装 Go 语言插件
https://marketplace.visualstudio.com/items?itemName=golang.go
其他可选项
•Intellj goland,收费软件
•vim,sublime等
---------------------------
一些基本命令
bug-start a bug report
build-compile packages and dependencies
clean-remove object files and cached files
doc-show documentation for package or symbol
env-print Go environment information
fix-update packages to use new APIs
fmt-gofmt (reformat) package sources
generate-generate Go files by processing source
get-add dependencies to current module and install them
build的例子:
https://github.com/cncamp/golang/blob/master/examples/module1/helloworld/main.go
cd helloworld
cat main.go
go build main.go
交叉编译
GOOS=linux go build main.go,就可以在mac下编译linux的目标文件
-fmt
go fmt main.go
main.go:10:1: unexpected semicolon or newline before {
exit status 2
不定自动修正?
gofmt -w -l main.go
main.go:10:1: unexpected semicolon or newline before {
还是不行,只能找到不符合格式要求的地方,不能修复,没有啥用处。
----------------------------
一些基本命令
install-compile and install packages and dependencies
--可以在制作docker文件中,直接提供源码go install
list-list packages or modules
mod-module maintenance
--
run-compile and run Go program
test-test packages
tool-run specified go tool
version-print Go version
vet-report likely mistakes in packages
----------------------------
Go build
•Go 语言不支持动态链接,因此编译时会将所有依赖编译进同一个二进制文件。
•指定输出目录。
go build –o bin/mybinary .
•常用环境变量设置编译操作系统和 CPU 架构。
GOOS=linux GOARCH=amd64 go build
•全支持列表。
$GOROOT/src/go/build/syslist.go
----------------------------
Go test
Go 语言原生自带测试
import "testing"
func TestIncrease(t *testing.T) {
t.Log("Start testing")
increase(1, 2)
}
go test ./… -v 运行测试
go test 命令扫描所有*_test.go为结尾的文件,惯例是将测试代码与正式代码放在同目录,
如 foo.go 的测试代码一般写在 foo_test.go
例子:
go test
# github.com/cncamp/golang/examples/module1/callbacks
main_test.go:5:8: no required module provides package github.com/stretchr/testify/assert; to add it:
go get github.com/stretchr/testify/assert
FAIL github.com/cncamp/golang/examples/module1/callbacks [setup failed]
$ go get github.com/stretchr/testify/assert
go: downloading github.com/stretchr/testify v1.8.0
go: downloading github.com/davecgh/go-spew v1.1.1
go: downloading github.com/pmezard/go-difflib v1.0.0
go: downloading gopkg.in/yaml.v3 v3.0.1
go: added github.com/davecgh/go-spew v1.1.1
go: added github.com/pmezard/go-difflib v1.0.0
go: added github.com/stretchr/testify v1.8.0
go: added gopkg.in/yaml.v3 v3.0.1
zxl@qwq:~/go/src/github.com/cncamp/golang/examples/module1/callbacks$ go test
PASS
ok github.com/cncamp/golang/examples/module1/callbacks 0.009s
如何在vscode里面直接运行?
安装了一个go Nightly以后可以在vscode中直接运行go test了
----------------------------
Go vet
代码静态检查,发现可能的 bug 或者可疑的构造
-Print-format 错误,检查类型不匹配的print
str := “hello world!”
fmt.Printf("%d\n", str)
-Boolean 错误,检查一直为 true、false 或者冗余的表达式
fmt.Println(i != 0 || i != 1)
-Range 循环,比如如下代码主协程会先退出,go routine无法被执行
words := []string{"foo", "bar", "baz"}
for _, word := range words {
go func() {
fmt.Println(word).
}()
}
-Unreachable的代码,如 return 之后的代码
-其他错误,比如变量自赋值,error 检查滞后等
res, err := http.Get("https://www.spreadsheetdb.io/")
defer res.Body.Close()
if err != nil {
Log.Fatal(err)
}
----------------------------
代码版本控制
-下载安装Git Command Line
-https://git-scm.com/downloads
-github
-本课代码 https://github.com/cncamp/golang
-创建代码目录
-mkdir –p $GOPATH/src/github.com/cncamp
-cd $GOPATH/src/github.com/cncamp
-代码下载
•git clone https://github.com/cncamp/golang.git
-修改代码
-上传代码
git add filename
git commit –m 'change logs'
git push
----------------------------
Golang playground
官方 playground
https://play.golang.org/
可直接编写和运行 Go 语言程序
国内可直接访问的 playground
https://goplay.tools/
----------------------------
3控制结构
----------------------------
If
-基本形式
if condition1 {
// do something
} else if condition2 {
// do something else
} else {
// catch-all or default
}
-if的简短语句
-同for一样,if语句可以在表达式前执行一个简单的语句
if v:=x-100;v<0{
return v
}
----------------------------
switch
switch var1 {
case val1: //空分支
case val2:
fallthrough //执行case3中的f()
case val3:
f()
default://默认分支
...
}
不用写break
----------------------------
For
Go只有一种循环结构:for循环
-计入计数器的循环
for 初始化语句; 条件语句; 修饰语句 {}
for i:=0;i<10;i++{
sum+=i
}
-初始化语句和后置语句是可选的,此场景与 while 等价(Go 语言不支持 while)
for ; sum < 1000; {
sum += sum
}
-无限循环
for{
if condition1 {
break
}
}
----------------------------
for-range
遍历数组、切片、字符串、Map等
for index,char:=range myString {
...
}
for key,value:=range MyMap {
...
}
for index, value := range MyArray {
...
}
需要注意:如果 for range 遍历指针数组,则 value 取出的指
针地址为原指针地址的拷贝。
----------------------------
初始化环境:这些都放到$HOME/.bashrc文件中
export PATH=$PATH:/usr/local/go/bin
export GOPATH=/home/zxl/go:/home/zxl/mygo/go_learning
/home/zxl/go/src/github.com/cncamp/golang
export GOPROXY=https://goproxy.cn
go mod init github.com/cncamp/golang
第一个例子:入参
module1/helloworld/helloworld.go
package main
func main(){} -- 入口函数
没有入参,直接使用os.Args
go build main.go
zxl@qwq:~/go/src/github.com/cncamp/golang/examples/module1/helloworld$ ./main
os args is: [./main]
input parameter is: world
Hello world from Go
zxl@qwq:~/go/src/github.com/cncamp/golang/examples/module1/helloworld$ ./main zzz
os args is: [./main zzz]
input parameter is: world
Hello world from Go
./main --name zzz
os args is: [./main --name zzz]
input parameter is: zzz
Hello zzz from Go
加上--name 就认为是一个参数名,后面是参数值
name := flag.String("name", "world", "specify the name you want to say hi")
--第三个参数是备注信息
kubernetes程序中参数初始化时常用
第二个例子:go vet
module1/govet/main.go
name := "testing"
fmt.Printf("%s\n", name)
fmt.Printf("%d\n", name) -- 提示错误
fmt.Printf("%s\n", name, name) -- 提示错误
运行结果:
testing
%!d(string=testing)
testing
%!(EXTRA string=testing)
可以用命令行检查:
$ go vet
main.# github.com/cncamp/golang/examples/module1/govet
./main.go:8:2: fmt.Printf format %d has arg name of wrong type string
./main.go:9:2: fmt.Printf call needs 1 arg but has 2 args
使用IDE直接给出提示错误
第三个例子:for loop
for i, c := range fullString {
fmt.Println(i, string(c))
}
比较有特色的是函数或者range这种可以返回多个值,赋给多个变量。
----------------------------
4常用数据结构
----------------------------
变量与常量
-常量
const identifier type
-变量
var identifier type
----------------------------
变量定义
-变量
var 语句用于声明一个变量列表,跟函数的参数列表一样,类型在最后。
var c, python, java bool
-变量初始化
变量声明可以包含初始值,每个变量对应一个。
如果初始化值已存在,则可以省略类型;变量会从初始值中获得类型。
var i, j int = 1, 2
-短变量声明
在函数中,简洁赋值语句 := 可在类型明确的地方代替 var 声明。
函数外的每个语句都必须以关键字开始(var, func 等等),因此 := 结构不能在函数外使用。
c, python, java := true, false, "no!"
----------------------------
类型转换与推导
-类型转换
表达式 T(v) 将值 v 转换为类型 T。
一些关于数值的转换:
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
或者,更加简单的形式:
i := 42
f := float64(i)
u := uint(f)
-类型推倒
在声明一个变量而不指定其类型时(即使用不带类型的 := 语法或 var = 表达式语法),变量的类型由右值推导得出。
var i int
j := i // j 也是一个 int
----------------------------
数组
-相同类型且长度固定连续内存片段
-以编号访问每个元素
-定义方法
- var identifier [len]type
-示例
- myArray := [3]int{1,2,3}
----------------------------
切片(slice)
-切片是对数组一个连续片段的引用
-数组定义中不指定长度即为切片
- var identifier []type
-切片在未初始化之前默认为nil, 长度为0
-常用方法
例子:
module1/slice/main.go
func main() {
myArray := [5]int{1, 2, 3, 4, 5}
mySlice := myArray[1:3]
fmt.Printf("mySlice %+v\n", mySlice)
fullSlice := myArray[:]
remove3rdItem := deleteItem(fullSlice, 2)
fmt.Printf("remove3rdItem %+v\n", remove3rdItem)
}
func deleteItem(slice []int, index int) []int {
return append(slice[:index], slice[index+1:]...)
}
myArray := [5]int{1,2,3,4,5}
mySlice := myArray[1:3]
mySlice1 := []int{}
mySlice1 = append(mySlice1,1)
mySlice1 = append(mySlice1,2)
mySlice1 = append(mySlice1,3)
删除很麻烦
append(slice[:index], slice{index+1:}...)
注意切片的范围,前面的index是包括的,:后面的index不包括
----------------------------
Make和New
-New返回指针地址
-Make返回第一个元素,可以预设内存空间,避免未来的内存拷贝
-示例
mySlice1 := new([]int)
mySlice2 := make([]int,0)
mySlice2 := make([]int,10) -- len 10 cap 10
mySlice2 := make([]int,10,20) --len 10 cap 20
----------------------------
关于切片的常见问题
-切片是连续内存并且可以动态扩展
a :=[]int{}
b:=[]int{1,2,3}
c:=a
a:=append(b,1) --c和a不相等了,所以不要b赋值给a. b append后有可能扩展,开辟一个新的内存空间
最佳实践:append中的切片一定是同一个: a:=append(a,1)
-修改切片值
mySlice:=[]int{10,20,30,40,50}
for _,value:=range mySlice{
value*=2
}
临时变量编号,切片没有变
go语言都是值传递
for index,_=range mySlice {
mySlice[index]*=2
}
这样是改的切片值
例子:
module1/slice/forrange/main.go
for index, _ := range mySlice {
vet起作用了,改一下就不提示了
for index := range mySlice {
fmt打印
fmt.Printlin("aa","bb","cc") -- interface{}
fmt.Printf("%s%d%v") -- 格式化打印
%s 字符串
%d 值
%v struct结构
%+v struct结构和其中的值
fmt.Sprintf(...) -- 拼字符串
fmt.Errorf(...)
package组织
类似JAVA,但不用写全路径
----------------------------
Map
-声明方法
var map1 map[keytype]valuetype
-示例
myMap := make(map[string]string, 10)
myMap["a"] = "b"
myFuncMap := map[string]func() int{ -- 函数可以作为变量的类型
"funcA": func() int { return 1 },
}
fmt.Println(myFuncMap)
f := myFuncMap["funcA"]
fmt.Println(f())
---------------------------
访问 Map 元素
按 Key 取值
value, exists := myMap["a"]
if exists {
println(value)
}
遍历 Map
for k, v := range myMap {
println(k, v)
}
例子:module1/map/main.go
----------------------------
结构体和指针
-通过 type … struct 关键字自定义结构体
-Go 语言支持指针,但不支持指针运算
指针变量的值为内存地址
未赋值的指针为 nil
type MyType struct {
Name string
}
func printMyType(t *MyType){
println(t.Name)
}
func main(){
t := MyType{Name: "test"}
printMyType(&t)
}
----------------------------
结构体标签
-结构体中的字段除了有名字和类型外,还可以有一个可选的标签(tag)
-使用场景:Kubernetes APIServer 对所有资源的定义都用 Json tag 和 protoBuff tag
--NodeName string `json:"nodeName,omitempty" protobuf:"bytes,10,opt,name=nodeName"`
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)
}
----------------------------
类型重命名
// Service Type string describes ingress methods for a service
type ServiceType string
const (
// ServiceTypeClusterIP means a service will only be accessible inside the
// cluster, via the ClusterIP.
ServiceTypeClusterIP ServiceType = "ClusterIP"
// ServiceTypeNodePort means a service will be exposed on one port of
// every node, in addition to 'ClusterIP' type.
ServiceTypeNodePort ServiceType = "NodePort"
// ServiceTypeLoadBalancer means a service will be exposed via an
// external load balancer (if the cloud provider supports it), in addition
// to 'NodePort' type.
ServiceTypeLoadBalancer ServiceType = "LoadBalancer"
// ServiceTypeExternalName means a service consists of only a reference to
// an external name that kubedns or equivalent will return as a CNAME
// record, with no exposing or proxying of any pods involved.
)
----------------------------
接口
-接口定义一组方法集合
type IF interface {
Method1(param_list)return_type
}
----------------------------
----------------------------
反射机制
-reflect.TypeOf()返回被检查对象的类型
-reflect.ValueOf()返回被检查对象的值
-示例
myMap := make(map[string]string,10)
myMap["a"]=“b”
----------------------------
----------------------------
defer
-函数返回之前执行某个语句或函数
-等同与JAVA和C#的finally
----------------------------
Panic和recover
defer func() {
fmt.Println("defer fnc is called)
if err := recover(); err != nil {
fmt..Println(err)
}
}()
panic("a panic is triggered")
----------------------------
7.多线程
----------------------------
并发和并行
-并发concurrency
两个或者多个事件在同一时间间隔发生
-并行parallellism
两个或者多个事件在同一时刻发生
----------------------------
协程
-进程
-线程
-协程
-Go语言中的轻量级线程实现
-Golang在runtime、系统调用
----------------------------
Communicating Sequential Process
-CSP
-Go 协程 goroutine
----------------------------
线程和协程的差异
-每个goroutine(协程)默认占用内存远比JAVA,C的线程少
-goroutine:2KB
-线程:8MB
-线程/goroutine切换
----------------------------
协程示例
-启动新协程: go functionName()
----------------------------
----------------------------
----------------------------