协程(coroutine)是Go语言中的轻量级线程实现,由Go运行时(runtime)管理。

在一个函数调用前加上go关键字,这次调用就会在一个新的goroutine中并发执行。当被调用的函数返回时,这个goroutine也自动结束。需要注意的是,如果这个函数有返回值,那么这个返回值会被丢弃。

func longWait() {
	fmt.Println("Beginning longWait()")
	time.Sleep(5 * 1e9)
	fmt.Println("End of longWait()")
}

func shortWait() {
	fmt.Println("Beginning shortWait()")
	time.Sleep(2 * 1e9)
	fmt.Println("End of shortWait()")
}

func main(){
	fmt.Println("main begin")
	time.Sleep(3 * 1e9)

	go shortWait()
	go longWait()
	fmt.Println("main end")
}

执行上述代码,发现longWait没有执行到,shortWait全部执行完毕,这时候main函数结束就不会在执行longWait。如果我们想要让main()函数等待所有goroutine退出后再返回,但如何知道goroutine都退出了呢?这就引出了多个goroutine之间通信的问题。

chan

channel是引用类型,是CSP格式的个体实现,用于多个goroutine通讯,其内部实现了同步,确保并发安全。
当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此,调用者或被调用者将引用一个channel对象,和其它的类型一样,channel的零值也是nil。

chan定义
make(chan type) //等坐于 make(chan type,0)
make(chan type,capacity) //capacity是容量
//当capacity=0时,channel是无缓冲阻塞读写的,当capacity>0时,channel是有缓冲非阻塞的,直到写满capacity元素才阻塞写入。

//channel通过操作符<-来接收和发数据
ch := make(chan int) //创建一个int类型管道
ch <- value //发送value到ch
<-ch //接收并且丢弃
num := <-ch //从ch中接收数据,并赋值给num
num, ok := <-ch //功能上和上面的一样,但是它还同时检查是否已经关闭或者为空

var ch1 chan int      // 普通channel
var ch2 chan <- int    // 只用于写int数据
var ch3 <-chan int     // 只用于读int数据

channel作为一种原生类型,本身也可以通过channel进行传递,例如下面这个流式处理结构:

type PipeData struct {
    value int
    handler func(int) int
    next chan int
}

func handle(queue chan *PipeData) {
    for data := range queue {
        data.next <- data.handler(data.value)
    }
}

下面是用chan来解决并发的问题,chan相当于队列,先入先出,当有数据进入chan中后,num才能获取chan中的数据,主线程才能继续执行。如果没有数据写入chan,就会一直阻塞。

select

select()函数用来监控一组描述符,该机制常被用于实现高并发的socket服务器程序。Go语言直接在语言级别支持select关键字,用于处理异步IO问题,大致结构如下:

1.select+case是用于阻塞监听goroutine的,如果没有case,就单单一个select{},则为监听当前程序中的goroutine,此时注意,需要有真实的goroutine在跑,否则select{}会报panic。

2.select底下有多个可执行的case,则随机执行一个。

3.select常配合for循环来监听channel有没有故事发生。需要注意的是在这个场景下,break只是退出当前select而不会退出for,需要用break TIP / goto的方式。

4.无缓冲的通道,则传值后立马close,则会在close之前阻塞,有缓冲的通道则即使close了也会继续让接收后面的值。

func main() {
	ch := make(chan int,1)
	for i := 0; i < 10; i++{
		select {
		case ch <- i:
		case x := <- ch:
			fmt.Println(x)
		}
	}
}
1casechanneldeadlock

go中协程也会存在一些资源竞争,比如下面代码如果不加锁的话运行结果很难得到5000,加锁后可以得到想要的结果。

var lock sync.Mutex

type Money struct {
	amount int
}

func (m *Money)Add(i int) {
	m.amount += i
}
func (m *Money) Minute(i int) {
	lock.Lock()
	defer lock.Unlock()
	if m.amount >= i {
		m.amount = m.amount - i
	}
}
func (m *Money) Get() int {
	return m.amount
}

func main() {
	m := new(Money)
	m.Add(10000)
	for i := 0; i < 1000; i++ {
		go func() {
			time.Sleep(500 * time.Millisecond)
			m.Minute(5)
		}()
	}

	time.Sleep(2 * time.Second)
	fmt.Println(m.Get())
}