在Go语言中,每一个并发的执行单元叫作一个goroutine。设想这里的一个程序有两个函数,一个函数做计算,另一个输出结果,假设两个函数没有相互之间的调用关系。一个线性的程序会先调用其中的一个函数,然后再调用另一个。如果程序中包含多个goroutine,对两个函数的调用则可能发生在同一时刻。
runtime.GOMAXPROCS 去设置CPU,Golang默认使用所有的cpu核。

go

  1. func Add(x, y int) {
  2. z := x + y
  3. fmt.Println(z)
  4. }
  5. go Add(1, 1)

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

chan

image.png
如果说goroutine是Go语言程序的并发体的话,那么channels则是它们之间的通信机制。一个channel是一个通信机制,它可以让一个goroutine通过它给另一个goroutine发送值信息。每个channel都有一个特殊的类型,也就是channels可发送数据的类型。一个可以发送int类型数据的channel一般写为chan int。
使用内置的make函数,我们可以创建一个channel:

  1. ch := make(chan int) // ch has type 'chan int'

和map类似,channel也对应一个make创建的底层数据结构的引用。当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者和被调用者将引用同一个channel对象。和其它的引用类型一样,channel的零值也是nil。
两个相同类型的channel可以使用==运算符比较。如果两个channel引用的是相同的对象,那么比较的结果为真。一个channel也可以和nil进行比较。
一个channel有发送和接受两个主要操作,都是通信行为。一个发送语句将一个值从一个goroutine通过channel发送到另一个执行接收操作的goroutine。发送和接收两个操作都使用<-运算符。在发送语句中,<-运算符分割channel和要发送的值。在接收语句中,<-运算符写在channel对象之前。一个不使用接收结果的接收操作也是合法的。
有缓存的一般用于协程之间的通信,无缓存一般用于同步。

  1. ch = make(chan int) // unbuffered channel
  2. ch = make(chan int, 0) // unbuffered channel
  3. ch = make(chan int, 3) // buffered channel with capacity 3
  4. ch <- x // a send statement
  5. x = <-ch // a receive expression in an assignment statement
  6. <-ch // a receive statement; result is discarded
  7. //Channel还支持close操作,用于关闭channel,随后对基于该channel的任何发送操作都将导致panic异常。对一个已经被close过的channel进行接收操作依然可以接受到之前已经成功发送的数据;如果channel中已经没有数据的话将产生一个零值的数据。
  8. close(ch)

向channel写入数据通常会导致程序阻塞,直到有其他goroutine从这个channel中读取数据。从channel中读取数据的语法是m如果channel之前没有写入数据,那么从channel中读取数据也会导致程序阻塞,直到channel中被写入数据为止.

select

select的用法与switch语言非常类似,由select开始一个新的选择块,每个选择条件由case语句来描述。与switch语句可以选择任何可使用相等比较的条件相比,select有比较多的限制,其中最大的一条限制就是每个case语句里必须是一个IO操作,大致的结构如下:

  1. select {
  2. case <-chan1:
  3. // 如果chan1成功读到数据,则进行该case处理语句
  4. case chan2 <- 1:
  5. // 如果成功向chan2写入数据,则进行该case处理语句
  6. default:
  7. // 如果上面都没有成功,则进入default处理流程
  8. }
  9. //为channel实现超时机制:
  10. // 首先,我们实现并执行一个匿名的超时等待函数
  11. timeout := make(chan bool, 1)
  12. go func() {
  13. time.Sleep(1e9) // 等待1秒钟
  14. timeout <- true
  15. }()
  16. // 然后我们把timeout这个channel利用起来
  17. select {
  18. case <-ch:
  19. // 从ch中读取到数据
  20. case <-timeout:
  21. // 一直没有从ch中读取到数据,但从timeout中读取到了数据
  22. }

栈管理

Go支持几十万协程并发,其中一个重要原因是协程的栈空间很小—-2K.
假如每个goroutine分配固定栈大小并且不能增长,太小则会导致溢出,太大又会浪费空间,无法存在许多的goroutine。因此,goroutine可以初始时只给栈分配很小的空间,然后随着使用过程中的需要自动地增长,不需要那么大的空间时,栈空间也要自动缩小。go 1.3之前采用分段栈,之后改为连续栈。
原理:

  • 开始栈只有一个段,发生函数调用时检查栈是否够用,需要更多的栈空间时,直接new一个2倍大的栈空间,并将原先栈空间中的数据拷贝到新的栈空间中。
  • 栈的收缩是垃圾回收的过程中实现的.当检测到栈只使用了不到1/4时,栈缩小为原来的1/2。