- IDE
- 入口函数
- 变量
- 常量, 枚举, iota
- 占位符, nil
- 函数体外语句
- if、switch、type switch、select、for
- continue, break, goto
- 指针
- 数组、切片、range、map
- 日期格式化
- struct (类)
- struct tag、reflect 反射、json 序列化
- 通过匿名变量实现 struct 的继承、自定义 String() 方法
- type 关键字
- 函数的引用传递
- 函数闭包
- 标识符大小写
- package
- 接口
- 错误处理
- defer (延迟执行)
- panic (崩溃) 和 recover (恢复)
- 模块管理
- go run, go build, go install
- 并发协程 goroutine
- 通道 (channel)
- web framework
- go gin
IDE
GoLand 收费
LiteIDE 免费,用户体验一般
入口函数
可执行程序必须包含 main 函数
如果定义了 main 或 init 函数,启动后会自动执行,不需要显示调用,init 会在 main 之前执行,不管哪个先定义
main/init 外的代码会先执行,不管哪块代码先写
func main() {
fmt.Println("Hello, World!")
}
func init() {
fmt.Println("
init
")
}
var b = test()
会先执行 test 再执行 init 再执行 main
变量
var a string
var b string = "abc" // 声明同时初始化
var c = "abc" // 直接初始化可以省略类型
d := "abc" // 同时省略 var 和类型,如果 d 已经定义的话,会报错
var a, b, c string // 可以同时定义、赋值多个
var ( // 定义多个类型不同的变量
a int
b bool
)
和其他语言不同,go 的局部变量如果定义但是没使用,也会报错
declared and not used
需要使用变量才行
常量, 枚举, iota
const LENGTH int = 10
常量当作枚举用
const (
Unknown = 0
Female = 1
Male = 2
)
iota 对 const 计数
const (
a = iota // 0
b // 1
c // 2
d = "ha" // "ha" iota = 3
e // "ha" iota = 4
f = 100 // 100 iota = 5
g // 100 iota = 6
h = iota // 7
i // 8
)
遇到新的 const 时 iota 会重置为 0
占位符, nil
var a, _ = test()
空用 nil 表示
函数体外语句
syntax error: non-declaration statement outside function body
错误原因: 函数体外的每个语句,都必须是 golang 的关键字开始
比如不能是
a := 1
test()
必须用
var a = 123
var b = test()
这样才不报错
if、switch、type switch、select、for
go 的 if 条件语句不需要括号
if result > 100 {
fmt.Printf("%d + %d = %d
", a, b, result)
} else {
fmt.Println(result)
}
switch 语法
switch var1 {
case val1:
...
case val2:
...
default:
...
}
type switch 可判断变量类型
var x = test()
switch x.(type) {
case nil:
...
case int:
...
case func(int) float64:
...
case bool, string: // bool 或 string 类型
...
default:
...
}
select 的所有 case 都必须是通信操作
如果有多个 case 可以执行,随机选择一个
如果都不可执行,那么阻塞直到某个通信可以运行
var c1, c2, c3 chan int
var i1, i2 int
select {
case i1 = <-c1:
fmt.Printf("received ", i1, " from c1
")
case c2 <- i2:
fmt.Printf("sent ", i2, " to c2
")
case i3, ok := (<-c3):
if ok {
fmt.Printf("received ", i3, " from c3
")
} else {
fmt.Printf("c3 is closed
")
}
default:
fmt.Printf("no communication
")
}
go 没有 while 语句,只用 for
for init; condition; post {}
for condition { }
for { }
init; condition; post 这些条件可以是空的
continue, break, goto
for a < 10 {
if a == 5 {
continue;
}
a++;
}
continue 可以加 label
re:
for i := 1; i <= 10; i++ {
for j := i; j <= 100; j++ {
if i + j == 100 {
continue re
}
}
}
break 和 goto 的用法类似,也可以加 label
指针
var a int = 20
var i *int
i = &a
fmt.Printf("%d
", *i)
和 c 语言一样
数组、切片、range、map
var n [10]int
for i = 0; i < 10; i++ {
n[i] = i + 100
}
切片就是长度可变的数组
var n []int
var n []int = make([]int, 10) // 初始化长度
s := arr[startIndex:endIndex]
s := arr[startIndex:]
s := arr[:endIndex]
len(n)
range 可用于迭代
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
集合
var m map[string]string // 声明,默认是 nil
m = make(map[string]string) // 初始化
m["key1"] = "value1"
m["key2"] = "value2"
value, ok := m ["key3"]
if ok {
// 存在
} else {
// 不存在
}
// 删除
delete(m, "kk")
// 迭代
for k := range m {
fmt.Println(m[k])
}
注意要用 make 初始化,不然变量是 nil
日期格式化
// 这个 layout 是固定的,必须用这几个数字,不然会报错,这应该是 golang 诞生的时间...
layout := "2006-01-02 15:04:05"
// 按 layout 指定的格式转换成 string
fmt.Printf("current time is %s
", time.Now().Format(layout))
// t, err := time.Parse(layout, "2021-08-09 12:34:27")
var t time.Time
var err error
t, err = time.Parse(layout, "2021-08-09 12:34:27") // 按 layout 指定的格式转换成 Time 类型
if err == nil {
fmt.Println(t.Second())
}
做法和其他语言用 YYYYMMDD 这样的做法不一样,比较直观
struct (类)
go 没有类,继承等概念,而是通过 struct 实现面向对象
type student struct {
name string
age int
}
func (s student) getName() string {
return s.name
}
func (s * student) setName(name string) {
s.name = name
}
func (this student) getAge() int {
return this.age
}
func (this * student) setAge(age int) {
this.age = age
}
func (this student) display() {
fmt.Printf("name is %s, age is %d
", this.name, this.age)
}
可以看到数据和方法的定义是分开的,
方法是在函数定义中加入 struct 比如 (s student),或 (s * student) 表示传引用
初始化和使用
s0 := student{"Li", 20}
s0.display()
s1 := student{}
s1.name = "Wang"
s1.age = 35
s1.display()
s2 := new(student)
s2.name = "Zhang"
s2.age = 30
s2.display()
s3 := new(student)
s3.setName("Mr." + s1.getName())
s3.setAge(s2.getAge() - 5)
s3.display()
s4 := student{name: "Han", age: 12}
s4.display()
和其他语言比还是很不一样
struct tag、reflect 反射、json 序列化
import (
"fmt"
"reflect"
)
/*
* tag 用 `` 标识
* 由多个 key-value 组成
* key 和 value 之间用 : 隔开,不能有空格
* value 用 "" 标识
* 多个 key-value 之间用空格隔开
*/
type Student struct {
Name string `json:"name" id:"100"`
Age int `json:"age" id:"101"`
}
func main() {
student := Student{"Wang", 18}
// 通过反射拿到类型
reflectType := reflect.TypeOf(student)
fmt.Printf("type of student is %+v
", reflectType)
ageField, _ := reflectType.FieldByName("Age")
fmt.Println(ageField.Index)
fmt.Println(ageField.Name)
fmt.Println(ageField.Type)
// 通过反射拿到 tag
fmt.Println(ageField.Tag.Get("json"))
fmt.Println(ageField.Tag.Get("id"))
// 通过反射拿到值
reflectValue := reflect.ValueOf(student)
fmt.Printf("value of student is %+v
", reflectValue)
nameField := reflectValue.FieldByName("Name")
fmt.Println(nameField)
}
结果
type of student is main.Student
[1]
Age
int
age
101
value of student is {Name:Wang Age:18}
Wang
比如 json 就用了 tag 实现序列化时使用和变量名不一样的字段名
import (
"fmt"
"encoding/json"
)
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
student := Student{"Wang", 18}
jsonStr, _ := json.Marshal(student)
fmt.Println(string(jsonStr))
}
输出 {"name":"Wang","age":18}
通过匿名变量实现 struct 的继承、自定义 String() 方法
type Person struct {
Name string
Age int
}
type Teacher struct {
Title string
Class int
Grade int
Person // 匿名, 相当于定义了 Name 和 Age 变量,如果 Teacher 本身定义了这两个变量,那挑先定义的
}
func main() {
teacher := Teacher{}
teacher.Name = "Li" // Teacher 没有直接定义 Name 和 Age
teacher.Age = 30
teacher.Title = "senior"
teacher.Class = 3
teacher.Grade = 6
fmt.Println(teacher)
}
结果是 {senior 3 6 {Li 30}}
可以看到 {Li 30} 被作为一个子变量,可以自定义 String 方法输出
import (
"fmt"
strconv
)
func (this Teacher) String() string {
return "{name:" + this.Name + ", age:" + strconv.Itoa(this.Age) +
", title:" + this.Title + ", class:" + strconv.Itoa(this.Class) +
", grade:" + strconv.Itoa(this.Grade) + "}"
}
结果是 {name:Li, age:30, title:senior, class:3, grade:6}
type 关键字
除了定义 struct,还用于类型别名,和函数别名
func main() {
type myType int
var m myType = 123
fmt.Println(m)
type myFuncType func(int) string
var fn myFuncType = myFunc
fmt.Println(fn(5))
}
func myFunc(n int) string {
return fmt.Sprintf("receive %d", n)
}
其实定义 struct 也相当于是别名
函数的引用传递
func swap(x *int, y *int) {
var temp int
temp = *x
*x = *y
*y = temp
}
var a int = 100
var b int= 200
swap(&a, &b)
struct 是值传递,如果要传 struct 的引用,同样要用指针
func updateStudent(s *student) {
s.age *= 2
}
updateStudent(&s0)
数组默认是值传递
func update(array [3]int) {
array[0] = 100
}
var array = [3]int{1, 2, 3}
update(array) // array 的值不会改变
数组的引用传递要用指针
func update(array *[3]int) {
(*array)[0] = 100
}
var array = [3]int{1, 2, 3}
update(&array) // array 的值会改变
切片默认就是引用传递
func update(slice []int) {
slice[0] = 100
}
slice := make([]int, 0)
slice = append(slice, 1, 2, 3)
update(slice) // slice 的值会改变
map 默认引用传递
func update(mapVar map[string]int) {
mapVar["key"] = 100
}
mapVar := make(map[string]int)
mapVar["key"] = 1
update(mapVar) // mapVar 的值会改变
go 的引用传递和 C 差不多
函数闭包
一个外部函数,如果定义了内部函数,并且内部函数引用了外部函数的变量,并且外部函数返回的是内部函数的引用,这个被返回的内部函数,就是闭包
python 的例子
def outer(a):
base = a
def inner(n):
return base ** n
return inner
f = outer(2) ## 这个 f 就是闭包
print(f(10))
print(f(3))
go 语言
func outer(a int) func(n int) float64 {
base := a
return func(n int) float64 {
return math.Pow(float64(base), float64(n))
}
}
f := outer(2) // 这个 f 就是闭包
fmt.Println(f(10))
fmt.Println(f(3))
通过把函数当作返回类型实现
标识符大小写
标识符(包括常量、变量、类型、函数名、结构字段等等)
大写字母开头的,可以被外部包的代码所使用
小写字母开头的,对包外是不可见的,但包内可见
package
文件的第一行声明该文件所属的包
package fmt
通过 import 引用包
系统自带的 package 在 GOROOT/src
比如
import (
"fmt"
"net/http"
)
可以找到目录 GOROOT/src 有
src/
|- fmt
|- net
|- http
同一级目录下的所有文件必须属于同一个包,子目录下的所有文件属于另一个包
包名通常都和目录名一样,并且用小写
自定义 package
src/
|- test.go
|- calc
|- calc.go
|- func.go
calc.go
package calc
import "errors"
func Calc(num1, num2 int, operator string) (int, error) {
switch operator {
case "+":
return sum(num1, num2), nil
case "-":
return minus(num1, num2), nil
default:
return 0, errors.New("invalid operator!")
}
}
func.go
package calc
func sum(num1, num2 int) int {
return num1 + num2
}
func minus(num1, num2 int) int {
return num1 - num2
}
test.go
package main
import (
"fmt"
"calc"
)
func main() {
var result, _ = calc.Calc(100, 23, "+")
fmt.Println(result)
}
可以看到,calc.go 的 Calc 函数可以直接调用 func.go 的 sum 和 minus 函数,因为都是 package calc,都是同一个包的
但是 test.go 必须 import calc,并且只能调 calc.Calc 不能调用 calc.sum,因为它们是不同 package 的
大写开头的标识符可以被包外引用,小写开头的标识符只能被包内引用
但是直接运行会报错
package calc is not in GOROOT (C:Program FilesGosrccalc)
解决方案一
go env -w GO111MODULE=off
go env -w GOPATH=xxx (xxx 就是源代码 src 的上级目录)
解决方案二
go mod init calculator // 任意名字,在 src 目录下,会产生 go.mod 文件
import 改成
import (
"fmt"
"calculator/calc"
)
这样就能正常运行了
引用的 package 里面定义 init() 会被执行
接口
// 定义接口
type Shape interface {
display()
reset()
}
// 定义结构体
type Rect struct {
m float64
n float64
}
type Circle struct {
radis float64
}
// 实现接口 (值传递)
func (this Rect) display() {
fmt.Printf("this is a rect : %f
", (this.m*2 + this.n*2))
}
func (this Rect) reset() {
this.m = 0
this.n = 0
}
// 实现接口 (引用传递)
func (this *Circle) display() {
fmt.Printf("this is a circle : %f
", (2 * math.Pi * this.radis))
}
func (this *Circle) reset() {
this.radis = 0
}
func main() {
// 接口对象
var shape Shape
// 初始化为实现该接口的结构体,并调用接口
shape = Rect{5, 10}
shape.display()
shape.reset()
shape.display() // 因为是值传递,所以实际上 reset 没改变 shape 的值,display 结果没变
// 初始化为实现该接口的另一结构体,并调用接口
shape = &Circle{5} // Circle 实现 Shape 接口用的是引用传递,所以初始化要用上 & 符号
shape.display()
shape.reset()
shape.display() // 因为是引用传递,所以 reset 改变了 shape 的值,display 的结果改变了
}
这部分有点像 Java
错误处理
go 语言没有 try...catch... 命令
而是主张把参数作为返回值,由调用者决定怎么处理
import (
"errors"
"fmt"
)
func divide(a int, b int) (int, error) {
if b == 0 {
return -1, errors.New("除数不能为0")
}
return a / b, nil
}
func main() {
var result, err = divide(6, 0)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println(result)
}
errors.New("除数不能为0") 返回一个实现了 Error() 接口的结构体 errorString
package errors
func New(text string) error {
return &errorString{text} // errorString 通过引用传递实现 error 接口,所以初始化要加上 & 符号
}
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
error 是一个接口
type error interface {
Error() string
}
errors.New("除数不能为0") 不能格式化字符串,可以改成
fmt.Errorf("除数不能为 %d", 0)
可以参考 errorString 按自己的需求,自定义自己的 struct
defer (延迟执行)
defer 语句会被延迟处理,在 defer 所属的函数即将返回时,会将 defer 语句返序执行
func main() {
fmt.Println("start")
defer fmt.Println("defer 1")
defer fmt.Println("defer 2")
fmt.Println("end")
结果是
start
end
defer 2
defer 1
可用于释放资源,比如关闭文件,释放锁等等
f1, err1 := os.Open(filename1)
defer f1.Close()
f2, err2 := os.Open(filename2)
defer f2.Close()
// 执行文件操作
甚至程序奔溃后还会执行
可以简化代码,作用就类似于 java 的 final
panic (崩溃) 和 recover (恢复)
作用类似于 throw
func func1() {
panic("unknown error occur")
fmt.Println("func1")
}
func func2() {
func1()
fmt.Println("func2")
}
func main() {
fmt.Println("start....")
func2()
fmt.Println("end")
}
结果为
start....
panic: unknown error occur
goroutine 1 [running]:
main.func1(...)
xxx/test.go:125
main.func2()
xxx/test.go:130 +0x27
main.main()
xxx/test.go:12 +0x8a
exit status 2
可以看到后面的 end 不会被执行
如果希望程序能继续执行,可以用 recover,类似于 catch,并且 recover 必须写在 defer 函数里面
func func1() {
panic("unknown error occur")
fmt.Println("func1")
}
func func2() {
defer func() {
if err := recover(); err != nil {
fmt.Printf("panic: %v
", err)
}
}()
func1()
fmt.Println("func2")
}
func main() {
fmt.Println("start....")
func2()
fmt.Println("end")
}
结果
start....
panic: unknown error occur
end
panic 语句被 func2 的 defer 函数里的 recover 捕获,这样 func2() 后面的代码能继续执行
模块管理
如下面导入三方包
import (
"github.com/petermattis/goid"
)
可能会报错
test.go:8:2: no required module provides package github.com/petermattis/goid; to add it:
go get github.com/petermattis/goid
需要执行命令按照包
go get github.com/petermattis/goid
如果报错可能需要设置代理
go env -w GOPROXY=https://goproxy.io,direct
如果报冲突错误,可以直接 set 环境变量
set GOPROXY=https://goproxy.io,direct
查看 go 的环境变量
go env
再执行 get 命令就成功了
go: downloading github.com/petermattis/goid v0.0.0-20180202154549-b0b16
15b78e5
go get: added github.com/petermattis/goid v0.0.0-20180202154549-b0b1615
b78e5
go.mod 会被自动改动,多了安装的这个模块
module calculator
go 1.17
require github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
并且会多一个 go.sum 文件
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
get 的时候可以指定版本
go get github.com/google/uuid@v1.0.0
go.mod 变成
module calculator
go 1.17
require (
github.com/google/uuid v1.0.0 // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
)
go.sum 变成
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
如果包已经存在,然后 go get 指定了新的版本,相当于升降级,取消就用 @none
如果不指定参数,就会自动扫描代码,把需要的三方包,都添加进来,相当于对每个包做 go get 操作
# 不指定参数,添加代码 import 的三方包
go get
依赖包被下载到 $GOPATH/pkg/mod/cache/download
代码被下载到 $GOPATH/pkg/mod
go.sum 和 go.mod 比多了个 hash 值,这是包的 hash 值,用于校验,防止下载的包或本地的包不正常,比如被篡改过
go.sum 用于保证开发使用的依赖,和实际使用的依赖是一致的
如果有配置 GOSUMDB 变量比如 GOSUMDB=sum.golang.org 那么 go 除了校验 go.sum 的值,还会去 GOSUMDB 做二次校验
go.mod 只记录直接依赖的版本,而 go.sum 记录所有用到的依赖的版本
go run, go build, go install
go run 编译并运行程序,不会生成可执行文件
go run main.go
如果 main.go 直接调用同目录下另一个文件的大写开头的变量或函数
package main
func main() {
Test()
}
其中 Test() 也是 package main 的
package main
func Test() {
}
这样直接运行 go run main.go 会报错
.main.go:4:5: undefined: Test
必须运行整个目录
go run .
这样就能运行了
go build 会编译并生成可执行文件(如果有 main 函数的话)
go build
go install 把编译的包放到 $GOPATH/pkg 把生成的可执行文件放到 $GOPATH/pkg
go install
和 build 比多一步包编译和安装
并发协程 goroutine
线程是操作系统调度的最小单位,通常为了并发执行请求,会启动多个线程
这种做法在请求少的时候没问题,当并发请求量很大时,就不适用,因为大量线程的频繁切换,会严重损耗性能
为了解决这种问题,引入了协程的概念
就是线程在执行一个请求中,如果遇到 IO 等操作,不是切换其他线程执行,而是可以继续执行其他就绪的请求
就是把协程放到队列,线程空闲时就挑就绪的协程执行,遇到 IO 操作,就挂起协程,然后挑其他协程执行,挂起的协程就绪再放回队列
这样即充分利用了多核 CPU 做并发操作,又避免了频繁切换线程,大大提高了性能和并发量
其他语言都是通过开发三方包来实现这样的功能
而 Go 语言天然支持协程,只需要通过 go 关键字来开启 goroutine 即可
Go 语言能控制线程调度协程
import (
"syscall"
"github.com/petermattis/goid"
)
func main() {
fmt.Printf("main tid : [%d], gid : [%d]
", GetThreadId(), goid.Get())
for i := 1; i <= 5; i++ {
go gofunc(i)
}
// 如果主程序退出,协程也会退出
time.Sleep(time.Duration(5) * time.Second)
fmt.Printf("main tid : [%d], gid : [%d]
", GetThreadId(), goid.Get())
}
func gofunc(functionId int) {
for i := 1; i <= 3; i++ {
fmt.Printf("go func [%d], tid : [%d], goid : [%d]
",
functionId, GetThreadId(), goid.Get())
time.Sleep(time.Duration(1) * time.Second)
}
}
func GetThreadId() int {
var kernel32Dll *syscall.DLL
var GetCurrentThreadIdProc *syscall.Proc
var err error
kernel32Dll, err = syscall.LoadDLL("Kernel32.dll")
if err != nil {
fmt.Printf("syscall.LoadDLL fail: %v
", err.Error())
return -1
}
// "GetCurrentThreadId" 这个名字不能改,应该是 Kernel32.dll 里的命令
GetCurrentThreadIdProc, err = kernel32Dll.FindProc("GetCurrentThreadId")
if err != nil {
fmt.Printf("kernel32Dll.FindProc fail: %v
", err.Error())
return -1
}
var pid uintptr
pid, _, err = GetCurrentThreadIdProc.Call()
return int(pid)
}
结果
main tid : [22752], gid : [1]
go func [1], tid : [22744], goid : [19]
go func [5], tid : [22752], goid : [23]
go func [4], tid : [22752], goid : [22]
go func [2], tid : [12620], goid : [20]
go func [3], tid : [14964], goid : [21]
go func [4], tid : [22744], goid : [22]
go func [5], tid : [14964], goid : [23]
go func [1], tid : [12620], goid : [19]
go func [2], tid : [15288], goid : [20]
go func [3], tid : [14964], goid : [21]
go func [3], tid : [22752], goid : [21]
go func [2], tid : [14964], goid : [20]
go func [5], tid : [30092], goid : [23]
go func [4], tid : [22744], goid : [22]
go func [1], tid : [15288], goid : [19]
main tid : [15288], gid : [1]
线程和协程不是固定的一一对应关系,每个线程都可以调用任意一个协程
main 也是一个协程,同样可以被不同的线程调用
通道 (channel)
channel 可以用于协程间的通信
// 创建一个传递 int 数据的 channel, 缓冲区为 100
ch := make(chan int, 100)
// 发送消息到 channel
ch <- msg
// 从 channel 读取消息
msg := <-ch
如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值
如果通道带缓冲区,发送方不会阻塞,除非缓冲区满了
接收方在有值可以接收之前会一直阻塞
package main
import (
"fmt"
"time"
)
var channel = make(chan int, 5)
func main() {
go producer()
go consumer()
fmt.Println("start producer and consumer")
time.Sleep(time.Duration(5) * time.Second)
}
func producer() {
for i := 0; i <= 10; i++ {
channel <- i
fmt.Printf("produce %d
", i)
}
}
func consumer() {
for i := 0; i <= 10; i++ {
msg := <-channel
fmt.Printf("consume %d
", msg)
if i%3 == 0 {
time.Sleep(time.Duration(1) * time.Second)
}
}
}
结果
start producer and consumer
produce 0
produce 1
produce 2
produce 3
produce 4
produce 5
consume 0
consume 1
consume 2
consume 3
produce 6
produce 7
produce 8
consume 4
consume 5
consume 6
produce 9
produce 10
consume 7
consume 8
consume 9
consume 10
可以在读取数据的时候判断有没有出错
// ok 是 bool 类型
v, ok := <-ch
关闭通道
close(ch)
遍历通道
package main
import (
"fmt"
"time"
)
var channel = make(chan int, 5)
func main() {
go producer()
// 遍历通道
for i:= range channel {
fmt.Printf("consume %d
", i)
}
time.Sleep(time.Duration(5) * time.Second)
}
func producer() {
for i := 0; i <= 10; i++ {
channel <- i
fmt.Printf("produce %d
", i)
}
// 关闭通道
close(channel)
}
结果
produce 0
produce 1
produce 2
produce 3
produce 4
produce 5
consume 0
consume 1
consume 2
consume 3
consume 4
consume 5
consume 6
produce 6
produce 7
produce 8
produce 9
produce 10
consume 7
consume 8
consume 9
consume 10
如果 producer 里面没有 close channel 的话,range 会阻塞等待下一个消息,可是已经没有线程会发消息了,会报死锁错误
web framework
按 github star
- gin-gonic/gin - 51k
- beego/beego - 26k
- kataras/iris - 21k
- labstack/echo - 20k
- gorilla/mux - 15k
gin 看起来比较流行
go gin
安装
go get -u github.com/gin-gonic/gin
例子
package main
import (
"encoding/json"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// group 定义的 url 有相同的前缀
v1 := router.Group("/v1")
{
v1.POST("/login", loginHandler)
// :groupId 表示这个是必须有的变量
// 可以匹配 /service/groupA
// 不能匹配 /service/ 或 /service
v1.GET("/service/:groupId", listServiceHandler)
// 请求 /service/group 的时候,不会和上一个 /service/:groupId 的 handler 冲突
v1.GET("/service/group", listGroupHandler)
// url 可以有多个可变值
v1.PUT("/service/:groupId/:serviceId", addServiceHandler)
// *serviceId 表示这个是可有可无的变量
// 可以匹配 /service/groupA/ 或 /service/groupA/serviceB
v1.DELETE("/service/:groupId/*serviceId", deleteServiceHandler)
}
// 单独定义一个 url
router.GET("/status", statusHandler)
fmt.Println("start server")
// 启动
router.Run(":8080")
}
var db = make(map[string][]string)
type User struct {
Name string `json:"name"`
Password int64 `json:"password"`
}
func loginHandler(c *gin.Context) {
// 按 json 格式获取 body 的值
json := User{}
c.BindJSON(&json)
fmt.Println(json)
// 返回状态和内容
c.String(http.StatusOK, fmt.Sprintf("
Hello %s
", json.Name))
}
func listServiceHandler(c *gin.Context) {
// 获取路径参数
group := c.Param("groupId")
services, ok := db[group]
if !ok {
c.String(http.StatusNotFound, "group not exist")
return
}
data, _ := json.Marshal(services)
c.String(http.StatusOK, string(data))
}
func listGroupHandler(c *gin.Context) {
// 获取 query 参数
includeEmptyGroup := c.Query("includeEmptyGroup")
keys := make([]string, 0)
for k := range db {
if len(db[k]) == 0 {
if includeEmptyGroup != "yes" {
continue
}
}
keys = append(keys, k)
}
data, _ := json.Marshal(keys)
c.String(http.StatusOK, string(data))
}
func addServiceHandler(c *gin.Context) {
group := c.Param("groupId")
service := c.Param("serviceId")
_, ok := db[group]
if !ok {
db[group] = make([]string, 0) // 初始化长度
}
db[group] = append(db[group], service)
c.String(http.StatusOK, "
add service successfully
")
}
func deleteServiceHandler(c *gin.Context) {
group := c.Param("groupId")
service := c.Param("serviceId")
_, ok := db[group]
if !ok {
c.String(http.StatusNotFound, "group not exist")
return
}
// 收到的 service 带有 / 符号,即 /xxxx
fmt.Printf("receive service %s!
", service)
service = service[1:]
found := false
if service == "" {
delete(db, group)
found = true
} else {
lenght := len(db[group])
for i := 0; i < lenght; i++ {
if service == db[group][i] {
db[group] = append(db[group][:i], db[group][i+1:]...) // ... 是必须的
found = true
break
}
}
}
if found {
c.String(http.StatusOK, "del successfully")
} else {
c.String(http.StatusNotFound, "service not exist")
}
}
func statusHandler(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
}
测试
> curl -X POST localhost:8080/v1/login -d '{"name":"lin", "password":123}'
Hello lin
> curl -X PUT localhost:8080/v1/service/group_A/service_1
> curl -X PUT localhost:8080/v1/service/group_B/service_2
> curl -X PUT localhost:8080/v1/service/group_B/service_3
> curl -X PUT localhost:8080/v1/service/group_C/service_4
add service successfully
> curl -X DELETE localhost:8080/v1/service/group_C/service_4
del successfully
> curl -X GET localhost:8080/v1/service/group
["group_A","group_B"]
> curl -X GET localhost:8080/v1/service/group?includeEmptyGroup=yes
["group_A","group_B","group_C"]
> curl -X GET localhost:8080/v1/service/group_A
["service_1"]
> curl -X GET localhost:8080/v1/service/group_B
["service_2","service_3"]
> curl -X GET localhost:8080/v1/service/group_C
[]
> curl -X GET localhost:8080/v1/service/group_D
group not exist
> curl -X DELETE localhost:8080/v1/service/group_D/service_4
group not exist
> curl -X DELETE localhost:8080/v1/service/group_C/service_4
service not exist
> curl -X DELETE localhost:8080/v1/service/group_B/
del successfully
> curl -X GET localhost:8080/v1/service/group
["group_A"]
> curl -X GET localhost:8080/v1/service/group?includeEmptyGroup=yes
["group_A","group_C"]
> curl -X GET localhost:8080/status
{"message":"pong"}
和 springboot 比感觉还差了一些