高级语法

defer

defer

延迟函数调用,defer 后边会接一个函数,但该函数不会立刻被执行,而是等到包含它的程序返回时(包含它的函数执行了 return 语句、运行到函数结尾自动返回、对应的 goroutine panic),defer 函数才会被执行。

通常用于资源释放、打印日志、异常捕获等。

使用 defer 语句进行延迟调用,用来关闭或释放资源

代码:

package main

import (
	"fmt"
)

func main() {
	// defer 语句的执行是按调用 defer 语句的倒序执行。
	defer func() {
		fmt.Println("first") // 5.  first
	}()

	defer func() {
		fmt.Println("second") // 4.  second
	}()

	fmt.Println("done") // 1. done

	fmt.Println(triple(4)) //3. 12
}

func double(x int) (result int) {
	// defer 语句在 return 语句之后执行
	defer func() {
		fmt.Printf("double(%d) = %d\n", x, result) //2. double(4) = 8
	}()

	return x + x
}

func triple(x int) (result int) {
	defer func() {
		result += x
	}()

	return double(x)
}

panic和recover

使用 panic 和 recover 来抛出错误和恢复

使用 panic 一般有两种情况:

1.程序遇到无法执行的错误时,主动调用 panic 结束运行;

2.在调试程序时,主动调用 panic 结束运行,根据抛出的错误信息来定位问题。

为了程序的健壮性,可以使用 recover 捕获错误,恢复程序运行。

一般情况下,在程序里记录错误日志,就可以帮助我们在碰到异常时快速定位问题。

但还有一些错误比较严重的,比如数组越界访问,程序会主动调用 panic 来抛出异常,然后程序退出。

如果不想程序退出的话,可以使用 recover 函数来捕获并恢复。

感觉挺不好理解的,但仔细想想其实和 try-catch 也没什么区别。

代码:

package main

import (
	"fmt"
)

func main() {
	G()
}

func G() {
	defer func() {
		fmt.Println("c") //4.  c
	}()
	F()
	fmt.Println("继续执行") // 3.  继续执行
}

func F() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("捕获异常:", err) // 1.  捕获异常:a
		}
		fmt.Println("b") // 2.  b
	}()
	panic("a")
}

方法method

方法可以看作是某种特定类型的函数,是 Go 面向对象编程的第一步。用好方法,具备面向对象编程思想是关键。

方法的声明和函数类似,他们的区别是:方法在定义的时候,会在 func 和方法名之间增加一个参数,这个参数就是接收者。

接收者有两种类型:值接收者和指针接收者。不管是使用值接收者,还是指针接收者,一定要搞清楚类型的本质:对类型进行操作的时候,是要改变当前值,还是要创建一个新值进行返回?这些就可以决定我们是采用值传递,还是指针传递。

最后就是方法的调用,可以直接使用 . 操作符调用,还可以使用方法变量和方法表达式。

代码如下:

package main

import (
	"fmt"
)

type Person struct {
	name string
}

type Point struct {
	x, y int
}

func main() {
	p := Person{name: "zhangsan"}

	// 调用方法
	fmt.Println(p.String()) // 1. person name is zhangsan

	// 值接收者,不会改变原始值
	p.Modify()
	fmt.Println(p.String()) // 2. person name is zhangsan
	// 等价于
	(&p).Modify()
	fmt.Println(p.String()) // 3. person name is zhangsan

	// 指针接收者,会改变原始值
	p.ModifyP()
	fmt.Println(p.String()) // 4. person name is lisi
	// 等价于
	(&p).ModifyP()
	fmt.Println(p.String()) // 5. person name is lisi

	// 方法变量
	p1 := Point{1, 2}
	q1 := Point{3, 4}
	f := p1.Add
	fmt.Println(f(q1)) // 6. {4 6}

	// 方法表达式
	f1 := Point.Add
	fmt.Println(f1(p1, q1)) // 7. {4 6}
}

func (p Person) String() string {
	return "person name is " + p.name
}

// 方法在定义的时候,会在 func 和方法名之间增加一个参数,
// 这个参数就是接收者
// 分为值接收者
func (p Person) Modify() {
	p.name = "lisi"
}

// 和指针接收者
func (p *Person) ModifyP() {
	p.name = "lisi"
}

func (p Point) Add(q Point) Point {
	return Point{p.x + q.x, p.y + q.y}
}

接口interface

之前介绍的类型都是具体类型,而接口是一种抽象类型,是多个方法声明的集合。在 Go 中,只要目标类型实现了接口要求的所有方法,我们就说它实现了这个接口。

