go基础第一篇:并发之channel
go不推荐使用共享内存机制,而是推荐使用CSP并发模型机制。
CSP全称是Communicating Sequential Processes,可以翻译成通信顺序进程(Communicating翻译成通信的意思)。简单解释就是,CSP模型由并发执行的实体组成,实体之间通过发送消息进行通信,发送消息时使用的是通道。CSP模型的关键是通道,而不是发送消息的实体。口诀是Do not communicate by sharing memory,instead,share memory by communicating。不要以共享内存的方式来通信,相反,要通过通信来共享内存。
go的CSP并发模型,是通过goroutine和通道channel实现的。goroutine是并发执行的实体,底层使用协程(coroutine)实现并发。coroutine运行在用户态,从而避免了内核态和用户态的切换导致的成本。
channel可分为两种:
一种是非缓冲channel,通道里无法存储数据,如果没有消费者,生产者协程会阻塞,反之,如果没有生产者,消费者协程会阻塞。
另一种是缓冲channel,创建时要指定通道的大小,未达到指定大小时,生产者可以任意往通道中放数据,不会阻塞,直到达到指定大小后,阻塞。当通道中有数据时,消费者可以正常取,当通道中没数据且生产者不再生产时,消费者协程会阻塞。
新建channel也是用make关键字:
新建非缓冲channel,语法是make(chan type)或者make(chan type, 0),如ch := make(chan string),通道想放什么类型的数据都行,如int、string,甚至可以是interface{}。
新建缓冲channel,语法是make(chan type, value),value是个大于0的整数,如ch := make(chan string, 10),即只需要在make函数中添加第二个参数,指定channel的大小。
往通道中放数据、从通道中取数据,都要用到一个特殊的操作符<-,小于号后面跟一个中横线,好像一个左箭头,箭头的指向就是数据的流向,往通道中放数据,箭头要指向通道,通道<-,从通道中取数据,箭头要背向通道,<-通道。
func service() string { time.Sleep(time.Second * 1) return "Done" } func otherTask() { fmt.Println("working on something else") time.Sleep(time.Second * 2) fmt.Println("Task is done") } func main() { start := time.Now() fmt.Println(service()) otherTask() fmt.Println("cost", time.Since(start)) }
上例中,service函数需要执行1s,otherTask函数需要执行2s,上面这个程序,会先执行service函数,再执行otherTask函数,总耗时在3s。
现在我们要求总耗时2s,且能够在主协程中获取service函数的返回值。观察otherTask函数和service函数,otherTask函数和service函数的返回值没有关系,所以可以并行执行service函数和otherTask。把service函数另起一个协程执行,那么如何在主协程中获取子协程中的值呢?建个channel就好了,子协程往channel中放,主协程从channel中取。
改造如下:
func service() string { time.Sleep(time.Second * 1) return "Done" } func asyncService(ch chan string) { go func() { result := service() ch <- result }() } func otherTask() { fmt.Println("working on something else") time.Sleep(time.Second * 2) fmt.Println("Task is done") } func main() { start := time.Now() ch := make(chan string) asyncService(ch) otherTask() result := <-ch fmt.Println("result= " + result) fmt.Println("cost", time.Since(start)) }
如上,在主协程中创建了一个channel,在子协程中执行service函数,并将返回值放入主协程创建的channel,在主协程中就可以从channel中取数据了。用的channel是一个非缓冲channel,子协程执行完service函数后,把service函数返回值放到channel时会阻塞,因为otherTask函数还没执行完,主协程还不会从channel中取数据,直到otherTask函数执行完,主协程从channel中取数据,子协程才能把service函数返回值放到channel,主协程取出并使用。
我们还可以优化一下,把子协程解放出来,没必要阻塞1s,占用资源。只需改动一点,把ch := make(chan string)改成ch := make(chan string, 1)就可以了。代码省略。
channel关闭 close(ch)。关闭一个已经关闭的channel会panic。
v, ok := <-ch
ok为true时,不确定channel是否已关闭。因为channel关闭后,假如channel还有数据没有取完,取数据时ok依旧为true,且v是有效数据值,只有当有效数据都取完后,再去取,ok才为false,v的值是channel存放的数据类型的零值。
ok为false时,通道肯定已关闭。
向一个已关闭的channel发送消息会panic。
可以用for...range遍历channel,当channel关闭时,for循环自动退出。
没有内置的函数可以判断channel是否已关闭。
向一个nil channel发送数据或者从中取数据,都只会阻塞,不会panic,关闭一个nil channel时会panic。
channel是线程安全的,面对并发问题,应首先想到channel。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】