channel
- 函数中启动的 goroutine,【不会随着函数返回而结束,是随着 main 函数结束而结束】
- 即 goroutine 生命周期 == main 函数声明周期
- 可以从【关闭的通道读取值】,但是是【空值】,对于 string 类型 channel 就是 空字符,对于 int 类型 channel 就是 0
- 因此
n, ok := <-c
这种读取形式,需要通过 ok 判断 channel 是否已关闭 - 但
for n := range c
这种读取形式,不需要判断 channel 是否已关闭,因为关闭了就不会读取了
- 因此
package main
import (
"fmt"
"time"
)
// 不要通过共享内存来通信,要通过通信来共享内存
// 以前:创建个 flag(共享内存),传递事件的变化
// 现在:创建个 channel(通信),传递事件的变化
func worker2(id int, c chan int) {
for {
n, _ := <-c
// 不加此判断,那么 即使通道关闭,仍会读取(空字符或0)进行继续打印
// n, ok := <-c
//if !ok {
// break
//}
fmt.Printf("Worker %d received %v\n",
id, n)
}
}
// worker : 从 channel c 中消费数据
func worker(id int, c chan int) {
// range 不会从关闭的通道读取【空值】
for n := range c {
fmt.Printf("Worker %d received %c\n",
id, n)
}
}
// 此处只创建了 一个 channel 就进行了返回
// 但是通过 goroutine 创建的 worker 并【不会因为此函数返回而推出】,goroutine 的生命周期【等同于 main 函数的生命周期】
// 即使 createWorker 返回了,但 main 函数没结束,worker 函数一直运行
// createWorker : 创建 channel 返回,启动 worker 消费此 channel 中的数据
func createWorker(id int) chan<- int {
c := make(chan int)
go worker(id, c)
return c
}
func chanDemo() {
var channels [10]chan<- int
for i := 0; i < 10; i++ {
// createWorker : 创建 channel 返回,启动 worker 消费此 channel 中的数据
channels[i] = createWorker(i)
}
// 生产数据 传入 channel
for i := 0; i < 10; i++ {
channels[i] <- 'a' + i
}
for i := 0; i < 10; i++ {
channels[i] <- 'A' + i
}
time.Sleep(time.Millisecond)
}
// bufferedChannel 创建一个 带缓冲的 channel ,传给 worker 从此 channel 中取数据消费
func bufferedChannel() {
c := make(chan int, 3)
go worker(0, c)
c <- 'a'
c <- 'b'
c <- 'c'
c <- 'd'
time.Sleep(time.Millisecond)
}
func channelClose() {
c := make(chan int)
go worker(0, c) // range 不会从关闭的通道读取【空值】
go worker2(1, c) // 测试从关闭通道读取【空值】
c <- 'a'
c <- 'b'
c <- 'c'
c <- 'd'
close(c)
time.Sleep(time.Millisecond)
}
func hello() {
go func() {
for {
fmt.Println("hello")
}
}()
return
}
func main() {
fmt.Println("Channel as first-class citizen")
chanDemo()
fmt.Println("Buffered channel")
bufferedChannel()
fmt.Println("Channel close and range")
channelClose()
//hello()
//time.Sleep(1 * time.Millisecond)
}
main 函数的结束时机(保证channel 都运行完成)
- 通过
wg *sync.WaitGroup
来等待所有的协程结束wg
的值 不必等于 协程的数量,但是必须要等于【所有协程调用wg.done
的次数和】,这样才能保证所有协程的正常运行结束
package main
import (
"fmt"
"sync"
)
func doWork(id int,
w worker) {
for n := range w.in {
fmt.Printf("Worker %d received %c\n",
id, n)
// 每次 done 就会将计数 -1
w.done()
}
}
type worker struct {
in chan int
// 结束逻辑抽象 func 内可实现自定义的结束逻辑
done func()
}
func createWorker(
id int, wg *sync.WaitGroup) worker {
w := worker{
in: make(chan int),
// 抽象后 更方便实现自己的结束逻辑
done: func() {
wg.Done()
},
}
// 开启 goroutine 消耗数据
go doWork(id, w)
return w
}
func chanDemo() {
var wg sync.WaitGroup
var workers [10]worker
// 此处开了 10个 协程
for i := 0; i < 10; i++ {
workers[i] = createWorker(i, &wg)
}
// 通过 waitGroup 等待所有 goroutine 都完成
wg.Add(20)
// wg 的值不必和 协程数 相等,但是要 和 done 次数保持相等
// 可以看到 每个协程 会输入两次数据,因此也就会执行两次 done,10 个协程共 20 次
// 所以要是 wg 的值为 10,那么 10 此后就退出了,数据就无法全部打印完成
for i, worker := range workers {
worker.in <- 'a' + i
}
for i, worker := range workers {
worker.in <- 'A' + i
}
wg.Wait()
}
func main() {
chanDemo()
}
通过通信共享内存
- 下面的例子是中序遍历
Traverse
是传统遍历方法,直接打印出来TraverseWithChannel
也是打印数据,另外将数据传给 channel ,由 main 函数进行读取处理(本例子中是找出最大值)- 此处的channel 对于 main 函数来说,相当于一个数据源,可以理解为 python 的 yield 函数
package main
import "fmt"
type Node struct {
Value int
Left, Right *Node
}
func (node *Node) Traverse() {
node.TraverseFunc(func(n *Node) {
n.Print()
})
fmt.Println()
}
func (node Node) Print() {
fmt.Print(node.Value, " ")
}
func (node *Node) SetValue(value int) {
if node == nil {
fmt.Println("Setting Value to nil " +
"node. Ignored.")
return
}
node.Value = value
}
func CreateNode(value int) *Node {
return &Node{Value: value}
}
// 中序遍历 左 根 右
// 对于遍历到的节点,采取的操作进行抽象,用 f 函数
// f 具象化,可以做:记录节点值,或传给 channel 等
func (node *Node) TraverseFunc(f func(*Node)) {
if node == nil {
return
}
node.Left.TraverseFunc(f)
f(node)
node.Right.TraverseFunc(f)
}
func (node *Node) TraverseWithChannel() chan *Node {
out := make(chan *Node)
// 开启 goroutine 进行遍历
go func() {
node.TraverseFunc(func(node *Node) { // 此 func 就是抽象的实现,将 node 数据放入 channel
node.Print() // 此处也可实现打印
out <- node
})
close(out)
}()
return out
}
func main() {
var root Node
// 建树
root = Node{Value: 3}
root.Left = &Node{}
root.Right = &Node{5, nil, nil}
root.Right.Left = new(Node)
root.Left.Right = CreateNode(2)
root.Right.Left.SetValue(4)
// 中序遍历 正常做法
root.Traverse()
// 中序遍历的所有值 采用 channel 记录的方法 都传给 channel c
// 相当于 python 的 yield
c := root.TraverseWithChannel()
// 从 channel c 中取值,找出最大的
maxNodeValue := 0
for node := range c {
if node.Value > maxNodeValue {
maxNodeValue = node.Value
}
}
fmt.Println("Max node value:", maxNodeValue)
}
// OutPut:
// 0 2 3 4 5
// 0 2 3 4 5 Max node value: 5
channel select 函数
- 当多个 case 满足时,select 的选择是随机的
- 可以通过 定时器,结合 select 实现定时打印数据,或进行关闭
package main
import (
"fmt"
"math/rand"
"time"
)
// 每隔 1.5s 产生一条数据,输入到 out channel 中
func generator() chan int {
out := make(chan int)
// goroutine 不断产生数据
go func() {
i := 0
for {
time.Sleep(
time.Duration(rand.Intn(1500)) *
time.Millisecond)
out <- i
i++
}
}()
return out
}
func worker(id int, c chan int) {
for n := range c {
time.Sleep(time.Second)
fmt.Printf("Worker %d received %d\n",
id, n)
}
}
func createWorker(id int) chan<- int {
c := make(chan int)
go worker(id, c)
return c
}
func main() {
// 数据生成器
var c1, c2 = generator(), generator()
// 返回一个 channel
var worker = createWorker(0)
// 缓存区:用于平衡 数据产生和数据消耗的速度差异,避免数据产生太快,消耗太慢,导致部分数据被覆盖,无法读取到
var values []int
// 定时器
// 10 s 后发送一条 time 的信号
tm := time.After(10 * time.Second)
// 每秒 发送一条 time 的消息
tick := time.Tick(time.Second)
for {
// nil cha
var activeWorker chan<- int
var activeValue int
// 缓存区不为空,读取第一个数据
if len(values) > 0 {
activeWorker = worker
activeValue = values[0]
}
// 当很多 case 都满足时, select 是随机选择的,没有按照顺序
select {
// 将受到的数据,放入到缓存区
case n := <-c1:
values = append(values, n)
case n := <-c2:
values = append(values, n)
// 将上面读取的缓存区第一个数据,写入到 channel 中,同时将缓存区 去除 该队首元素
case activeWorker <- activeValue:
values = values[1:]
// 收到此信号,打印 timeout
case <-time.After(800 * time.Millisecond):
fmt.Println("timeout")
// 收到此信号表示,打印缓存区(每秒一次)
case <-tick:
fmt.Println(
"queue len =", len(values))
// 收到此信号(上面设置为10s) 表示结束
case <-tm:
fmt.Println("bye")
return
}
}
}
传统同步机制
通过【共享内存】来通信,因此需要【加锁】,来避免竞争条件下被覆盖
- syn.WaitGroup
- Mutex
- Cond
通过运行 go run -race 文件名.go 可检测出是否有竞争条件导致的脏写或脏读(使数据不正常)
不正常
// 通过运行 go run -race atomic.go 可检测出是否有竞争条件导致的脏写或脏读(使数据不正常)
// atomic.go 文件
package main
import (
"fmt"
"sync"
"time"
)
type atomicInt int
func (a *atomicInt) increment() {
fmt.Println("safe increment")
*a++
}
func (a *atomicInt) get() int {
return int(*a)
}
func main() {
var a atomicInt
a.increment()
go func() {
a.increment()
}()
time.Sleep(time.Millisecond)
fmt.Println(a.get())
}
正常
// 通过运行 go run -race atomic.go 可检测出是否有竞争条件导致的脏写或脏读(使数据不正常)
// atomic.go 文件
package main
import (
"fmt"
"sync"
"time"
)
type atomicInt struct {
value int
lock sync.Mutex
}
func (a *atomicInt) increment() {
fmt.Println("safe increment")
func() {
a.lock.Lock()
defer a.lock.Unlock()
a.value++
}()
}
func (a *atomicInt) get() int {
a.lock.Lock()
defer a.lock.Unlock()
return a.value
}
func main() {
var a atomicInt
a.increment()
go func() {
a.increment()
}()
time.Sleep(time.Millisecond)
fmt.Println(a.get())
}