接口赋值,空接口,类型断言和类型查询

代码如下:

package main

import "fmt"

// 定义接口,包含 Eat 方法
type Duck interface {
	Eat()
}

type Duck1 interface {
	Eat()
	Walk()
}

// 定义 Cat 结构体,并实现 Eat 方法
type Cat struct{}

func (c *Cat) Eat() {
	fmt.Println("cat eat")
}

// 定义 Dog 结构体,并实现 Eat 方法
type Dog struct{}

func (d *Dog) Eat() {
	fmt.Println("dog eat")
}

func (d *Dog) Walk() {
	fmt.Println("dog walk")
}

func main() {
	var c Duck = &Cat{}
	c.Eat() // 1. cat eat

	var d Duck = &Dog{}
	d.Eat() // 2. do eat

	s := []Duck{
		&Cat{},
		&Dog{},
	}
	for _, n := range s {
		n.Eat() // 3. cat eat 4. dog eat
	}

	var c1 Duck1 = &Dog{}
	var c2 Duck = c1
	c2.Eat() // 5. dog eat

	// 类型断言
	var n interface{} = 55
	assert(n) // 6. 55
	var n1 interface{} = "hello"
	// assert(n1) // panic: interface conversion: interface {} is string, not int
	assertFlag(n1) // 不是int类型,不会输出
	// 类型断言
	assertInterface(c) // 7.  &{}

	// 类型查询
	searchType(50)         // 8. Int: 50
	searchType("zhangsan") // 9. String: zhangsan
	searchType(c1)         // 10. dog eat
	searchType(50.1)       // 11.  Unknown type

	// 空接口
	s1 := "Hello World"
	i := 50
	strt := struct {
		name string
	}{
		name: "AlwaysBeta",
	}
	test(s1)   // Type = string, value = Hello World
	test(i)    // Type = int, value = 50
	test(strt) // Type = struct { name string }, value = {AlwaysBeta}
}

func assert(i interface{}) {
	// 类型断言是作用在接口值上的操作,x.(T)
	// 如果是,则输出 x 的值;如果不是,程序直接 panic。
	s := i.(int)
	fmt.Println(s)
}

func assertInterface(i interface{}) {
	// 类型断言是作用在接口值上的操作,x.(T)
	// 如果是,则输出 x 的值;如果不是,程序直接 panic。
	s := i.(Duck)
	fmt.Println(s)
}

func assertFlag(i interface{}) {
	if s, ok := i.(int); ok {
		fmt.Println(s)
	}
}

func searchType(i interface{}) {
	// 语法类似类型断言,只需将 T 直接用关键词 type 替代。
	switch v := i.(type) {
	case string:
		fmt.Printf("String: %s\n", i.(string))
	case int:
		fmt.Printf("Int: %d\n", i.(int))
	case Duck:
		v.Eat()
	default:
		fmt.Printf("Unknown type\n")
	}
}

func test(i interface{}) {
	fmt.Printf("Type = %T, value = %v\n", i, i)
}



goroutine

Go 语言的并发执行体称为 goroutine,使用关键词 go 来启动一个 goroutine。

go 关键词后面必须跟一个函数,可以是有名函数,也可以是无名函数,函数的返回值会被忽略。

go 的执行是非阻塞的。

当一个程序启动时,只有一个 goroutine 来调用 main 函数,称为主 goroutine。新的 goroutine 通过 go 关键词创建,然后并发执行。当 main 函数返回时,不会等待其他 goroutine 执行完,而是直接暴力结束所有 goroutine。

代码:

package main

import (
	"fmt"
	"time"
)

func main() {
	// 并不阻塞
	go spinner(100 * time.Millisecond)
	const n = 45
	// 计算斐波那契数列
	fibN := fib(n)
	fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN) // Fibonacci(45) = 1134903170
}

func spinner(delay time.Duration) {
	for {
		// 转小菊花
		for _, r := range `-\|/` {
			fmt.Printf("\r%c", r)
			time.Sleep(delay)
		}
	}
}

func fib(x int) int {
	if x < 2 {
		return x
	}
	return fib(x-1) + fib(x-2)
}

channel

一般写多进程程序时,都会遇到一个问题:进程间通信。常见的通信方式有信号,共享内存等。goroutine 之间的通信机制是通道 channel。

package main

import "fmt"

func Add(x, y int, ch chan int) {
	z := x + y
	ch <- z
}

