GO语言学习--goroutine间访问channel阻塞与不阻塞(select中case)

        channel是Golang在语言层面提供的goroutine间的通信方式,channel主要用于进程内各goroutine间通信,了解channel结构,与goroutine访问机制,程序就能很灵活的实现并发编程。

channel的数据结构如下:

type hchan struct {

    qcount  uint          // 当前队列中剩余元素个数

    dataqsiz uint          // 环形队列长度,即可以存放的元素个数

    buf      unsafe.Pointer // 环形队列指针

    elemsize uint16        // 每个元素的大小

    closed  uint32            // 标识关闭状态

    elemtype *_type        // 元素类型

    sendx    uint          // 队列下标,指示元素写入时存放到队列中的位置

    recvx    uint          // 队列下标,指示元素从队列的该位置读出

    recvq    waitq          // 等待读消息的goroutine队列

    sendq    waitq          // 等待写消息的goroutine队列

    lock mutex              // 互斥锁,chan不允许并发读写

}

从channel读数据,如果channel缓冲区为空或者没有缓冲区,当前goroutine会被阻塞。 向channel写数据,如果channel缓冲区已满或者没有缓冲区,当前goroutine会被阻塞。

被阻塞的goroutine将会挂在channel的等待队列中:

因读阻塞的goroutine会被向channel写入数据的goroutine唤醒;

因写阻塞的goroutine会被从channel读数据的goroutine唤醒;

下图展示了一个没有缓冲区的channel,有几个goroutine阻塞等待读数据:


一般情况下,recvq和sendq至少有一个为空,上图recvq中取数据的在火急火燎排队等,buf缓存区一有数据就会被拿走,怎么会有sendq排队不去buf缓存区存放数据了,实际上这种情况数据根本不会要存到buf缓存区的,直接从你sendq队列goroutine的数据区就取走了。相似的如果recvq队列为空,sendq队列排队的情况也可同样的机制。

从上面了解goroutine要从channel访问数据,如果遇到为空或满了的情况,该channel就要排队,但也有例外,这情况下,它只是来看一下是否能访问(队列是否为空),如果不能,它下次再来。这个就可以给我们站台排队买票来理解:

package main

import (

"fmt"

"time"

)

func releaseTickets(ticketNumchan int) {

for {

time.Sleep(1 * time.Second)//假设隔一秒钟放一张票

      ticketNum <-1

  }

}

func ticketTout(ticketNumchan int) {

for {

time.Sleep(100 * time.Millisecond)//黄牛党,0.1秒轮询

      fmt.Println("黄牛:我排队咯")

tickets := <- ticketNum//因为放票时距是1秒,部分黄牛也会阻塞

      fmt.Printf("黄牛:又买到%d张票啦\n",tickets)

}

}

func main() {

var ticketBuf = make(chan int,10)

go releaseTickets(ticketBuf)

go ticketTout(ticketBuf)

ticket :=0

  for {

if ticket ==1 {

break //普通购票者买到票了,回家

      }

time.Sleep(300 * time.Millisecond)//普通购票者,0.3秒轮询

      select {

case ticket = <- ticketBuf ://队伍太长了,不想排队,等下再来看看

        fmt.Printf("普通购票者:也买到%d张票啦\n",ticket)//基本不可能

      default:

fmt.Println("普通购票者:唉,不排队就买不到票")

}

}

}

这种情况下,普通购票者不进队列排队,而黄牛不停的进入队列排队,所以买不着票。

case ticket = <- ticketBuf : 

这个case语句执行<- ticketBuf (抢票)是不进入队列,如果不行就返回了。

select的case语句读channel不会阻塞,尽管channel中没有数据。这是由于case语句编译后调用读channel时会明确传入不阻塞的参数,此时读不到数据时不会将当前goroutine加入到等待队列,而是直接返回。

那么如何才能买到票了,又不想把身份证交给黄牛,同时黄牛加价也吓人。有一种办法是叫自己的有时间的朋友(代理人)排队代买啦,然后你就不停打电话问朋友是否买到票,这样就能实现不用在现场排队,又能买到票,落下心来,好安排计划。

package main

import (

"fmt"

"time"

)

func releaseTickets2(ticketNumchan int) {

for {

time.Sleep(1 * time.Second)//假设隔一秒钟放一张票

      ticketNum <-1

  }

}

func ticketTout2(ticketNumchan int) {

for {

time.Sleep(100 * time.Millisecond)//黄牛党,0.1秒轮询

      fmt.Println("黄牛:我排队咯")

tickets := <- ticketNum//因为放票时距是1秒,部分黄牛也会阻塞

      fmt.Printf("黄牛:又买到%d张票啦\n",tickets)

}

}

func proxyMan(ticketNumchan int,ticketchan int) {

for {

time.Sleep(2 * time.Second)//佛系代理人,2秒轮询,

                                  //但只要排队还是能抢到票的

      ticket <- <-ticketNum//代理人阻塞排队

  }

}

func main() {

var ticketBuf = make(chan int,10)

var mobAboutTicket = make(chan int,2)

go releaseTickets2(ticketBuf)

go ticketTout2(ticketBuf)

go proxyMan(ticketBuf,mobAboutTicket)//代理人进程

  ticket :=0

  for {

if ticket ==1 {//电话通道里告诉已经买到票了

        fmt.Println("普通购票者:朋友帮我买到票了")

break //普通购票者买到票了,回家

      }

time.Sleep(300 * time.Millisecond)

select {

//case <- ticketBuf : //委托给代理人了,自己就不在现场争通道了

                          //代理人在代理人的进程中随队争,ticketBuf通过参数传给ticketNum

      case ticket = <- mobAboutTicket ://与代理的专用通道,这个不会有其它人来抢了,

                                //非阻塞模式访问都可取到,相当于不停打电话问

                                //因为这个电话两人专用电话,不存在打不进

        fmt.Printf("代理购票者:也买到%d张票啦\n",ticket)

default:

fmt.Println("普通购票者:唉,不排队就买不到票")

}

}

}

以上部分内容来自Go语言中文网,里面一些概念讲得比较清楚,可可去看看。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352