【Go语言】【18】GO语言的select

一、select

Go语言引入了select关键字,其语法与switch非常类似,先看一个switch例子:


func main() {

var a int = 1

switch {

case a == 1:

fmt.Println("ok")

case a == 2:

fmt.Println("no ok")

default:

fmt.Println("default")

}

}

运行该程序,可以正常打印出“ok”

下面把switch替换为select:


func main() {

var a int = 1

select{

case a == 1:

fmt.Println("ok")

case a == 2:

fmt.Println("no ok")

default:

fmt.Println("default")

}

}

运行该程序抛出错误:a == 1 evaluated but not used

这是为何?是因为select中的case语句必须是一个IO操作!!!

即:switch中的case语句判断条件只要能比较就行,而select中的case语句判断条件必须是一个IO操作。

我们在《Go语言的并发》中说过Channel,从Channel中读取值和向Channel中写入值对应的操作都是IO操作。下面我们写一个关于select的例子:


func setValue(ch1 chan int, ch2 chan string) {

ch1 <- 1

ch2 <- "goroutine"

}

定义一个函数setValue(),入参为两个channel,该方法体内向channel 1中写入一个整形值、向channel 2中写入一个字符串;在并发章节我们说过:“当入以为channel时,就不是值传递了,而变成一个地址传递”。


func main() {

var ch1 chan int = make(chan int)

var ch2 chan string = make(chan string)

go setValue(ch1, ch2)

select {

case <-ch1:

fmt.Println("ch1 ok")

case <-ch2:

fmt.Println("ch2 ok")

default:

fmt.Println("default")

}

}

先定义两个channel类型的变量,使用make对其初始化;然后使用go关键字拉启一个goroutine;main所在goroutine继续向下走,开始执行select。

执行一下程序:

发现只打印了一个“default”,而没有打印“ch1 ok”或者“ch2 ok”,多执行几次结果一样,从并发的角度上考虑概率情况,这是不正常的。

您可能会想向ch1、ch2中写入数据,属于IO操作,可能会慢一些,当select执行完default时,IO操作依旧没有完成。我们验证一下,修改代码:


func main() {

var ch1 chan int = make(chan int)

var ch2 chan string = make(chan string)

go setValue(ch1, ch2)

select {

case <-ch1:

fmt.Println("ch1 ok")

case <-ch2:

fmt.Println("ch2 ok")

}

}

这里把default删除掉了,当执行到select时,程序会查看各个分支,由于没有default分支,此时若channel中没有内容,则main所在的goroutin会阻塞,至到ch1或者ch2中有内容为至。

执行一下该程序:

发现成功打印出"ch1 ok”,多运行几次依旧打印出“ch1 ok”,从没有打印出“ch2 ok”,这是为什么呢?

看过我前面章节的可能会想,由于目前使用的是go1.4版本,它并不支持多核并发,加之GO语言在执行时更倾向先让一个goroutine执行完(即我们常说的让领导先走:) ),下面我们再修改一下程序:


func main() {

runtime.GOMAXPROCS(runtime.NumCPU())  // 强制Go进行多核并发

var ch1 chan int = make(chan int)

var ch2 chan string = make(chan string)

go setValue(ch1, ch2)

select {

case <-ch1:

fmt.Println("ch1 ok")

case <-ch2:

fmt.Println("ch2 ok")

}

}

再多运行几次程序:

从运行结果上来看,多核也没有解决这个问题,这是因为当执行到select时,它发现ch1、ch2均无内容,程序发生阻塞,直到另外的goroutine把数据写入ch1或ch2,由于在另外的goroutine中ch1总是第一个被写入数据,所以main所在的gorouinte总是先从ch1中获取到数据,从而打印“ch1 ok”之后就退出了!

那如何完善这个程序呢?


func setValue(ch chan int){

ch <- 1

}

func main() {

runtime.GOMAXPROCS(runtime.NumCPU())

var ch1, ch2 chan int = make(chan int), make(chan int)

go setValue(ch1)

go setValue(ch2)

select {

case <-ch1:

fmt.Println("ch1 ok")

case <-ch2:

fmt.Println("ch2 ok")

}

}

