golang

协程(goroutine) Link to heading

协程是go语言内部唯一实现程序并发的方式,go不支持创建系统线程

在Go中,开启一个新的协程是非常简单的。 我们只需在一个函数调用之前使用一个go关键字,即可让此函数调用运行在一个新的协程之中。 当此函数调用退出后,这个新的协程也随之结束了。

// 在下面的例子程序中,主协程创建了两个新的协程,由于主协程Sleep 2秒。所以两个协程内容没有执行完。
package main

import (
	"log"
	"math/rand"
	"time"
)

func SayGreetings(greeting string, times int) {
	for i := 0; i < times; i++ {
		log.Println(greeting)
		d := time.Second * time.Duration(rand.Intn(5)) / 2
		time.Sleep(d)
	}
}

func main() {
	rand.Seed(time.Now().UnixNano())
	log.SetFlags(0)
	go SayGreetings("hi!", 10)
	go SayGreetings("Hello!", 10)
	time.Sleep(2 * time.Second)
}

并发同步 Link to heading

使用通道技术可以实现并发同步,sync标准库提供的WaitGroup可以很简单实现这一点

package main

import (
	"log"
	"math/rand"
	"runtime"
	"sync"
	"time"
)

var wg sync.WaitGroup

func SayGreetings(greeting string, times int) {
	for i := 0; i < times; i++ {
		log.Println(greeting)
		d := time.Second * time.Duration(rand.Intn(5)) / 2
		time.Sleep(d)
	}
  // 协程完成时候通知任务已完成
	wg.Done()
}

func main() {
	rand.Seed(time.Now().UnixNano())
	log.SetFlags(0)
	log.Println(runtime.NumCPU())
	// 用add方法注册两个新任务
	wg.Add(2)
	go SayGreetings("hi v!", 10)
	go SayGreetings("hello z!", 10)
	// 阻塞,直到任务都完成
	wg.Wait()
  // 当上面协程都完成,此时才会Sleep
	time.Sleep(2 * time.Second)
	log.Println("end")
}

延迟函数 Link to heading

一个在defer后面的函数调用就是延迟函数,多个延迟函数遵守后进先出规则执行

// 下面例子,最后打印值为0~9
package main

import "fmt"

func main() {
	defer fmt.Println("9")
	fmt.Println("0")
	defer fmt.Println("8")
	fmt.Println("1")
	if false {
		defer fmt.Println("not reachable")
	}
	defer func() {
		defer fmt.Println("7")
		fmt.Println("3")
		defer func() {
			fmt.Println("5")
			fmt.Println("6")
		}()
		fmt.Println("4")
	}()
	fmt.Println("2")
	return
	defer fmt.Println("not reachable")
}

修改函数返回值 Link to heading

一个延迟调用可以修改包含此延迟调用的最内层函数的返回值

// 下面例子,先执行r = n + n,r = 10。然后执行延迟函数 r = r +n,此时r = 10,n = 5,最后返回值15
package main

import "fmt"

func Triple(n int) (r int) {
	defer func() {
		r += n // 修改返回值
	}()

	return n + n // <=> r = n + n; return
}

func main() {
	fmt.Println(Triple(5)) // 15
}

协程和延迟调用的实参的估值时刻 Link to heading

// 第一个匿名函数中的循环打印出2、1和0这个序列,但是第二个匿名函数中的循环打印出三个3。 因为第一个循环中的i是在fmt.Println函数调用被推入延迟调用队列的时候估的值,而第二个循环中的i是在第二个匿名函数调用的退出阶段估的值(此时循环变量i的值已经变为3)。
package main

import "fmt"

func main() {
	func() {
		for i := 0; i < 3; i++ {
			defer fmt.Println("a:", i)
		}
	}()
	fmt.Println()
	func() {
		for i := 0; i < 3; i++ {
			defer func() {
				fmt.Println("b:", i)
			}()
		}
	}()
}

可以对第二个循环修改一下,使得它的结果和第一个一样

for i := 0; i < 3; i++ {
  defer func(i int) {
    // 此i为形参i,非实参循环变量i。
    fmt.Println("b:", i)
  }(i)
}

同样的估值时刻规则也适用于协程调用

// 下面这个例子程序将打印出123 789
package main

import "fmt"
import "time"

func main() {
	var a = 123
	go func(x int) {
		time.Sleep(time.Second)
		fmt.Println(x, a) // 123 789
	}(a)

	a = 789

	time.Sleep(2 * time.Second)
}

恐慌和恢复 Link to heading

Go不支持异常抛出和捕获,而是推荐使用返回值显式返回错误。 不过,Go支持一套和异常抛出/捕获类似的机制。此机制称为恐慌/恢复(panic/recover)机制。

package main

import "fmt"

func main() {
	defer func() {
		recover()
		fmt.Println("panic is over")
	}()
	panic("panic is coming")
	fmt.Println("end?")
}