范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

彻底明白Go语言的Channel了

  channel 概述
  Hello 大家好!我们又见面了,本文我们一起搞懂 Go语言中 channel 及channel底层实现和一些常见面试题。
  channel 是 Go 语言内建的first-class类型,也是 Go 语言与众不同的特性之一。先看一个应用场景,比如协程A执行过程中需要创建子协程A1、A2 ... An,协程A创建完子协程后就等待子协程退出,这样场景的Go为我们提供三种解决方案: 使用 channel 控制子协程 waitGroup 信号量机制控制子协程 Context 使用上下文控制子协程
  它们三种解决方案各有优劣,比如:使用 channel 来控制子协程优点实现简单,缺点就是当需要大量创建协程时就需要有相同数量的 channel,这样对于子协程继续派生出来的协程就不方便控制。
  首先,想一想,为什么 Go 引入channel,及channel能为我们提供解决怎么样的问题?了解 channel 可以从CSP 模型了解,CSP模型是 Tony Hoare 在1978年发表的论文中,CSP主要讲一种并发编程语言, CSP 允许使用进程组建来描述系统,它们独立运行,并且只通过消息传递的方式通信。 这篇论文是对 Go 创始人Rob Pike 对Go语言并发设计的产生巨大影响,最后通过引入 channel 这种新的类型,来实现 CSP 的思想。
  在使用 channel 类型时候,你无需引入某个包,就能使用它,它就是 Go 语言内置的类型,不像其他库,你必须的引入sync包货atomic 包才能使用它们。 channel 基本用法
  channel 很多人常说所谓的  通道 ,那么通道也是我们生活中类似的管道,用来传输东西。计算机可以使用通道来进行通信,在Go语言中,常见的允许 Goroutine 之间进行数据传输。在传输中,你需要明确一些规则,首先,每个通道只允许交换指定类型的数据,也称为通道元素类型(类似生活中,自家水管只允许运输能喝的水,运输汽油,你需要使用另一个管道)。在 Go 语言中,使用chan 关键字来声明一个新通道,使用 close() 函数来关闭通道。
  定义好通道,可以往 channel 发送数据,从channel中接收数据,你还可以定义  只能接受 、 只能发送 、 也可以接受又可以发送  三种类型。
  声明通道类型格式如下 : var 变量 chan 元素类型
  例子 var ch1 chan int   // 声明一个传递整型的通道 var ch3 chan []int // 声明一个传递int切片的通道
  创建 channel  : var ch chan string fmt.Println(ch) //输出:
  注:通道是引用类型,通道类型的空值是nil。
  声明的通道后需要使用make函数初始化之后才能使用,以channel 的缓冲区大小也是可选的。 func main() {   //初始化通道,缓冲区大小为2   ch := make(chan int,2)   ch <- 1   ch <- 2   ch <- 3 //会报错,因为缓冲区只允许大小为2   x1 := <- ch   x2:= <- ch   fmt.Println(x1)   fmt.Println(x2) }
  (1)发送数据
  往 chan 中发送一个数据使用  ch <-  ,格式如下: ch <- 1 //把1发送到ch中
  (2)接收数据
  从 chan 中接收一条数据使用 <-ch  ,接收数据也是一条语句,课时如下: x := <- ch //从ch中接收只并赋值给变量x1。 <- ch  //从ch中接收值,忽略结果
  注 :关于关闭通道需要注意的事情是,只有在通知接收方goroutine所有的数据都发送完毕的时候才需要关闭通道。通道是可以被垃圾回收机制回收的,它和关闭文件是不一样的,在结束操作之后关闭文件是必须要做的,但关闭通道不是必须的。 channel 实现原理
  这节,面试官会问:channel 的底层实现?
  接下来,一起学习 chan 的数据结构及初始化,还有三个最重要操作方法分为是  send  、 recv   和  close  ,认真学习 channel 底层原理的实现。
  源码目录位置: runtime/chan.go  ,以下贴出chan 类型的数据结构如下: type hchan struct {   qcount   uint           // total data in the queue(循环队列元素数量)   dataqsiz uint           // size of the circular queue(循环队列大小)   buf      unsafe.Pointer // points to an array of dataqsiz elements(循环队列指针)   elemsize uint16 //chan中元素大小   closed   uint32 //是否已经close   elemtype *_type // element type(chan元素类型)   sendx    uint   // send index(send在buf中索引)   recvx    uint   // receive index(recv在buf中索引)   recvq    waitq  // list of recv waiters(receive的等待队列)   sendq    waitq  // list of send waiters(sender等待队列)    // lock protects all fields in hchan, as well as several   // fields in sudogs blocked on this channel.   //   // Do not change another G"s status while holding this lock   // (in particular, do not ready a G), as this can deadlock   // with stack shrinking.   lock mutex //互斥锁,保护所有字段,上面注释已经讲得非常明确了 }
  (1)chan 初始化
  Go 在编译时,会根据容量大小选择调用 makechan64,还是 makechan。通过源码我们可以知道,makechan64只做了size检查,然后底层最终还是调用makechan实现的。(makechan 的目标就是生成 hchan 对象)
  Makechan 到底做了什么,源码如下: func makechan(t *chantype, size int) *hchan {   elem := t.elem   //编译器会检查类型是否安全   // compiler checks this but be safe.   if elem.size >= 1<<16 {//是否 >= 2^16     throw("makechan: invalid channel element type")   }   if hchanSize%maxAlign != 0 || elem.align > maxAlign {     throw("makechan: bad alignment")   }   mem, overflow := math.MulUintptr(elem.size, uintptr(size))   if overflow || mem > maxAlloc-hchanSize || size < 0 {     panic(plainError("makechan: size out of range"))   }   var c *hchan   switch {   case mem == 0:     // chan的size或元素的size为0,就不必创建buf     c = (*hchan)(mallocgc(hchanSize, nil, true))     // 竞争检测器使用此位置进行同步     c.buf = c.raceaddr()   case elem.ptrdata == 0:     // 元素不是指针,分配一块连续的内存给hchan数据结构和buf     c = (*hchan)(mallocgc(hchanSize+mem, nil, true))     // hchan数据结构后面紧接着就是buf     c.buf = add(unsafe.Pointer(c), hchanSize)   default:     // Elements contain pointers.     c = new(hchan)     c.buf = mallocgc(mem, elem, true)   }   // 将元素大小、类型、容量都记录下来   c.elemsize = uint16(elem.size)   c.elemtype = elem   c.dataqsiz = uint(size)   lockInit(&c.lock, lockRankHchan)   if debugChan {     print("makechan: chan=", c, "; elemsize=", elem.size, "; dataqsiz=", size, " ")   }   return c }
  总结:channel 底层是根据不同容量和元素类型,来分配不同的对象来初始化 chan 对象的字段,及返回 hchan 对象。
  (2)send方法
  send() 是往chan 发送数据,方法大致分为6个部分,源码如下:
  第一部分: func chansend1(c *hchan, elem unsafe.Pointer) {   chansend(c, elem, true, getcallerpc()) } func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {   //第一部分   if c == nil {     if !block {       return false     }     //阻塞休眠     gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2)     throw("unreachable")   }   // 部分代码省略… }
  send最开始,首先是判断,如果 chan 为nil 的话,调用gopark 进行阻塞休眠,这时,调用者永远阻塞住了。那么这个代码 throw("unreachable")  不会执行的。
  第二部分: // 第二部分,如果chan没有被close,并且chan满了,直接返回 if !block && c.closed == 0 && full(c) {     return false   }
  第三部分: // 第三部分,chan已经被close的情景 lock(&c.lock)   if c.closed != 0 {     unlock(&c.lock)     panic(plainError("send on closed channel"))   }
  第三部分,如果 chan 已经被close,你再往里面发送数据的话会出现Panic。如下代码会出现Panic。 ch := make(chan int,1) close(ch) ch <- 1
  第四部分: //第四部分:如果有recvq接收者就说明buf中没数据,因此直接从sender送到receizver中 if sg := c.recvq.dequeue(); sg != nil {     // Found a waiting receiver. We pass the value we want to send     // directly to the receiver, bypassing the channel buffer (if any).     send(c, sg, ep, func() { unlock(&c.lock) }, 3)     return true   }
  第五部分: // 第五部分,buf还没满 if c.qcount < c.dataqsiz {     // Space is available in the channel buffer. Enqueue the element to send.     qp := chanbuf(c, c.sendx)     if raceenabled {       racenotify(c, c.sendx, nil)     }     typedmemmove(c.elemtype, qp, ep)     c.sendx++     if c.sendx == c.dataqsiz {       c.sendx = 0     }     c.qcount++     unlock(&c.lock)     return true   }
  第五部分说明当前没有 receiver,需要把数据放入到 buf 中,放入之后,就成功返回了。
  第六部分: // 第六部分:buf已满 //chansend1不会进入if块里,因为chansend1的block=true if !block {     unlock(&c.lock)     return false   }
  第六部分是处理 buf 满的情况。如果 buf 满了,发送者的 goroutine 就会加入到发送者的等待队列中,直到被唤醒。这个时候,数据或者被取走了,或者 chan 被 close 了。
  (2)recv
  在处理从 chan 中接收数据,源码如下:
  第一部分: if c == nil { //判断chan为nil     if !block {       return     }     gopark(nil, nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2)     throw("unreachable")   }
  从chan 获取数据时,如果 chan 为 nil,调用者会被永远阻塞。
  第二部分:   // 第二部分, block=false且c为空     if !block && empty(c) {       ......     }
  第三部分:   lock(&c.lock)//加锁,返回时释放锁   //第三部分,c已经被close,且chan为空empty   if c.closed != 0 && c.qcount == 0 {     if raceenabled {       raceacquire(c.raceaddr())     }     unlock(&c.lock)     if ep != nil {       typedmemclr(c.elemtype, ep)     }     return true, false   }
  如果 chan 已经被 close 了,并且队列中没有缓存的元素,那么返回 true、false。
  第四部分: // 第四部分,如果sendq队列中有等待发送的sender if sg := c.sendq.dequeue(); sg != nil {     // Found a waiting sender. If buffer is size 0, receive value     // directly from sender. Otherwise, receive from head of queue     // and add sender"s value to the tail of the queue (both map to     // the same buffer slot because the queue is full).     recv(c, sg, ep, func() { unlock(&c.lock) }, 3)     return true, true   }
  当处理 buf 满的情况。这个时候,如果是 unbuffer 的 chan,就直接将 sender 的数据复制给 receiver,否则就从队列头部读取一个值,并把这个 sender 的值加入到队列尾部。
  第五部分:处理没有等待的 sender 的情况。这个是和 chansend 共用一把大锁,所以不会有并发的问题。如果 buf 有元素,就取出一个元素给 receiver。
  第六部分: 处理 buf 中没有元素的情况。如果没有元素,那么当前的 receiver 就会被阻塞,直到它从 sender 中接收了数据,或者是 chan 被 close,才返回。
  (3)close
  通过 close函数,你可以把 chan 关闭,底层调用 closechan 方法执行。具体源码和上面两个位置一样。
  (4)使用channel 踩的坑
  常见的错误 panic 和 goroutine 泄漏
  示例1: ch := make(chan int,1) close(ch) ch <- 1
  往 chan 添加,但close了,会出现Panic,解决就是不close。
  示例2:   ch := make(chan int,1)   ch <- 1   close(ch)   <- ch   close(ch)
  从 chan 取出数据,但 close了,也会Panic
  (5)介绍 panic 和 recover
  Panic 和 recover 也是面试点,简单注意
  Panic :在 Go 语言中,出现 Panic 是代表一个严重问题,意味着程序结束并退出。在 Go 中 Panic 关键字用于抛出异常的。类似 Java 中的 throw。
  recover:在 Go 语言中,用于将程序状态出现严重错误恢复到正常状态。当 发生 Panic 后,你需要使用recover 捕获,不捕获程序会退出。类似 Java 的 try catch 捕获异常。
  你的每次 点赞+收藏+关注,是我创作最大动力。加油,奋斗永远都在路上!

python的print命令print命令用来输出指定对象的内容,语法为print(对象1,对象2,sep分隔符,end终止符)1。对象1,对象2,print命令可以一次打印多个对象数据,对象之间以逗号,分开陈磊已变道,拼多多未偏航采写陈纪英在拼多多创始人黄峥,把董事长职位交接给陈磊的那一天,他可能会想起2002年的一场相逢。美国威斯康星州首府麦迪逊,威斯康辛大学麦迪逊分校所在地,即将在此开启硕士生涯的新生黄5G速联全新UI展开讲讲DiLink4。0(5G)8月29日成都车展,比亚迪DiLink4。0(5G)智能网联系统震撼发布。一起来看看它隐藏的那些黑科技车载5G无限畅快DiLink4。0(5G)的5G技术率先在汉车型上搭载,汉EV太任性!王思聪最新电脑配置曝光,早在上个世纪个人拥有一台电脑已经算是一件很了不起的事情了,但是在如今电脑已经越来越普及,一台家用普通电脑价格超过一万就可以算是豪玩了。作为任性富二代王校长王思聪可不是这么认为的,近iPhone11Pro最新售价确认,标配版跌至新低,可以不等iPhone13了市面上的优质旗舰手机有很多,而许多消费者们之所以没有入手,其实主要的原因就是因为代价太大,因为优质旗舰手机的价格往往也非常高,而一款手机好不好是一个方面,值不值得买就是另一回事了,为外卖小哥缴纳社保美团未来何去何从?最近一段时间,各项政策纷纷出台,从教育到住房,从外卖到娱乐圈,都出现了不少的新规定。对于互联网巨头的美团,在这次风暴中也受到了巨大的影响。其中影响最大的当属反垄断和骑手社保问题了。2021年8月编程语言排行榜今年如果说哪个专业最受高考考生的喜爱,非计算机和软件行业莫属计算机和软件行业又离不开编程语言的支持,那么到底哪种语言最流行?世界上都有哪些语言深受开发者喜爱?近日HelloGitH京东上线二手交易平台鲸置9月2日消息,据Tech星球报道,近日,京东上线了一款名为鲸置的二手交易平台,目前,该应用已上架苹果AppStore和Android应用商城。官方介绍显示,鲸置是京东集团旗下二手闲2021年华为手机哪款性价比最高?20003000400050007500元推荐全文长达1万3千字,建议先关注我,可随时在我的主页查看更多数码选购指南硬核技巧科普如果对你有帮助,你的一个小小赞同就能让我开心半天。华为终端旗下原本拥有华为手机和荣耀手机两大品牌,联想拯救者3Pro有望首发898,165Hz6000mAh,完美电竞旗舰来袭联想是从2003年开始才进入智能手机领域,与小米华为OV相比联想起步较晚。联想虽是后来者,但联想手机的产品线还是比较广的,高中低端机型都有,有点像如今的小米产品比较齐全。联想做手机花呗,借呗逾期了,这两天都有接到电话说叫我应诉,我该怎么办?首先要做的是不要慌,拿出电话拨打12368查询是否被真正起诉,如果被起诉准备好相关资料,证件积极应诉不要逃避,如没被起诉那就是催收的一些套路如果你有证据可以到投诉它们。接下来我说下
移动电源PSE认证办理周期移动电源PSE认证办理周期。现如今移动电源(充电宝)不仅在我们销量的非常火热,在日本也受到了很多人的广泛欢迎。这也使很多企业看到了良好的商机。移动电源是一种个人可随身携带,自身能储世界杯亚洲区预选赛十二强赛中国VS沙特,国足需要如何把握战局如何把握战局我是不太清楚了。但是赛后新闻应该这样。沙特在和中国队比赛时创造了有记录以来最高的控球率,达到了99,而中国队全场比赛仅有四次进入沙特队半场,一次是半场开球,三次丢球后开汽车空调两态压力开关是如何进行工作的?汽车空调压力开关安装在汽车空调管路中,检测制冷循环系统的压力,当压力异常时启动相应的保护电路,防止造成空调系统的损坏。压力开关安装在空调系统高压端,一般安装在空调管路高压端上或冷凝汽车空调三态压力开关是如何进行工作的?汽车空调压力开关安装在汽车空调制冷管路中,根据检测到的压力进行切断压缩机和提高冷凝风扇风速,三态压力开关安装在空调系统高压端,一般安装在空调管路高压端上或冷凝器出口处。三态压力开关汽车空调内控变排量压缩机是如何进行工作的?目前越来越多的汽车配置变排量压缩机,变排量压缩机又可以分为两种。一种是内控变排量压缩机,另外一种是外控变排量压缩机。本文重点讲解一下内控变排量压缩机是如何进行工作的。内控变排量压缩重庆三峡博物馆游览重庆中国三峡博物馆,又名重庆博物馆,是首批国家一级博物馆中央地方共建国家级博物馆。位于重庆市渝中区,与重庆人民大礼堂正对。其前身为1951年3月成立的西南博物院,1955年6月更名投影机TELEC认证办理流程认证也称MIC认证,TELEC是日本无线电设备符合性认证的主要的注册认证机构,MIC是日本管制无线电射频设备的政府机构,MIC是日本总务省,负责编制法规标准,TELEC只是为MIC俄罗斯eac认证办理流程和准备资料俄罗斯产品安全认证是俄罗斯政府为保护广大消费者人身和动植物生命安全,保护环境保护国家安全,加强产品质量管理依照法律法规实施的一种产品合格评定制度。它要求产品必须符合俄罗斯联邦国家标音箱话筒CE检测认证办理流程音箱话筒CE检测认证办理流程。音箱话筒设备我们还是挺常见的,一些商店门口,广场舞上随处可见,通俗的讲就是指音箱主机箱体或低音炮箱体内自带功率放大器,对音频信号进行放大处理后由音箱本无线投影仪TELEC认证办理流程无线投影仪是以无线传输技术为核心,通过无线传输功能实现投影的投影仪。现如今,无线投影仪在许多事业单位和企业单位都发挥了它重大的作用。但是一般如无线投影仪这种无线产品无论是出口日本还LED灯PSE认证办理标准及流程LED灯PSE认证办理标准及流程。PSE认证是日本强制性安全认证,用以证明电机电子产品已通过日本电气和原料安全法(DENANLaw)或国际IEC标准的安全标准测试。日本的DENTO