告诉Go的运行环境当前是多核编程,同时起2个goroutine在多核的状态下运行,这样向channel1、channel2中写入数据顺序就变得不可预测了所以有可能先打印“ch1 ok”,也有可能先打印“ch2 ok”。

多运行几次看一下结果:

从结果上来看,达到了预期目的!

二、死锁

如上面的setValue()方法所示,入参是一个channel,方法体是向该channel中写入数据,由于channel作为入参是一个地址传递,所以在select中的case始终能从channel中读取到数据。

试想若程序猿把setValue()中赋值忘记了呢?如下:


func setValue(ch chan int) {

// ch <- 1  注释掉该行

}

运行一下发现系统报了死锁:

像这种情况是在所难免的,如果避免死锁这类问题呢?

有一种办法是引入另一个超时Channel,另启一个goroutine先让它休息一定时间(超时时间),然后把数据写入该Channel,代码如下:


package main

import (

"fmt"

"runtime"

"time"

)

func setValue(ch chan int) {

//ch <- 1  让ch1、ch2产生死锁

}

func main() {

runtime.GOMAXPROCS(runtime.NumCPU())

var timeout chan bool = make(chan bool)  // 创建一个超时的Channel

go func() {                                                    // 新创建一个goroutine

time.Sleep(time.Second * 10)              // 休息10秒

timeout <- true                                   // 在10秒内ch1、ch2若还没有向里面写入数据,则认为超时

}()                                                                 // 加一个()的意思是让这个gorouinte执行

var ch1, ch2 chan int = make(chan int), make(chan int)

go setValue(ch1)

go setValue(ch2)

select {

case <-ch1:

fmt.Println("ch1 ok")

case <-ch2:

fmt.Println("ch2 ok")

case <-timeout:                                        // 若ch1、ch2死锁,10秒钟后timeout填充数据,避免死锁

fmt.Println("Timeout coming...")

}

}

运行一下程序:

三、有意思的程序

啥话都别说了,直接上代码:


package main

import (

"fmt"

)

func main() {

var ch chan int = make(chan int, 1)

for {

select {

case ch <- 0:

case ch <- 1:

}

fmt.Printf("%d", <-ch)

}

}

看懂了没有?

解释一下:

1、先定义一个类型为int的Channel

2、再来一个死循环

3、使用select关键字进行选择

4、case的后面是分别向ch写入0或者1

5、使用Printf进行打印结果

在命令行窗口或者git中执行一下该程序,结果如下:

打印出一连串的随机数

这是为什么呢?

select {

case ch <- 0:

case ch <- 1:

}

这句话的意思就是向channel中放置数据为0或者1的随机数.

时间: 2024-10-11 05:16:21

【Go语言】【18】GO语言的select的相关文章

评:C语言18个经典问题答录

C语言18个经典问题答录这个大家都看过,自己也仔细看了一遍,另外,将一点感悟加注了一下. 1.这样的初始化有什么问题?char *p = malloc(10); 编译器提示"非法初始式" 云云. 答:这个声明是静态或非局部变量吗?函数调用只能出现在自动变量(即局部非静态变量) 的初始式中.因为静态变量的地址必须在编译的过程中就确定下来而malloc()申请的内存地址是在运行时确定的. 评:gcc编译不会报错.但是这样的编程习惯确实不好,即使知道需要内存大小,一般也是使用宏定义来标识需要

DML语言(数据操纵语言)

#DML语言/*数据操作语言:插入:insert修改:update删除:delete */ #一.插入语句#方式一:经典的插入/*语法:insert into 表名(列名,...) values(值1,...); */SELECT * FROM beauty;#1.插入的值的类型要与列的类型一致或兼容INSERT INTO beauty(id,NAME,sex,borndate,phone,photo,boyfriend_id)VALUES(13,'唐艺昕','女','1990-4-23','1

编译性语言不如解释性语言跨平台性好

