Go 学习之 channel 篇

channel

  1. 函数中启动的 goroutine,【不会随着函数返回而结束,是随着 main 函数结束而结束】
    • 即 goroutine 生命周期 == main 函数声明周期
  2. 可以从【关闭的通道读取值】,但是是【空值】,对于 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 函数

go : select 的执行顺序

  1. 当多个 case 满足时,select 的选择是随机的
  2. 可以通过 定时器,结合 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
		}
	}
}

传统同步机制

通过【共享内存】来通信,因此需要【加锁】,来避免竞争条件下被覆盖

  1. syn.WaitGroup
  2. Mutex
  3. 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())
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值