应用入口
- 必须是
main
包:package main
- 必须是
main
方法:func main()
- 文件名不一定是
main.go
应用入口返回值
运行 go run main.go
会输出 exit status 255
func main() {
os.Exit(-1)
}
不能使用 return
的形式
// 错误
func main() {
return -1
}
获取命令行参数
通过 os.Args
获取命令行参数
func main() {
fmt.Println(os.Args)
}
main
函数不支持传入参数
// 错误
func main(arg []string){}
变量声明
var a int = 1
var b, c int = 1, 2
- 可以换行
var ( d int = 1 e int = 2 )
a := 1
,只能在函数内部使用
常量
const (
C0 = iota
C1
C2
)
fmt.Println(C0, C1, C2) // 0 1 2
iota
第一次出现的时候值是 0
语法糖:如果每句话都差不多,可以省略
const (
C0 = iota
C1 = iota
C2 = iota
)
// 等价于
const (
C0 = iota
C1
C2
)
iota
iota
默认是0
,每行加1
- 只能在
const()
中使用
字符串
string
是值类型,默认的初始值是空字符串,不是nil
string
是只读的byte slice
,len
函数返回的是它所包含的byte
数,这个byte
和字符是不一样的string
的byte
数组可以存放任何数据
s := "\xE4\xB8\xA5"
fmt.Println(s) // 严
fmt.Println(len(s)) // 3
Unicode 和 UTF-8
字符 | “中” |
---|---|
Unicode | 0x4E2D |
UTF-8 | 0xE4B8AD |
string/[]byte | [0xE4, 0xB8, 0xAD] |
rune
可以获取字符的 Unicode
s := "中"
fmt.Println(len(s)) // 3
c := []rune(s)
fmt.Println(len(c)) // 1
fmt.Printf("中 Unicode:%x", c) // 4e2d
fmt.Printf("中 UTF-8:%x", s) // e4b8ad
高效拼接字符串 —— strings.Builder
var builder strings.Builder
for i := 0; i < 10; i++ {
builder.WriteString(strconv.Itoa(i))
}
str := builder.String()
高效拼接字符串 —— bytes.Buffer
var buf bytes.Buffer
for i := 0; i < 10; i++ {
buf.WriteString(strconv.Itoa(i))
}
str := buf.String()
数据类型
bool
string
int
、int8
、int16
、int32
、int64
uint
、uint8
、uint16
、uint32
、uint64
、uinttptr
byte
:alias foruint8
rune
:alias forint32
, represents a Unicode code pointfloat32
、float64
complex64
、complex128
类型转换
- 不允许隐式类型转换
- 别名和原有类型也不能进行隐式类型转换
type MyInt int64
func TestImplicit(t *testing.T){
var a int32 = 1
var b int64
var c MyInt
b = a // 报错
b = int64(a)
c = b // 报错
c = MyInt(b)
}
位运算
&^
按位清零
1 &^ 0
->1
1 &^ 1
->0
0 &^ 1
->0
0 &^ 0
->0
如果后面一位是 1
输出为 1
;如果后面一位是 0
前面一位是啥,输入就是啥
const (
Readable = 1 << iota
Writeable
Executable
)
func TestBitClear(t *testing.T){
a := 7
fmt.Println(a & Readable == Readable) // true
a = a &^ Readable
fmt.Println(a & Readable == Readable) // false
}
for 循环
第一种:
i := 1
for i <= 3 {
fmt.Println(i)
i++
}
第二种:
for j := 1; j <= 3; j++ {
fmt.Println(j)
}
第三种:
k := 1
for {
if k > 3 {
break
}
fmt.Println(k)
k++
}
第四种:数组/map
遍历
for _, e : range arr {
fmt.Println(e)
}
if/switch
if
num := 9
只能在 if
语句中使用
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
switch
- 不要加
break
- 一个
case
可以多个值,用逗号隔开 - 如果要贯穿,可以使用
fallthrough
switch time.Now().Weekday() {
case time.Saturday, time.Sunday:
fmt.Println("It's the weekend")
default:
fmt.Println("It's a weekday")
}
函数
方式一:
func f1(x, y int)(int, int){
return x + y, x * y
}
方式二:
使用这种方式,可以直接返回值,如果在 return
后面再指定其他返回值,会覆盖掉 sum
和 product
func f2(x, y int) (sum, product int) {
sum = x + y
product = x * y
return
}
使用:
a, b := f1(1, 2)
c, d := f2(2, 3)
函数是一等公民
- 可以有多个返回值
- 所有参数都是值传递:
slice
,map
,channel
会有传引用的错觉 - 函数可以作为变量的值
- 函数可以作为参数和返回值
结构体
定义
type User struct {
Username string
Age int
}
实例创建
u1 := User{ UserName: "uccs" , Age: 16 }
u2 := User{ "uccs", 16 }
u3 := new(User)
u3.UserName = "uccs"
u3.Age = 16
第三种方式返回的是指针,相当于 &User{}
fmt.Println("%T", u3) // *encap.User
fmt.Println("%T", u1) // encap.User
把结构体当成参数
// 传递的是值,不是地址
func modify(u User){
u.Age = 12
}
func main(){
u := User{ "uccs", 2 }
modify(u)
fmt.Println(u) // {"uccs", 2}
}
传递地址
用于类型时:用 *
,用于值时:用 &
(*u
表示 u
对应的值)
// 传递的是地址,不是值
func modify(u *User){
// *u 表示取地址的值
// (*u).Age = 22
// 下面的是语法糖,等价于上面的
u.Age = 22
}
func main(){
u := User{ "uccs", 2 }
modify(&u)
fmt.Println(u) // {"uccs", 22}
}
传值和传指针的差异
在实例方法被调用时,实例的成员会进行复制
func modify(u User){
fmt.Printf("%x\n", &u.Age) // c0000100b8
u.Age = 12
}
func main(){
u := User{ "uccs", 2 }
modify(u)
fmt.Printf("%x ", &u.Age) // c0000100a0
}
在实例方法被调用时,可以避免内存拷贝
func modify(u *User){
fmt.Printf("%x\n", &u.Age) // c0000100a0
u.Age = 22
}
func main(){
u := User{ "uccs", 2 }
modify(&u)
fmt.Printf("%x\n", &u.Age) // c0000100a0
}
支持 label
type User struct {
Username string `json:"username"`
Age int `json:"age"`
}
func main() {
u := User{Username: "astak", Age: 16}
bytes, error := json.Marshal(u)
if error != nil {
fmt.Println(error)
}
fmt.Println(string(bytes)) // {"username":"astak","age":16}
}
数组
定长
a := [3]int{1, 2, 3}
// 可以省略长度
a := [...]int{1, 2, 3}
数组的比较
长度相同的数组可以进行比较
a := [...]int{1, 2, 3}
b := [...]int{1, 2, 3}
c := [...]int{1, 2}
fmt.Println(a == b) // true
fmt.Println(a == c) // 报错
数组截取
a := [...]int{1, 2, 3, 4, 5}
a[1:2] // 2
a[1:3] // 2, 3
a[1:len(a)] // 2, 3, 4, 5
a[1:] // 2, 3, 4, 5
a[:3] // 1, 2, 3
切片
字面量声明
array := [...]int{1, 2, 3}
slice := []int{1, 2, 3}
taSlice := reflect.TypeOf(slice)
taArray := reflect.TypeOf(array)
fmt.Println(taSlice.Kind()) // slice
fmt.Println(taArray.Kind()) // array
开辟一块内存
语法:make([]type, len, cap)
s := make([]int, 3)
遍历
s := []int{1, 2, 3, 4}
for i, v := range s {
fmt.Println(i, v)
}
追加
s := []int{1, 2, 3, 4}
s = append(s, 5)
slice 的特点
type slice struct {
array unsafe.Pointer
len int
cap int
}
array
指向底层数组的指针len
长度cap
容量
追加内容时,如果 cap
不够,就复制到新的更长的数组,扩容时 slice
对应的结构体会被复用
指针
将 *
放在类型前面,表示声明一个指针类型,将 *
放在变量前面,表示取这个变量的值
slice
和map
,go
没有为它们提供自动的指针操作,因为他们本身就是引用类型
i := 1
iPtr := &i
,iPtr
的值是i
的地址iPtr := *&i
,iPtr
的值是i
的内容
func zeroValue(value int) int {
value = 0
return value
}
func zeroPointer(ptr *int) {
(*ptr) = 0
}
func main() {
i := 1
iPtr := &i
zeroValue(i)
fmt.Println(i)
zeroPointer(iPtr)
fmt.Println(*iPtr)
}
用指针获取 j 的值
i := 1
j := 2
iPtr := &i
println(i, j, &i, &j)
println(*(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(iPtr)) - unsafe.Sizeof(i))))
用指针遍历 slice
vals := []int{10, 20, 30, 40}
start := unsafe.Pointer(&vals[0])
// 每一个元素的长度,int 长度都一样
size := unsafe.Sizeof(int(0))
for i := 0; i < len(vals); i++ {
item := *(*int)(unsafe.Pointer(uintptr(start) + size*uintptr(i)))
fmt.Println(item)
}
将指针作为函数的参数
go
的函数和方法都是按值传递参数的,所以函数总是操作参数的副本
当指针作为参数传递时,函数将接收传入的内存地址的副本,之后函数可以通过解引内存地址来修改指针指向的值
如果一种类型的某些方法需要用到指针作为接收者,那么这种类型的所有方法都应该用指针作为接收者
结构体作为参数
growUp
方法要接收指针类型的参数,否则没法操作 person
的属性
type person struct {
name string
age int
}
func (p *person) growUp() {
p.age++
}
func main() {
terry := person{
name: "terry",
age: 18,
}
terry.growUp()
fmt.Println(terry)
nathan := &person{
name: "nathan",
age: 18,
}
nathan.growUp()
fmt.Println(nathan)
}
切片作为参数
切片作为参数时,传指针和传值的区别,都会修改原切片的值(map
也是一样)
type MySlice []int
func (s *MySlice) modifySlice() {
(*s)[1] = 100
}
func (s MySlice) modifySlice2() {
s[2] = 200
}
func main() {
s := MySlice{1, 2, 3, 4, 5}
s.modifySlice()
fmt.Println(s)
s.modifySlice2()
fmt.Println(s)
}
获取结构体中字段的指针
&
操作符可以获得结构体的内存地址,还可以获得结构体中指定字段的内存地址
type stats struct {
level int
endurance, health int
}
func levelUp(s *stats) {
s.level++
s.endurance = 42
s.health = 5
}
type character struct {
name string
stats stats
}
func main(){
player := character{name: "Matthias"}
levelUp(&player.stats)
fmt.Println(player.stats)
}
map
语法:
s1 := make(map[string]int, cap)
s1 := map[string]int{ "apple": 2, "orange": 2 }
s1 := map[string]int{}
s1 := make(map[string]int, 10)
fmt.Println(s1)
myMap := make(map[string]int)
var myMap map[string]int
检测 key 是否存在
map
在访问的 key
不存在时,仍会返回零值,不能通过返回 nil
来判断元素是否存在
if v, hasKey := myMap["apple"]; hasKey {
fmt.Println("key apple's is %s", v)
}
关联
将 celsius
和 kelvin
关联起来了
预声明的类型是不能进行关联的,比如
int
、float64
等
type kelvin float64
type celsius float64
func kelvinToCelsius(k kelvin) celsius {
return celsius(k - 273.15)
}
func (k kelvin) celsius() celsius {
return celsius(k - 273.15)
}
func main() {
var k kelvin = 294.0
var c celsius
c = kelvinToCelsius(k)
c = k.celsius()
fmt.Println(c)
}
序列化
type Person struct {
Name string
age int
}
p1 := Person{"uccs", 16}
xml 序列化和反序列化
序列化
xml.Marshal
输出的文本是没有格式的
xml.MarshalIndent
输出的文本是有格式的,xml.MarshalIndent(p1, prefix, suffix)
p1 := Person{"uccs", 16}
var data []byte
var err error
if data, err = xml.Marshal(p1); err != nil {
fmt.Println(err)
return
}
fmt.Println(string(data))
反序列化
p2 := new(Person)
if err = xml.Unmarshal(data, p2); err != nil {
fmt.Println(err)
return
}
fmt.Println(p2)
变成属性
type Person struct {
Name string `xml:"name,attr"`
Age int
}
json 序列化和反序列化
序列化
p1 := Person{"uccs", 16}
var data []byte
var err error
if data, err = json.Marshal(p1); err != nil {
fmt.Println(err)
return
}
fmt.Println(string(data))
反序列化
p2 := new(Person)
if err = json.Unmarshal(data, p2); err != nil {
fmt.Println(err)
return
}
fmt.Println(p2)
获取命令行参数
fmt.Println(os.Args)
自定义参数
自定义参数 -method
和 -value
methodStr := flag.String("method", "default", "method of sample")
valuePtr := flag.Int("value", -1, "value of sample")
flag.Parse()
fmt.Println(*methodStr, *valuePtr)
自定义参数内容
var method string
var value int
flag.StringVar(&method, "method", "default", "method of sample")
flag.IntVar(&value, "value", -1, "value of sample")
flag.Parse()
fmt.Println(method, value)
测试
- 源码文件以
_test
结尾:xxx_test.go
- 测试方法名以
Test
开头:func TestXXX(t *testing.T){...}
benchmark
在命令行中执行 go test -bench=.
,更详细的信息可以使用 go test -bench=. -benchmem
func Benchmark(b testing.B){
// 与性能测试无关的代码
b.ResetTimer()
for i := 0; i < b.N; i++ {
// 测试代码
}
b.StopTimer()
// 与性能测试无关的代码
}
错误处理
error
类型实现了error
接口type error interface { Error() string }
- 通过
errors.New
快速创建错误实例errors.New("n must be in the range [0, 10]")
- 类似
java
中的try...catch(err)...
func main(){ defer func() { if err := recover(); err != nil { fmt.Println("recovered from", err) } }() panic(errors.New("Something wrong!")) }
退出
panic
- 用于不可恢复的错误
- 退出前会执行
defer
指定的内容
os.Exit
- 退出时不会调用
defer
执行的函数 - 退出时不输出当前调用栈信息
- 退出时不会调用
package
- 以首字母大写来表明可被包外代码访问
- 代码的
package
可以和所在目录不一致 - 同一目录里的
Go
代码的package
要保持一致
init
- 在
main
被执行前,所有依赖的package
的init
方法都会被执行 - 不同包的
init
函数按照包导入的依赖关系决定执行顺序 - 每个包可以有多个
init
函数 - 包的每个源文件也可以有多个
init
函数,这点比较特殊
下载
- 通过
go get
获取远程依赖go get -u
强制从网络更新远程依赖
- 注意代码在
GitHub
上的组织形式,以适应go get
- 直接以代码路径开始,不要有
src
- 直接以代码路径开始,不要有
Context
- 根
Content
:通过context.Background()
创建 - 子
Context
:通过context.WithCancel(parentContext)
创建ctx, cancel := context.WithCancel(context.Background())
- 当前
Context
被取消时,基于它的子Context
都会被取消 - 接收取消通知
<-ctx.Done()
func isCancelled(ctx context.Context) bool {
select {
case <-ctx.Done():
return true
default:
return false
}
}
func TestCloseChannel(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
for i := 0; i < 5; i++ {
go func(i int, ctx context.Context) {
for {
if isCancelled(ctx) {
break
}
time.Sleep(time.Millisecond * 5)
}
fmt.Println(i, "cancelled")
}(i, ctx)
}
cancel()
time.Sleep(time.Second * 1)
}
输出
fmt.Printf("%x", 17)
输出16
进制fmt.Printf("%b", 17)
输出2
进制fmt.Printf("%T", 17)
输出类型fmt.Printf("%c", 'a')
输出字符fmt.Printf("%q", "\"golang\"")
输出带引号的字符串:"\"golang\""
fmt.Printf("%v", location{lat: 4, lon: 5})
,输出没有key
fmt.Printf("%+v", location{lat: 4, lon: 5})
,输出有key
fmt.Printf("%#v", location{lat: 4, lon: 5})
,输出key
和value
,并且带有类型fmt.Printf("Name: value(%[1]v), Type(%[1]T)\n", "uccs")
,表示都是用第一个值
组合和转发
report
是由temperature
和location
组合得到的average
方法接收的参数是temperature
,调用average
方法就有两种方式:t.average()
report.temperature.average()
type report struct {
sol int
temperature temperature
location location
}
type temperature struct {
high, low celsius
}
type location struct {
lat, long float64
}
type celsius float64
func (t temperature) average() celsius {
return (t.high + t.low) / 2
}
func main(){
bradbury := location{-4.5895, 137.4417}
t := temperature{high: -1.0, low: -78.0}
report := report{sol: 15, temperature: t, location: bradbury}
fmt.Printf("%+v", report)
fmt.Printf("%v", t)
}
转发
r.report.average()
调用的是 temperature
的 average
方法,这就实现了转发
func (r report) average() celsius {
return r.temperature.average()
}
fmt.Printf("%v", report.average())
嵌入
嵌入是之使用类型,不使用名称,这样就可以直接调用 temperature
的 average
方法
使用嵌入,go
会自动将 temperature
和 location
的方法都加到 report
中
调用 average
方法就有两种方式:
report.temperature.average()
report.average()
type report struct {
sol int
temperature
location
}
func (t temperature) average() celsius {
return (t.high + t.low) / 2
}
fmt.Printf("%v", report.temperature.average())
fmt.Printf("%v", report.average())
转发冲突
如果 report
中有 temperature
和 location
都有 average
方法,就会出现转发冲突
使用 report.average
就会报错,不使用就不会报错
func (t temperature) average() celsius {
return (t.high + t.low) / 2
}
func (l location) average() celsius {
return 1
}
fmt.Printf("%v", report.average()) // 使用报错,不使用不会报错
如果想要调用使用 report.average()
,就需要再 report
上定义一个 average
方法
func (r report) average() celsius {
return r.temperature.average()
}
接口
任何类型的任何值,只要满足了接口的方法,就是实现了接口
声明变量 t
,它的类型是一个接口,这个接口有一个方法 talk
,返回值是 string
martian
实现了 talk
方法,所以 t
就有 talk
方法
var t interface {
talk() string
}
type martian struct{}
func (m martian) talk() string {
return "nack nack"
}
func main() {
t := martian{}
fmt.Println(t.talk())
}
声明类型 talker
,它的类型是一个接口,这个接口有一个方法 talk
,返回值是 string
函数 shout
接收 talker
类型的参数
martian
实现了 talk
方法,所以 t
就有 talk
方法
type talker interface {
talk() string
}
type martian struct {}
func (m martian) talk() string {
return "nack nack"
}
func shout(t talker) {
louder := strings.ToUpper(t.talk())
fmt.Println(louder)
}
func main() {
t := martian{}
shout(t)
// 或者这样使用
// fmt.Println(t.talk())
}
fmt.Println() 就是利用这个特性
只要实现了 String()
方法,就可以使用 fmt.Println()
输出
type User struct{ Name string }
func (u User) String() string {
return "我叫 xxx"
}
func main() {
u := User{"uccs"}
fmt.Println(u) // 我叫 xxx
}
nil
nil 会导致 panic
如果指针没有明确的指向,那么程序将无法对其实施解引用;如果尝试解引用一个 nil
指针将导致程序崩溃
var nowhere *int
fmt.Println(nowhere) // nil
// 对值为 nil 的指针进行解引用操作,会引起 panic
fmt.Println(*nowhere)
保护方法
值为 nil
的接收者和之为 nil
的参数在行为上并没有区别,所以在接收者为 nil
的情况下,也会继续调用方法
type person struct{
age int
}
func (p *person) birthday(){
// 这里会报错
p.age++
}
func main(){
var nobody *person
fmt.Println(nobody)
// 不会报错,还是会调用 birthday 方法
nobody.birthday()
}
nil 函数值
当变量被声明为函数类型时,它的默认值是 nil
var fn func(a, b int) int
fmt.Println(fn == nil) // true
检查函数值是否为 nil
,并在需要时提供默认行为
func sortStrings(s []string, less func(i, j int) bool) {
if less == nil {
les = func(i, j int) bool { return s[i] - s[j] }
}
sort.slice(s, less)
}
func main() {
food := []string{"onion", "carrot", "celery"}
sortStrings(food, nil)
fmt.Println(food) // ["carrot", "celery", "onion"]
}
nil slice
如果 slice
在声明之后没有使用复合字面值或内置的 make
函数进行初始化,那么它的值为 nil
range
、len
、append
等都可以正常处理值为 nil
的 slice
var soup []string
fmt.Println(soup == nil) // true
// 这段代码不会走,因为 slice 的值为 nil,所以不会执行 for 循环
for _, ingredient := range soup {
fmt.Println(ingredient)
}
fmt.Println(len(soup)) //
soup = append(soup, "onion")
fmt.Println(soup) // ["onion"]
空的 slice
和值为 nil
的 slice
不相等,但它们可以替换使用
nil map
和 slice
一样,如果 map
在声明后没有使用复合字面值或内置的 make
函数进行初始化,那么它的值为 nil
var soup map[string]int
fmt.Println(soup == nil) // true
// 不会报错,ok 为 false
measurement, ok := soup["onion"]
if ok {
fmt.Println(measurement)
}
// 这段代码不会走,因为 map 的值为 nil,所以不会执行 for 循环
for ingredient, amount := range soup {
fmt.Println(ingredient, amount)
}
nil 接口
- 声明为接口类型的变量在未被初始化时,其值为
nil
- 对于一个未被赋值的接口变量来说,它的接口类型和值都是
nil
,并且变量本身也等于nil
var v interface{}
// 类型为 nil
// 值为 nil
// true
fmt.Println("%T %v %v\n", v, v, v == nil)
- 当接口类型的变量被赋值后,接口就会在内部执行该变量的类型和值
- 在
go
中,接口类型的变量只有在类型和值都为nil
时才等于nil
- 即使接口变量的值仍为
nil
,但只要它的类型不为nil
,那么它就不等于nil
- 即使接口变量的值仍为
var v interface{}
// v 类型为 nil
// v 值为 nil
// true
fmt.Printf("%T %v %v\n", v, v, v == nil)
var p *int
v = p
// v 类型为 *int
// v 值为 nil
// false,虽然值为 nil,但类型不为 nil,所以这里为 false
fmt.Printf("%T %v %v\n", v, v, v == nil)
- 接口变量内部表示
fmt.Printf("%#v\n", v)
nil 另一种用法
type number struct{
value int
valid bool
}
func newNumber(v int) number{
return number{value: v, valid: true}
}
func (n number) String() string{
if !n.valid {
return "not set"
}
return fmt.Sprint("%d", n.value)
}
func main(){
n := newNumber(42)
fmt.Println(n) // 42
e := number{}
fmt.Println(e) // not set
}
goroutine
在 go
中,独立的任务叫做 goroutine
- 虽然
goroutine
与其他语言中的协程、进程、线程等概念有些相似,但它们之间并不是完全相同 goroutine
创建效率是非常高的go
能直截了当的协同多个并发concurrent
操作
在 go
中,无需修改现有顺序式的代码,就可以通过 goroutine
以并发的方式运行任意数量的任务
在 main
函数返回时,该程序运行的所有 goroutine
都会立即停止(不论有没有运行完)
需要注意的是即使已经停止等待
goroutine
,但只要main
函数还没有返回,仍在运行的goroutine
将会继续占用内容
func sleepyGopher() {
time.Sleep(3 * time.Second)
fmt.Println("... snore ...")
}
func main() {
go sleepyGopher() // 分支线路
time.Sleep(4*time.Second) // 主干路
}
不止一个 goroutine
每次使用 go
关键字都会产生一个新的goroutine
表面上看,goroutine
似乎在同时运行,但由于计算机处理单元有限,其实技术上来说,这些goroutine
不是真的在同时运行
- 计算机处理器会使用分时技术,在多个
goroutine
上轮流花费一些时间 - 在使用
goroutine
时,各个goroutine
的执行顺序无法确定
func sleepyGopher(i int) {
time.Sleep(3 * time.Second)
fmt.Println("... snore ...", i) // 这里输出的 i 不一定是按照顺序输出的
}
func main() {
for i := 0; i < 5; i++ {
go sleepyGopher(i) // 分支线路
}
time.Sleep(4*time.Second) // 主干路
}
channel
- 通过
channel
可以在多个goroutine
之间安全的传值 - 通道可以用作变量、函数参数、结构体字段
- 创建通道用
make
函数,并指定其传输数据的类型:c := make(chan int)
- 接收/发送
- 发送操作会等待直到另一个
goroutine
尝试对该通道进行接收操作为止- 执行发送操作的
goroutine
在等待期间将无法执行其他操作 - 未在等待通道操作的
goroutine
仍然可以继续自由的运行
- 执行发送操作的
- 发送操作会等待直到另一个
- 执行接收操作的
goroutine
将等待直到另一个goroutine
尝试向该通道进行发送操作为止
// 1. 创建 chan,需要指定类型
ch := make(chan string)
// 2. 启动 go 程
go func() {
time.Sleep(1 * time.Second)
// 3. 往 chan 发送一个值
ch <- "ping"
}()
// 4. 从 chan 接收一个值
msg := <-ch
fmt.Println(msg)
阻塞和死锁
- 当
goroutine
在等待通道发送或接收时,这种情况称为阻塞 - 除了
goroutine
本身占有少量的内存外,被阻塞的goroutine
不会占用任何其他资源goroutine
静静的停在那里,等待导致被阻塞的事情来解除阻塞
- 当一个或多个
goroutine
因为某些永远无法发声的事情被阻塞时,这种情况就是死锁,出现死锁的程序通常会崩溃或者挂起
// 死锁,这段程序没有给 chan 传值
func main(){
c := make(chan int)
<- c
}
// 解锁
func main(){
c := make(chan int)
go func(){ c <- 1 }() // 给 chan 传值
<- c
}
关闭通道
go
允许在没有值可供发送的情况下通过close
函数关闭通道- 通道被关闭后无法写入任何值,如果尝试写入将引发
panic
- 尝试读取被关闭的通道会获得与通道类型对应的零值
如果在循环里读取一个已关闭的通道,并没有检查通道是否关闭,那么该循环可能会一直运转下去,消耗系统资源
检查通道是否关闭:v, ok := <-ch
,如果 ok
为 false
,那么通道已经关闭
方法一
sourceGopher
函数负责向downstream
通道发送数据- 如果没有数据了,就向通道中发送空字符串
filterGopher
函数负责过滤upstream
通道中的数据- 用
item
接收upstream
通道中的数据 - 无限循环,如果
item
为""
跳出循环 - 过滤掉通道中包含
"bad"
的字符串后赋值给downstream
- 用
printGopher
函数负责打印upstream
- 用
v
接收upstream
通道中的数据 - 无限循环,如果
item
为""
跳出循环 - 打印
v
- 用
main
函数负责调用sourceGopher
、filterGopher
、printGopher
函数
func sourceGopher(downstream chan string) {
for _, v := range []string{"hello world", "a bad apple", "goodbye all"} {
downstream <- v
}
downstream <- ""
}
func filterGopher(upstream, downstream chan string) {
for {
item := <-upstream
if item == "" {
downstream <- ""
return
}
if !strings.Contains(item, "bad") {
downstream <- item
}
}
}
func printGopher(upstream chan string) {
for {
v := <-upstream
if v == "" {
return
}
fmt.Println(v)
}
}
func main() {
c0 := make(chan string)
c1 := make(chan string)
go sourceGopher(c0)
go filterGopher(c0, c1)
printGopher(c1)
}
方法二
方法二和方法一的区别是:go
提供了 close
关闭通道
- 在接收通道的值时,通过判断第二个参数是否为
false
来判断通道是否关闭
func sourceGopher(downstream chan string) {
for _, v := range []string{"hello world", "a bad apple", "goodbye all"} {
downstream <- v
}
close(downstream)
}
func filterGopher(upstream, downstream chan string) {
for {
v, ok := <-upstream
if !ok {
close(downstream)
return
}
if !strings.Contains(v, "bad") {
downstream <- v
}
}
}
func printGopher(upstream chan string) {
for {
v, ok := <-upstream
if !ok {
return
}
fmt.Println(v)
}
}
func main() {
c0 := make(chan string)
c1 := make(chan string)
go sourceGopher(c0)
go filterGopher(c0, c1)
printGopher(c1)
}
方法三
从通道中取值,需要判断传值是否结束,所以 go
提供了 range
关键字,可以遍历通道,当通道关闭时,range
会自动退出循环
func sourceGopher(downstream chan string) {
for _, v := range []string{"hello world", "a bad apple", "goodbye all"} {
downstream <- v
}
close(downstream)
}
func filterGopher(upstream, downstream chan string) {
for item := range upstream {
if !strings.Contains(item, "bad") {
downstream <- item
}
}
close(downstream)
}
func printGopher(upstream chan string) {
for v := range upstream {
fmt.Println(v)
}
}
func main() {
c0 := make(chan string)
c1 := make(chan string)
go sourceGopher(c0)
go filterGopher(c0, c1)
printGopher(c1)
}
超时控制
当代码运行到 select
时,只要有一个 case
能够执行,就会执行;如果都是阻塞状态,有 default
就会执行 default
,如果没有,就会阻塞
time.After
函数返回一个通道,该通道在指定事件后会接收到一个值(发送该值的 goroutine
是 go
运行时的一部分)
利用这种机制就可以实现超时控制:
AsyncService
如果超时了,就不再等待了,直接返回 time out
func TestSelect(t *testing.T) {
select {
case ret := <-AsyncService():
t.Log(ret)
case <-time.After(time.Millisecond * 100):
t.Log("time out")
}
}
select
语句在不包含任何case
的情况下将永远等下去
nil 通道
- 如果不使用
make
初始化通道,那么通道变量的值就是nil
(零值) - 对
nil
通道进行发送或接收不会引起panic
,但会导致永久阻塞 - 对
nil
通道执行close
函数,会引起panic
nil
通道的用处:- 对于包含
select
语句的循环,如果不希望每次循环都等待select
所涉及的所有通道,那么可以先将某些通道设为nil
,等待发送值准备就绪之后,再将通道变成一个非nil
值并执行发送操作
- 对于包含
并发控制
并发机制控制 —— 锁 [Lock]
如果不加锁,就出现了线程不安全操作,线程安全的情况下输出的是 5000
,和我们预期是一样的
func TestCounterThreadSafe(t *testing.T) {
var mut sync.Mutex
counter := 0
for i := 0; i < 5000; i++ {
go func() {
defer func() { // 释放锁
mut.Unlock()
}()
mut.Lock() // 加锁
counter++
}()
}
time.Sleep(1 * time.Second)
t.Logf("counter = %d", counter) // "counter = 5000"
}
并发机制控制 —— WaitGroup
func TestCounterWaitGroup(t *testing.T) {
var mut sync.Mutex
var wg sync.WaitGroup
counter := 0
for i := 0; i < 5000; i++ {
wg.Add(1) // 每启动一个协程加一次
go func() {
defer func() {
mut.Unlock()
}()
mut.Lock()
counter++
wg.Done() // 有一个等待的已经完成了
}()
}
wg.Wait()
t.Logf("counter = %d", counter)
}
WaitGroup
比 time.Sleep
好的一点在于:WaitGroup
等待了每一个协程执行的执行,而 time.Sleep
只是预估了一个时间,实际多久并不知道
并发机制控制 —— CSP
串行
func service() string {
time.Sleep(time.Millisecond * 50)
return "Done"
}
func otherTask() {
fmt.Println("working on something else")
time.Sleep(time.Millisecond * 100)
fmt.Println("Task in done.")
}
func TestService(t *testing.T) {
fmt.Println(service())
otherTask()
}
这里是按照顺序输出的(串行):
Done
working on something else
Task in done.
使用 chan
service
调用时,启动另一个协程去运行,不阻塞当前的协程- 返回结果时,返回
chan
,外面需要结果的话,再去读取chan
由于没有使用 buffer
,所以这里会阻塞,直到 retCh
有值才会继续往下执行
也就是说这里阻塞了 service
的调用的协程
func service() string {
time.Sleep(time.Millisecond * 50)
return "Done"
}
func otherTask() {
fmt.Println("working on something else")
time.Sleep(time.Millisecond * 100)
fmt.Println("Task in done.")
}
func AsyncService() chan string {
retCh := make(chan string)
go func() {
ret := service()
fmt.Println("returned result.")
retCh <- ret // 会阻塞
fmt.Println("service exited.") // retCh 有值才会执行
}()
return retCh
}
func TestAsyncService(t *testing.T) {
retCh := AsyncService()
otherTask()
fmt.Println(<-retCh)
time.Sleep(time.Second * 1)
}
输出:
working on something else
returned result.
Task in done.
Done
service exited.
使用 chan buffer
释放 service
的调用的协程,让其继续往下执行
func service() string {
time.Sleep(time.Millisecond * 50)
return "Done"
}
func otherTask() {
fmt.Println("working on something else")
time.Sleep(time.Millisecond * 100)
fmt.Println("Task in done.")
}
func TestService(t *testing.T) {
fmt.Println(service())
otherTask()
}
func AsyncService() chan string {
retCh := make(chan string, 1)
go func() {
ret := service()
fmt.Println("returned result.")
retCh <- ret
fmt.Println("service exited.")
}()
return retCh
}
func TestAsynService(t *testing.T) {
retCh := AsyncService()
otherTask()
fmt.Println(<-retCh)
time.Sleep(time.Second * 1)
}
输出:
working on something else
returned result.
service exited.
Task in done.
Done
单例
type Singleton struct{}
var singleInstance *Singleton
var once sync.Once
func GetSingletonObj() *Singleton {
once.Do(func() {
fmt.Println("Create Obj")
singleInstance = new(Singleton)
})
return singleInstance
}
func TestGetSingletonObj(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
obj := GetSingletonObj()
fmt.Printf("%x\n", unsafe.Pointer(obj))
wg.Done()
}()
}
wg.Wait()
}
完成一个任务和完成所有任务
这里只接收了第一个协程的结果,只有第一个协程被释放了,通过 runtime.NumGoroutine()
可以看到
func runTask(i int) string {
time.Sleep(10 * time.Millisecond)
return fmt.Sprintf("The result is from %d", i)
}
func FirstResponse() string {
numOfRunner := 10
ch := make(chan string)
for i := 0; i < numOfRunner; i++ {
go func(i int) {
ret := runTask(i)
ch <- ret
}(i)
}
return <-ch
}
func TestResponse(t *testing.T) {
fmt.Println("Before: ", runtime.NumGoroutine()) // 2
fmt.Println(FirstResponse()) // 只返回了第一个协程的结果
time.Sleep(time.Second)
fmt.Println("After: ", runtime.NumGoroutine()) // 11
}
接收了所有协程的结果,协程就会被释放,通过 runtime.NumGoroutine()
可以看到
func runTask(i int) string {
time.Sleep(10 * time.Millisecond)
return fmt.Sprintf("The result is from %d", i)
}
func AllResponse() string {
numOfRunner := 10
ch := make(chan string)
for i := 0; i < numOfRunner; i++ {
go func(i int) {
ret := runTask(i)
ch <- ret
}(i)
}
finalRet := ""
for j := 0; j < numOfRunner; j++ {
finalRet += <-ch + "\n"
}
return finalRet
}
func TestResponse(t *testing.T) {
fmt.Println("Before: ", runtime.NumGoroutine()) // 2
fmt.Println(AllResponse()) // "......"
time.Sleep(time.Second)
fmt.Println("After: ", runtime.NumGoroutine()) // 2
}
反射
reflect.TypeOf vs reflect.ValueOf
reflect.TypeOf
返回的是类型(reflect.Type
)reflect.ValueOf
返回的是值(reflect.Value
)- 可以从
reflect.Value
获取类型 - 通过
kind
来判断类型
func CheckType(v interface{}) {
t := reflect.TypeOf(v)
switch t.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64:
fmt.Println("integer")
case reflect.Float32, reflect.Float64:
fmt.Println("float")
default:
fmt.Println("unknown")
}
}
func TestBasicType(t *testing.T) {
var f int = 12
CheckType(f)
}
func TestTypeAndValue(t *testing.T) {
var f int64 = 12
t.Log(reflect.TypeOf(f), reflect.ValueOf(f))
t.Log(reflect.ValueOf(f).Type())
}
利用反射编写灵活的代码
- 按名字访问结构的成员:
reflect.ValueOf(*e).FieldByName("Name")
- 按名字访问结构的方法:
reflect.ValueOf(e).MethodByName("UpdateAge").Call([]reflect.Value{reflect.ValueOf(xxx)})
reflect.ValueOf
和 reflect.TypeOf
都有 FieldByName
方法,区别:
reflect.ValueOf
返回一个值reflect.TypeOf
返回两个值
type Employee struct {
EmployeeID string
Name string `format:"normal"`
Age int
}
func (e *Employee) UpdateAge(newVal int) {
e.Age = newVal
}
type Customer struct {
CookieID string
Name string
Age int
}
func TestInvokeByName(t *testing.T) {
e := &Employee{"1", "Mike", 30}
fmt.Printf("Name: value(%[1]v), Type(%[1]T)\n", reflect.ValueOf(*e).FieldByName("Name"))
if nameField, ok := reflect.TypeOf(*e).FieldByName("Name"); !ok {
t.Error("Failed to get 'Name' field.")
} else {
fmt.Println("Tag:format", nameField.Tag.Get("format"))
}
reflect.ValueOf(e).MethodByName("UpdateAge").Call([]reflect.Value{reflect.ValueOf(1)})
fmt.Println("Updated Age:", e)
}
比较 map 和 slice
两个 map
是不能直接进行比较的,使用 reflect.DeepEqual
来进行比较(slice
同理)
func TestDeepEqual(t *testing.T) {
a := map[int]string{1: "one", 2: "two", 3: "three"}
b := map[int]string{1: "one", 2: "two", 3: "three"}
fmt.Println(a == b) // 报错
fmt.Println(reflect.DeepEqual(a, b))
s1 := []int{1, 2, 3}
s2 := []int{1, 2, 3}
s3 := []int{2, 3, 1}
fmt.Println(reflect.DeepEqual(s1, s2))
fmt.Println(reflect.DeepEqual(s1, s3))
}