编译性语言例如c语言:用c语言开发了程序后,需要通过编译器把程序编译成机器语言(即计算机识别的二进制文件,因为不同的操作系统计算机识别的二进制文件是不同的),所以c语言程序进行移植后,要重新编译. 解释性语言,例如java语言,java程序首先通过编译器编译成class文件,如果在windows平台上运行,则通过windows平台上的java虚拟机(VM)进行解释.如果运行在linux平台上,则通过linux平台上的java虚拟机进行解释执行.所以说能跨平台,前提是平台上必须要有相匹配的java

Go语言之GO 语言引用类型

GO 语言引用类型 Go 语言切片 Go 语言切片(Slice) Go 语言切片是对数组的抽象. Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大. 定义切片 申明一个未指定大小的数组来定义切片: var identifier []type 切片不需要说明长度. 或使用make()函数来创建切片: var slice1 []type = m

Go语言之Go语言反射

GO 语言反射 反射是指在程序运行期对程序本身进行访问和修改的能力.程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分.在运行程序时,程序无法获取自身的信息. 支持反射的语言可以在程序编译期将变量的反射信息,如字段名称.类型信息.结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们. Go 语言反射 Go语言提供了一种机制在运行时更新和检查变量的值.调用变量的方法和变量支持的内在操作,但是在编译时并不知道这

Swift语言指南(一)--语言基础之常量和变量

Swift 是开发 iOS 及 OS X 应用的一门新编程语言,然而,它的开发体验与 C 或 Objective-C 有很多相似之处. Swift 提供了 C 与 Objective-C 中的所有基础类型,包括表示整数的 Int,表示浮点数的 Double 与 Float,表示布尔值的 Bool,以及表示纯文本数据的 String. Swift 还为两个基本集合类型 Array 与 Dictionary 提供了强大的支持,详情可参考 (集合类型)Collection Types. 与 C 语言类

0基础学C语言:C语言视频教程免费分享!

C语言是一种通用的.过程式的编程语言,广泛用于系统与应用软件的开发.作为计算机编程的基础语言,长期以来它一直是编程爱好者追捧而又比较难学的语言.C语言是一种计算机程序设计语言,它既具有高级语言的特点,又具有汇编语言的特点. 很多初学者在学习C语言的时候,如果有适合自己的视频教程,学习起来就会事半功倍.今天在这里给大家分享一个0基础学习C语言的视频教程,需要的朋友可以看看,作为参考! 课程部分截图: 百度云盘下载:http://pan.baidu.com/s/1jIbtWEi 密码:npd9

编译性语言、解释性语言和脚本语言

1.计算机不能直接理解高级语言,只能理解机器语言,所以必须要把高级语言翻译成机器语言,计算机才能执行高级语言编写的程序.(计算机只能执行机器语言:我们要执行高级语言编的代码,就只能用编译器把它变成机器语言) 2.翻译有两种方式:a.编译b.解释.两种方式主要是翻译的时间不同 3.编译语言:编译型语言写的程序执行之前,需要一个专门的编译过程,把程序编译成机器语言文件:比如,exe文件,以后运行的话就不用重新编译了,直接使用编译的结果就行了:因为翻译只做了一次,运行时不需要翻译,所以编译型语言的程序

为什么和其他语言相比C语言是快速的语言

初入门的我们经常听见别人说"真正的程序员用C语言编程,C是最快的语言因为它是最靠近及其底层的语言."那么和其他语言相比C语言到底有什么特别的呢? C语言没有什么特别,这就是它快速的秘诀. 新的语言支持更多的特性,比如,垃圾回收(garbage collection),动态类型(dynamic typing)等等.这些新加入的特性让出学者们更容易上手. 问题的关键就在于,这些新的功能增加了处理开销(processing overhead),也就降低了程序性能.而C语言中没有这些功能,它不

Swift语言指南(八)--语言基础之元组

元组 元组(Tuples)将多个值组合为一个复合值.元组内的值可以是任何类型,各个元素不需要为相同类型(各个元素之间类型独立,互不干扰--Joe.Huang). 下例中,(404, "Not Found") 是一个描述HTTP状态码的元组.HTTP状态码是当你向WEB服务器请求页面时服务器返回的一个特殊值,如果你(向WEB服务器)请求了一个不存在的网页,返回的状态码就是 404 Not Found : 1 let http404Error = (404, "Not Found