【go语言 | 第5篇】channel——多个goroutine之间通信

文章目录

channel的定义和使用

channel 用于多个 goroutine 之间的通信。

go 复制代码
package main

import "fmt"

func main() {
	// 创建一个 channel
	c := make(chan int)

	go func() {
		defer fmt.Println("goroutine结束!")

		fmt.Println("goroutine正在运行")
		// 将数据发送到 channel 中
		c <- 100
	}()

	// 从 channel 中接收数据, 存入到 num
	num := <- c

	fmt.Println("num:", num)
	fmt.Println("main goroutine 结束")
}

为什么 main goroutine 中的执行会在 子goroutine 之后?

(1)main go 已经到达 num := <- c 时,如果 channel 中还没有存在 c,就会对 main go 进行阻塞。

(2)sub go 已经到达 c <- 100,如果 main go 还没有到达 num := <- c,因为此时的管道 c 是无缓冲的,就会对 sub go 进行阻塞。

channel------有缓冲和无缓冲同步

1. 无缓冲的channel

(1)在第1步,两个goroutine都到达通道,但哪个都没有开始执行发送或者接收。

(2)在第2步,左侧的goroutine将它的手伸进了通道,这模拟了向通道发送数据的行为。这时,这个goroutine会在通道中被锁住,直到交换完成。

(3)在第3步,右侧的goroutine将它的手放入通道,这模拟了从通道里接收数据。这个goroutine一样也会在通道中被锁住,直到交换完成。

(4)在第4步和第5步,进行交换,并最终,在第6步,两个goroutine都将它们的手从通道里拿出来,这模拟了被锁住的goroutine得到释放。两个goroutine现在都可以去做其他事情了。

2. 有缓冲的channel

go 复制代码
package main

import (
	"fmt"
	"time"
)

func main() {
	// 创建一个容量为3的 channel(有缓冲的channel)
	c := make(chan int, 3)

	go func() {
		defer fmt.Println("子go程结束!")

		for i := 0; i < 3; i++ {
			c <- i
			fmt.Println("子go程 i = ", i, "  元素个数:", len(c), "  元素个数:", cap(c))
		}	
	}()

	time.Sleep(1 * time.Second)

	for i := 0; i < 3; i++ {
		// 从 channel 中获取数据
		num := <- c
		fmt.Println("num =", num)
	}

	fmt.Println("main goroutine 运行结束")

}


(1)在第1步,右侧的goroutine正在从通道接收一个值。

(2)在第2步,右侧的这个goroutine独立完成了接收值的动作,而左侧的goroutine正在发送一个新值到通道里。

(3)在第3步,左侧的goroutine还在向通道发送新值,而右侧的goroutine正在从通道接收另外一个值。这个步骤里的两个操作既不是同步的,也不会互相阻塞。

(4)最后,在第4步,所有的发送和接收都完成,而通道里还有几个值,也有一些空间可以存更多的值。

channel------关闭channel

go 复制代码
package main

import "fmt"

func main() {
	c := make(chan int)

	go func() {
		for i := 0; i < 3; i++ {
			c <- i
		}

		// close 关闭 channel
		close(c)
	}()

	for {
		// ok 为 true 表示 channel 没有关闭, ok 为 false 表示 channel 已经关闭
		if data, ok := <- c; ok {
			fmt.Println("data: ", data) 
		} else {
			break
		}
	}

	fmt.Println("main goroutine 结束")

}

(1)channel不像文件一样需要经常去关闭,只有当你确实没有任何发送数据 了,或者想显式的结束range循环之类 的,才去关闭channel;

(2)关闭channel后,无法向channel再发送数据(引发panic错误后导致接收立即返回零值);

(3)关闭channel后,可以继续从channel接收数据;

(4)对于nil channel,无论收发都会被阻塞。

channel 与 range

go 复制代码
package main

import "fmt"

func main() {
	c := make(chan int)

	go func() {
		for i := 0; i < 3; i++ {
			c <- i
		}

		// close 关闭 channel
		close(c)
	}()

	/* for {
		// ok 为 true 表示 channel 没有关闭, ok 为 false 表示 channel 已经关闭
		if data, ok := <- c; ok {
			fmt.Println("data: ", data) 
		} else {
			break
		}
	} */

	// 可以使用 range 代替不断迭代的操作的cahnnel
	for data := range c {
		fmt.Println("data: ", data)
	}

	fmt.Println("main goroutine 结束")

}

channel 与 select

单流程下,一个go只能监控一个 channel 状态,select 可以完成监控多个 channel 的状态。

go 复制代码
package main

import (
	"fmt"
)

func method(c, quit chan int) {
	x, y := 1, 1

	for {
		select {
		case c <- x:
			// 如果 c 可写入,执行下面
			x = y
			y = x + y
		case <- quit:
			fmt.Println("quit.....")
			return 
		}
	}
}

func main() {
	c := make(chan int)
	quit := make(chan int)

	go func() {
		for i := 0; i < 6; i++ {
			fmt.Println(<-c)
		}

		quit <- 0
	}()

	method(c, quit)
}
相关推荐
余槐i1 小时前
PostgreSQL 函数索引报错 must be immutable:3种绕法与代价
数据库·后端·sql·postgresql·性能优化
需要8262 小时前
Spring Bean 生命周期:从实例化到销毁的完整钩子
java·后端·spring
死磕音视频3 小时前
让小模型做选择题:限定候选项并读取 logits 概率的入门实践(附 Python 代码)
开发语言·python·大模型·文本分类·transformers
凤山老林3 小时前
Spring Boot 接入国密 SM2/SM3/SM4 的一些实践记录
spring boot·后端·python
伯恩bourne4 小时前
Spring Boot Bean 注册的两种方式:@Component 家族 vs @Configuration + @Bean
java·spring boot·后端
张彦峰ZYF4 小时前
停顿短不等于延迟低:G1、ZGC 与低延迟服务的目标分野、代价账本与选型标定
后端·回收暂停、应用停止总时长·到达安全点、分配停滞·屏障吃吞吐、并发线程吃核心·并发窗口吃堆余量·分区式靠挑回收集兑现目标·染色指针式不支持压缩引用
pride.li5 小时前
第03章 Python 教程-基础语法:注释与变量
服务器·前端·后端·python·算法·计算机视觉
顽疲5 小时前
Java vue 养老系统源码详解:月账单 care_month_bill 与 Quartz 扣费 Job 实践
java·开发语言·vue.js·vue养老·springboot养老·java养老
Wang's Blog5 小时前
Java 项目实战: 交友项目 - 安装开发工具与环境准备
java·开发语言·交友
upala5 小时前
【仓颉语言入门 · 第二篇 第 6 课】
后端