// 通道支持三个主要操作:send,receive 和 close。
// ch <- x // 发送
// x = <-ch // 接收
// <-ch // 接收,丢弃结果

// close(ch) // 关闭
func counter(out chan<- int) {
	for x := 0; x < 10; x++ {
		out <- x
	}
	close(out)
}

// 类型 chan<- int 是一个只能发送的通道,类型 <-chan int 是一个只能接收的通道。
func squarer(out chan<- int, in <-chan int) {
	for v := range in {
		out <- v * v
	}
	close(out)
}

func printer(in <-chan int) {
	for v := range in {
		fmt.Println(v)
	}
}

func main() {
	// 使用 make 创建通道:
	// make 函数接受两个参数,第二个参数是可选参数,表示通道容量。不传或者传 0 表示创建了一个无缓冲通道。
	// 无缓冲通道上的发送操作将会阻塞,直到另一个 goroutine 在对应的通道上执行接收操作
	// 所以,无缓冲通道是一种同步通道
	n := make(chan int)
	s := make(chan int)

	go counter(n)
	go squarer(s, n)
	printer(s)
	// 0 1 4 9 16 25 36 49 64 81

	ch := make(chan int)
	for i := 0; i < 10; i++ {
		go Add(i, i, ch)
	}

	for i := 0; i < 10; i++ {
		fmt.Println(<-ch)
		//随机的: 0 8 2 4 6 12 10 14 16
	}
}

sync

sync 包提供了两种锁类型:sync.Mutex 和 sync.RWMutex,前者是互斥锁,后者是读写锁。

当一个 goroutine 获取了 Mutex 后,其他 goroutine 不管读写,只能等待,直到锁被释放。

RWMutex 属于经典的单写多读模型,当读锁被占用时,会阻止写,但不阻止读。而写锁会阻止写和读。

互斥锁代码:

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	// 互斥锁
	// 当一个 goroutine 获取了 Mutex 后,其他 goroutine 不管读写,只能等待,直到锁被释放
	var mutex sync.Mutex
	wg := sync.WaitGroup{}

	// 主 goroutine 先获取锁
	fmt.Println("Locking  (G0)") // 1. Locking  (G0)
	mutex.Lock()
	fmt.Println("locked (G0)") // 2. locked (G0)

	wg.Add(3)
	for i := 1; i < 4; i++ {
		go func(i int) {
			// 由于主 goroutine 先获取锁,程序开始 5 秒会阻塞在这里
			fmt.Printf("Locking2 (G%d)\n", i)
			// 345三个结果是随机
			// 3. Locking2 (G3)
			// 4. Locking2 (G1)
			// 5. Locking2 (G2)
			mutex.Lock()
			fmt.Printf("locked2 (G%d)\n", i)

			time.Sleep(time.Second * 2)
			mutex.Unlock()
			fmt.Printf("unlocked2 (G%d)\n", i)
			// 8 10 12之间间隔两秒
			// 8. locked2 (G3)
			// 9. unlocked2 (G3)
			// 10. locked2 (G1)
			// 11. unlocked2 (G1)
			// 12. locked2 (G2)
			// 13. unlocked2 (G2)
			wg.Done()
		}(i)
	}

	// 主 goroutine 5 秒后释放锁
	time.Sleep(time.Second * 5)
	fmt.Println("ready unlock (G0)")
	// 6. ready unlock (G0)
	mutex.Unlock()
	fmt.Println("unlocked (G0)")
	// 7. unlocked (G0)

	wg.Wait()
}

读写锁代码:

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	// RWMutex 属于经典的单写多读模型,当读锁被占用时,会阻止写,但不阻止读。而写锁会阻止写和读。

	var rwMutex sync.RWMutex
	wg := sync.WaitGroup{}

	Data := 0
	wg.Add(20)
	for i := 0; i < 10; i++ {
		go func(t int) {
			// 第一次运行后,写解锁。
			// 循环到第二次时,读锁定后,goroutine 没有阻塞,同时读成功。
			fmt.Println("Locking")
			// 1. 连续10个Locking
			rwMutex.RLock()
			defer rwMutex.RUnlock()
			fmt.Printf("Read data: %v\n", Data)
			wg.Done()
			time.Sleep(2 * time.Second)
		}(i)
		go func(t int) {
			// 写锁定下是需要解锁后才能写的
			rwMutex.Lock()
			defer rwMutex.Unlock()
			Data += t
			fmt.Printf("Write Data: %v %d \n", Data, t)
			wg.Done()
			time.Sleep(2 * time.Second)
		}(i)
	}

	wg.Wait()
}