golang笔记11--go语言并发编程模块 channel

本文深入探讨Go语言的并发编程模块Channel,讲解如何使用Channel进行goroutine间通信,包括基本操作、等待任务结束、树的遍历、select调度以及对比传统同步机制。示例代码详细展示了Channel的多种应用场景。
摘要由CSDN通过智能技术生成

1 介绍

本文继上文 golang笔记10–go语言并发编程模块 goroutine, 进一步了解 go语言的并发编程模块 --channel,以及相应注意事项。
具体包括 : channel、使用Channel等待任务结束、使用Channel进行树的遍历、用select进行调度、传统同步机制 等内容。

2 Channel

2.1 channel

channel 是go语言在语言级别提供的 goroutine 间的通信方式。我们可以使用channel 在两个或者多个goroutine 之间传递消息;channel 是进程内的通信方式,因此通过channel 传递对象的过程和调用函数时的参数传递行为比较一致,比如也可以传递指针等。
channel 是类型相关的,也就是说一个 channel 只能传递一种类型的值,这个类型需要在申明 channel 时指定。

channel 常见操作包括 申明 chan,定义 chan,将数据写入 chan,从 chan 中读数据;
申明一个channel: var ch chan int;
定义一个channel: ch := make(chan int)
数据写入channel: ch <- 32
从channel读数据: value <-ch

package main

import (
	"fmt"
	"time"
)

func worker(id int, c chan int) {
	for {
		fmt.Printf("Worker %d received %d\n", id, <-c)
	}
}

func chanDemo() {
	var channels [10]chan int
	for i := 0; i < 10; i++ {
		channels[i] = make(chan int)
		go worker(i, channels[i])
	}
	for i := 0; i < 10; i++ {
		channels[i] <- 'a' + i
	}
	time.Sleep(time.Second) // 防止 main 函数过早退出
}

// chan<- int 表明返回的chan只能用来收数据
func createWorker(id int) chan<- int {

	c := make(chan int)
	go func() {
		for {
			fmt.Printf("Worker %d received %d\n", id, <-c)
		}
	}()
	return c
}
func chanDemo2() {
	var channels [10]chan<- int
	for i := 0; i < 10; i++ {
		channels[i] = createWorker(i)
	}
	for i := 0; i < 10; i++ {
		channels[i] <- 'a' + i
	}
	for i := 0; i < 10; i++ {
		channels[i] <- 'A' + i
	}
	time.Sleep(time.Second) // 防止 main 函数过早退出
}

func bufferedChannel() {
	c := make(chan int, 3) // 设置缓冲区为3
	go worker(0, c)
	c <- 'a'
	c <- 'b'
	c <- 'c'
	c <- 'd'
	time.Sleep(time.Second)
}

func workerClose(id int, c chan int) {
	//for {
	//	n, ok := <-c
	//	if !ok {
	//		break
	//	}
	//	fmt.Printf("Worker %d received %d\n", id, n)
	//}
	for n := range c {
		fmt.Printf("Worker %d received %d\n", id, n)
	}
}
func channelClose() {
	c := make(chan int, 3) // 设置缓冲区为3
	go workerClose(0, c)   // 在workerClose中检测是否有数据,没数据就退出,若不检测就会导致不停的收到 0 值
	c <- 'a'
	c <- 'b'
	c <- 'c'
	c <- 'd'
	close(c)
	time.Sleep(time.Second)
}

func main() {
	fmt.Println("channel as first-class citizen")
	chanDemo()
	chanDemo2()
	fmt.Println("buffered channel")
	bufferedChannel()
	fmt.Println("channel close and range")
	channelClose()
}

2.2 使用Channel等待任务结束

本案例中使用 2 种方式实现等待任务结束,第一种为自定义 done chan bool, 第二种使用go 内置的 sync.WaitGroup 的 wait() 来实现。

package main**加粗样式**

import (
	"fmt"
	"sync"
)

func doWork(id int, c chan int, done chan bool) {
	for {
		fmt.Printf("Worker %d received %c\n", id, <-c)
		go func() { // 让其并行发done, 防止 chanDemo() 中大写字母被卡死
			done <- true
		}()

	}
}

type worker struct {
	in   chan int
	done chan bool
}

// chan<- int 表明返回的chan只能用来收数据
func createWorker(id int) worker {
	w := worker{
		in:   make(chan int),
		done: make(chan bool),
	}
	go doWork(id, w.in, w.done)
	return w
}
func chanDemo() {
	var workers [10]worker
	for i := range workers {
		workers[i] = createWorker(i)
	}
	for i, worker := range workers {
		worker.in <- 'a' + i
	}
	for i, worker := range workers {
		worker.in <- 'A' + i
	}
	for _, worker := range workers {
		<-worker.done
		<-worker.done
	}
}

func doWorkV2(id int, c chan int, w workerV2) {
	for {
		fmt.Printf("Worker %d received %c\n", id, <-c)
		go func() { // 让其并行发done, 防止 chanDemo() 中大写字母被卡死
			w.done()
		}()

	}
}

type workerV2 struct {
	in   chan int
	done func()
}

func createWorkerV2(id int, wg *sync.WaitGroup) workerV2 {
	w := workerV2{
		in: make(chan int),
		done: func() {
			wg.Done()
		},
	}
	go doWorkV2(id, w.in, w)
	return w
}

