使用golang的channel的坑

很多时候我们经过使用有缓冲channel作为通信控制的功能,以至有一些误解和坑出现。

误解一:有缓存channel是顺序的

执行下面代码。

package mainimport (    "time"
    "math/rand")func main(){
    cache:=make(chan int,4)    go func() {        for i:=0;i< 10;i++ {
            cache<-i
        }
    }()    go getCache(cache)    go getCache(cache)    go getCache(cache)
    time.Sleep(3*time.Second)
}func getCache(cache <-chan int)  {    for  {        select {        case i:=<-cache:            println(i)
            time.Sleep(time.Duration(rand.Int31n(100))*time.Millisecond)
        }
    }

}

多执行几次看看结果,并不是每一次都是可以顺序输出的,有缓存channel是乱序的。因为这里让一些同学误解了,我在此多解释一下。
针对通道的发送和接收操作都是可能造成相关的goroutine阻塞。试想一下,有多个goroutine向同一个channel发送数据而被阻塞,如果还channel有多余的缓存空间时候,最早被阻塞的goroutine会最先被唤醒。也就是说,这里的唤醒顺序与发送操作的开始顺序是一致的,对接收操作而言亦为如此。无论是发送还是接收操作,运行时系统每次只会唤醒一个goroutine。 而这里的乱序是指,如果像使用channel缓存中多个goroutine实现顺序是正确的,因为每一个goroutine抢到处理器的时间点不一致,所以不能保证顺序。

误解二:channel缓存的大小就是并发度

如下代码。

package mainimport (	"fmt"
	"sync"
	"time")var wg = sync.WaitGroup{}func main() {
	wg.Add(2)
	bf := make(chan string, 64)	go insert(bf)	go get(bf)
	wg.Wait()
}func insert(bf chan string) {
	str := "CockroachDB 的技术选型比较激进,比如依赖了 HLC 来做事务的时间戳。但是在 Spanner 的事务模型的 Commit Wait 阶段等待时间的选择,CockroachDB 并没有办法做到 10ms 内的延迟;CockroachDB 的 Commit Wait 需要用户自己指定,但是谁能拍胸脯说 NTP 的时钟误差在多少毫秒内?我个人认为在处理跨洲际机房时钟同步的问题上,基本只有硬件时钟一种办法。HLC 是没办法解决的。另外 Cockroach 采用了 gossip 来同步节点信息,当集群变得比较大的时候,gossip 心跳会是一个非常大的开销。当然 CockroachDB 的这些技术选择带来的优势就是非常好的易用性,所有逻辑都在一个 binary 中,开箱即用,这个是非常大的优点。"
	for i := 0; i < 10000000; i++ {
		bf <- fmt.Sprintf("%s%d", str, i)
	}
	wg.Done()
}func sprint(s string) {
	time.Sleep(1000 * time.Millisecond)
}func get(bf chan string) {	for {		go func() {			select {			case str := <-bf:
				sprint(str)			case <-time.After(3 * time.Second):
				wg.Done()
			}

		}()
	}
}

很多同学乍一看以为定义了

bf := make(chan string, 64)

就是说该程序的并发度控制在了64,执行就会发现内存一直在增长。 因为get()函数中启动的goroutine会越来越多,因为get()每读取一个数据,insert()就会往channel插入一条数据,此时并发度就不是64了。 需要修改为:

package mainimport (	"fmt"
	"sync"
	"time")var wg = sync.WaitGroup{}func main() {
	wg.Add(2)
	bf := make(chan string, 64)	go insert(bf)	//go get(bf)
    for i:=0;i<64;i++ {        go get1(bf)
    }

	wg.Wait()
}func insert(bf chan string) {
	str := "CockroachDB 的技术选型比较激进,比如依赖了 HLC 来做事务的时间戳。但是在 Spanner 的事务模型的 Commit Wait 阶段等待时间的选择,CockroachDB 并没有办法做到 10ms 内的延迟;CockroachDB 的 Commit Wait 需要用户自己指定,但是谁能拍胸脯说 NTP 的时钟误差在多少毫秒内?我个人认为在处理跨洲际机房时钟同步的问题上,基本只有硬件时钟一种办法。HLC 是没办法解决的。另外 Cockroach 采用了 gossip 来同步节点信息,当集群变得比较大的时候,gossip 心跳会是一个非常大的开销。当然 CockroachDB 的这些技术选择带来的优势就是非常好的易用性,所有逻辑都在一个 binary 中,开箱即用,这个是非常大的优点。"
	for i := 0; i < 10000000; i++ {
		bf <- fmt.Sprintf("%s%d", str, i)
	}
	wg.Done()
}func sprint(s string) {
	time.Sleep(1000 * time.Millisecond)
}func get1(bf chan string)  {    for {        select {        case str := <-bf:
            sprint(str)        case <-time.After(3 * time.Second):
            wg.Done()
        }
    }
}
时间: 2024-10-12 06:30:39

