goroutine
使用goroutine的方法很简单,直接在语句前面加go关键字即可,如果是多核处理器的电脑,使用gorountine,就会在另外一个CPU上执行goroutine,子协程不一定会和主协程在一个CPU上执行。
这里有两个注意的地方,使用go关键字的进程称之为子协程,而没有使用go关键字的进程称之为主协程,在多CPU的机器上,如果有多个协程,那么这些协程的执行顺序以及执行完成的顺序都是不确定的,但有一点,如果主协程结束,那么整个进程就结束了,不论子协程是否结束,整个进程都结束了,也就看不到子协程的运行结果了。
package main
import "fmt"
func main() {
go fmt.Println("hello") //子协程
fmt.Println("world") //主协程
}
上面的代码执行之后,会有三种结果:
1、hello world 2、world 3、world hello
上面这三个结果都是正确的。
对于第一种,子协程在一个CPU上面执行输出hello之后,再轮到主协程中执行输出world。
对于第二种,子协程在一个CPU上还没来得及执行,或者说没有执行完成,但是主协程已经执行完成了,此时整个进程就运行完毕了,所以就看不到子协程的运行结果了。
对于第三种,主协程中语句还没执行完成的时候,子协程已经运行完毕,几乎同时完成,所以会出现着这种情况。
Channel
channel的零值是nil
//创建channel
chi := make(chan int)
chs := make(chan string)
chif := make(chan interface{})
//因为可以认为所有的类型都实现了空接口
//所以这个chif的chan接收任何类型的chan
// 发送和接收channel
chi := make(chan int)
chi <- 3 //发送
i := <-chi //接收,保存值
<-chi //接收,不保存值
close(chi) //关闭channel
//对一个已经关闭的channel发送数据,都会导致panic异常。
//对于一个已经关闭的channel,可以继续接收这个已关闭的channel中的数据,如果没有数据的话,接收到的就是chan类型的零值。
// 声明单向channle
chi := make(<-chan int) //只能被读数据的channel
chi := make(chan <- int) //只接收数据的channel
// 有缓冲的channel
ch := make(chan int) //无缓冲
chi := make(chan int,10) //有缓冲channel,缓冲空间可存10个值
无缓冲的channel(同步channel)
有缓冲的channel(异步channel)
一个基于无缓冲的channel:
- 发送操作将导致发送者的gorountine阻塞,直到有另外一个goroutine在相同的channel上执行接收操作之后,发送者的阻塞才会解开。
- 同理,如果接收者的接收操作先发生,那么接收者因为从channel中接收不到数据,所有接收者goroutine也将阻塞,直到另一个goroutine在相同的channel上执行发送操作,接受者接收到了数据的阻塞才会解开。
-
向一个nil值(未用make分配空间)的channel发送或读取数据,会导致永远阻塞。
基于无缓冲的channel的发送和接收操作将导致两个goroutine做一次同步操作,所以也叫作同步channel。
使用range遍历channel
package main
import (
"fmt"
)
func main() {
num := make(chan int)
go func() {
num <- 30
num <- 40
close(num) // 注意
}()
for x := range num {
fmt.Println(x)
}
//输出
//30
//40
}
// 使用range遍历channel时,从channel中接收数据,如果从channel中获取到了数据,那么返回的结果就是获取到的值;如果channel已经关闭,那么对于无缓冲的channel而言,就没有数据可获取了
// 需要注意的是,如果使用for 遍历channel的时候,channel没有close,那么会造成死锁。
因为关闭channel操作 一般是 发送方通知接收方,发送方不再向channel中发送新的数据了,所以通常只有在发送者所在的goroutine才会调用close函数,在接收channel数据的goroutine中关闭channel显然是不合理的,很有可能会引起错误,因为发送数据的goroutine可能不知道channel已经关闭了,所以他一旦向channle中发送数据,就会引发错误。
总结
- 对写的一方来说,一定记着及时关闭channel,避免出现协程泄露。虽然它占得资源少,省点电不香么。
- 对读的一方来说,除非十分确定数据的个数,最好是用for来读数据,省的在“管儿”里有“野数据”造成内存泄露。同时根据第二个返回值的真假来控制for循环,避免出现“无效工作量”
- 对一个已经关闭的channel发送数据,都会导致panic异常。
- 对于一个已经关闭的channel,可以继续接收这个已关闭的channel中的数据,如果没有数据的话,接收到的就是chan类型的零值。
- 向一个nil值(未用make分配空间)的channel发送或读取数据,会导致永远阻塞。