func chanDemoV2() {
	var wg sync.WaitGroup
	var workers [10]workerV2
	for i := range workers {
		workers[i] = createWorkerV2(i, &wg)
	}
	wg.Add(20)
	for i, worker := range workers {
		worker.in <- 'a' + i
	}
	for i, worker := range workers {
		worker.in <- 'A' + i
	}
	wg.Wait()
}

func main() {
	fmt.Println("chapter11.2, channel as first-class citizen")
	chanDemo()
	fmt.Println("sync.WaitGroup wait()")
	chanDemoV2()
}
输出:
chapter11.2, channel as first-class citizen
Worker 0 received a
......
Worker 9 received J
sync.WaitGroup wait()
Worker 0 received a
......
Worker 8 received I

2.3 使用Channel进行树的遍历

本案例中使用channel 来遍历树,并计算树节点的最大值,具体案例如下:

vim node.go
package tree

import (
	"fmt"
)

type Node struct {
	Value       int
	Left, Right *Node
}

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}
}

func (node *Node) Traverse() {
	if node == nil {
		return
	}
	node.Left.Traverse()
	node.Print()
	node.Right.Traverse()
}

func (node *Node) TraverseV2() {
	node.TraverseFunc(func(nd *Node) {
		nd.Print()
	})
	fmt.Println()
}

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)
	go func() {
		node.TraverseFunc(func(node *Node) {
			out <- node
		})
		close(out)
	}()
	return out
}

vim main.go
package main

import (
	"fmt"
	"learngo/chapter11/11.3/tree"
)

func main() {
	fmt.Println("this is chapter 11.3")
	var root tree.Node
	root = tree.Node{Value: 3}
	root.Left = &tree.Node{}
	root.Right = &tree.Node{Value: 5}
	root.Right.Left = new(tree.Node)
	root.Left.Right = tree.CreateNode(2)
	root.Right.Left.SetValue(4)
	root.Traverse()
	fmt.Println("\nthis func traverseV2")
	root.TraverseV2()

	nodeCount := 0
	root.TraverseFunc(func(node *tree.Node) {
		nodeCount++
	})
	fmt.Println("Node count:", nodeCount)

	c := root.TraverseWithChannel()
	maxNode := 0
	for node := range c {
		if node.Value > maxNode {
			maxNode = node.Value
		}
	}
	fmt.Println("Max node value:", maxNode)
}
输出:
this is chapter 11.3
0 2 3 4 5 
this func traverseV2
0 2 3 4 5 
Node count: 5
Max node value: 5

2.4 用select进行调度

Go 语言中的 select 也能够让 Goroutine 同时等待多个 Channel 可读或者可写,在多个文件或者 Channel状态改变之前,select 会一直阻塞当前线程或者 Goroutine。
select 是与 switch 相似的控制结构,与 switch 不同的是,select 中虽然也有多个 case,但是这些 case 中的表达式必须都是 Channel 的收发操作。
本案例使用 select 进行调度,其包括了多个channel 的收发操作:

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func generator() chan int {
	out := make(chan int)
	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()
	var worker = createWorker(0)
	var values []int
	tm := time.After(10 * time.Second)
	tick := time.Tick(time.Second)
	for {
		var activeWorker chan<- int
		var activeValue int
		if len(values) > 0 {
			activeWorker = worker
			activeValue = values[0]
		}
		select {
		case n := <-c1:
			values = append(values, n)
		case n := <-c2:
			values = append(values, n)
		case activeWorker <- activeValue:
			values = values[1:]
		case <-time.After(800 * time.Millisecond):
			fmt.Println("timeout")
		case <-tick:
			fmt.Println(
				"queue len =", len(values))
		case <-tm:
			fmt.Println("bye")
			return
		}
	}
}
输出:
queue len = 3
Worker 0 received 0
queue len = 5
Worker 0 received 0
queue len = 5
Worker 0 received 1
queue len = 10
Worker 0 received 1
queue len = 10
Worker 0 received 2
queue len = 11
Worker 0 received 2
queue len = 12
Worker 0 received 3
queue len = 13
Worker 0 received 3
queue len = 14
Worker 0 received 4
bye

2.5 传统同步机制

go 语言除了chan 来通信外,也可以使用传统的同步机制,例如:WaitGroup、Mutex、Cond 等,但正常情况下一般优先使用 channel 来通信。

以下案例通过 Mutex 加锁来实现数据的可靠性,如果不加锁则会导致数据不安全( go run -race 11.5.go 会出现异常提示)。

package main

import (
	"fmt"
	"sync"
	"time"
)

type atomicInt struct {
	value int
	lock  sync.Mutex
}

func (a *atomicInt) 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() {
	fmt.Println("this chapter 11.5")
	var a atomicInt
	a.increment()
	go func() {
		a.increment()
	}()
	time.Sleep(time.Millisecond)
	fmt.Println(a.get())
}
输出:
this chapter 11.5
2

3 注意事项

4 说明

  1. 软件环境
    go版本:go1.15.8
    操作系统:Ubuntu 20.04 Desktop
    Idea:2020.01.04
  2. 参考文档
    select --go语言设计与实现
    由浅入深掌握Go语言 --慕课网
    go 语言编程 --许式伟
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昕光xg

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值