使用golang的channel的坑的相关文章

golang控制channel的出入口

golang控制channel的出入口 我们常常使用channel来在多个goroutine之间做数据通讯,但是chan作为函数的入参我们应该怎么写呢?也许有人觉得这个问题比较傻,不过这个还真的是我今天才知道的. 首先我们看看下面的代码: func main() { c := make(chan int) go in(c) go out(c) time.Sleep(time.Second) } func in(c chan int) { for i := 0; i < 10; i++ { c <

go的变量redeclare的问题,golang的一个小坑

go的变量声明有几种方式: 1 通过关键字 var 进行声明 例如:var i int   然后进行赋值操作 i = 5 2 最简单的,通过符号 := 进行声明和赋值 例如: i:=5 golang会默认它的类型 下面看一段代码,我们先声明一个变量a,然后再重新声明变量a,b,在这个函数中,变量a被声明了2次,成为a的重声明(redeclare),执行结果为23 package main import ( "fmt" ) func main(){ a:=1 a,b:=2,3 fmt.P

深入学习golang(2)—channel

Channel 1. 概述 “网络,并发”是Go语言的两大feature.Go语言号称“互联网的C语言”,与使用传统的C语言相比,写一个Server所使用的代码更少,也更简单.写一个Server除了网络,另外就是并发,相对python等其它语言,Go对并发支持使得它有更好的性能. Goroutine和channel是Go在“并发”方面两个核心feature. Channel是goroutine之间进行通信的一种方式,它与Unix中的管道类似. Channel声明: ChannelType = (

golang中channel的超时处理

并发中超时处理是必不可少的,golang没有提供直接的超时处理机制,但可以利用select机制来解决超时问题. func timeoutFunc() { //首先,实现并执行一个匿名的超时等待函数 timeout := make(chan bool, 1) go func() { time.Sleep(1e9) //等待1秒钟 timeout <- true }() //然后,我们把timeout这个channel利用起来 select { case <- ch: //从ch中读到数据 cas

golang goroutine channel [fmt.Println=&gt;String]

初识golang,这部分也不是很了解,百度了一下,做个小记录 goroutine是golang用来做并发处理的,使用起来很简单  go func(){...}(),就是看起来随便用一般而容易go的滥用,所以使用时要仔细斟酌才好. channel 大约是用来在线程间传递数据的,主线程开通一个channel,goroutine往channel中存入内容 1.channel只能用make创建 c := make(chan int) 2.channel中存入数据 c<-2  //把2存入到channel

如何优雅的关闭golang的channel

How to Gracefully Close Channels,这篇博客讲了如何优雅的关闭channel的技巧,好好研读,收获良多. 众所周知,在golang中,关闭或者向已关闭的channel发送数据都会引发panic. 谨遵优雅关闭channel的原则 不要在接受一端关闭channel 不要在有多个并发的senders中关闭channel.反过来说,如果只有一个协程充当sender,那么我们可以在这个sender协程内关闭掉channel. 一个简单的方法 SafeClose type M

Golang的channel使用以及并发同步技巧

在学习<The Go Programming Language>第八章并发单元的时候还是遭遇了不少问题,和值得总结思考和记录的地方. 做一个类似于unix du命令的工具.但是阉割了一些功能,这里应该只实现-c(统计total大小) 和-h(以human比较容易辨识的显示出来)的功能. 首先我们需要构造一个 能够返回FileInfo信息数组的函数,我们把它取名为dirEntries: func dirEntries(dir string) []os.FileInfo { entries, er

golang 之 channel

channel的机制是先进先出 无缓冲的channel: 如果你给channel赋值了,那么必须要读取它的值,不然就会造成阻塞. chreadandwrite :=make(chan int) chonlyread := make(<-chan int) //创建只读channel chonlywrite := make(chan<- int) //创建只写channel 有缓冲的channel: 发送方会一直阻塞直到数据被拷贝到缓冲区: 如果缓冲区已满,则发送方只能在接收方取走数据后才能从阻

golang闭包里的坑

介绍 go的闭包是一个很有用的东西.但是如果你不了解闭包是如何工作的,那么他也会给你带来一堆的bug.这里我会拿出Go In Action这本书的一部分代码,来说一说在使用闭包的时候可能遇到的坑.全部的代码在github上. 闭包的坑 首先看一段代码: search/search.go 29 // Launch a goroutine for each feed to find the results. 30 for _, feed := range feeds { 31 // Retrieve