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

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://qingkechina.blog.51cto.com/5552198/1685383

一、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的随机数.

本文出自 “青客” 博客,请务必保留此出处http://qingkechina.blog.51cto.com/5552198/1685383

时间: 2024-08-03 19:22:58

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

Java语言与C++语言的差异总结

Java的设计者曾说过,设计这门语言的灵感主要来自于C++. 世上先有C++,然后才有Java,整个Java语言的发展历史就是一部对C++的填坑史.所以在Java语言学习过程中,将其与C++语言对比是一件有意义的事情.通过这些对比,我们能够了解到Java语言相对于C++语言有哪些改进,能带给我们哪些进步,这样对于更加深入理解这两种语言是大有裨益的. 下面我总结一下Java语言与C++语言的各种差异. 1.Java用来操纵对象的引用可以先初始化再指向对象,而C++的引用必须在初始化时就指向对象.

C#语言与Java语言程序的比较

迈入二十一世纪以来,信息行业飞速壮大,其中在软件开发中Java语言与C#语言都独当一面,得到了逐步完善和广泛的应用,Java语言和C#语言都是一种面向对象的语言,但Java程序和C#程序还是有一定的区别. 下面分别是两种语言的程序的例子. 1.Java语言的基本程序: Import java.util.*; Package HelloJava{ Public class Message{ Public static void main(String [] args){ System.out.pr

Thinkphp搭建包括JS多语言的多语言项目实现方法_php实例

本文实例讲述了Thinkphp搭建包括JS多语言的多语言项目实现方法.分享给大家供大家参考.具体实现方法如下: 一.问题: 项目需要开发英文版,于是需要搭建多语言项目. 项目使用Thinkphp框架,隐约记得Thinkphp有多语言设置,翻看了帮助手册,果然有,这就边实验边开始: 二.实现方法: Thinkphp采用app_begain来检测和切换语言包,语言包和项目相关,构架等都比较简单,具体的这里:http://www.thinkphp.cn/info/188.html 搭建好了,就可以使用

r语言-对一个向量的划分,求C语言或R语言实现

问题描述 对一个向量的划分,求C语言或R语言实现 1C 向量U={123456}利用R1属性划分为:U/R1={{123}{456}}利用R2属性划分为:U/R2={{12}{3456}}利用R3属性划分为:U/R3={{1234}{56}}最后得到划分的交集:U/R={{12}{3}{4}{56}}

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

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

Swift语言指南(三)--语言基础之整数和浮点数

原文:Swift语言指南(三)--语言基础之整数和浮点数   整数   整数指没有小数的整数,如42,-23.整数可以是有符号的(正数,零,负数),也可以是无符号的(正数,零). Swift提供了8,16,32,64位形式的有符号和无符号的整数,这些整数遵循与C语言相似的命名规则.如8位无符号整数的类型为UInt8,32位有符号整数的类型为Int32,和Swift语言的其它类型一样,这些整型命名以大写字母开头.   整数的边界 你可以通过min或max属性为每一个整数类型指定一个最小值或最大值:

各国语言缩写列表,各国语言缩写-各国语言简称,世界各国域名缩写

$amount = '12345.67'; $formatter = new \NumberFormatter('en_GB', \NumberFormatter::CURRENCY); echo 'UK: ' . $formatter->formatCurrency($amount, 'EUR') . '<br/>'; $formatter = new \NumberFormatter('de_DE', \NumberFormatter::CURRENCY); echo 'DE: '

c语言与c++语言之间的相互转化

问题描述 c语言与c++语言之间的相互转化 struct student*p0 scanf("%s",p0->num) scanf("%s",&p0->name) 解决方案 C++也能兼容C,直接编译看有没有什么语法错误 解决方案二: 对的,C++编译器本来就支持几乎所有C语法 你为何还需要改 C --> C++ #include scanf("%s",p0->num); --> std::cin >&

c语言-C语言递归函数C语言递归函数C语言递归函数

问题描述 C语言递归函数C语言递归函数C语言递归函数 #include<stdio.h> #include<stdlib.h> //用递归函数来计算N的阶乘 double factorial(int n) { double result; if(n<0) { printf("输入错误 "); } else if(n==1 ||n==0) { result=1; } else { result=factorial(n-1)*n; //n=5 5-1=4 4*

java-spring框架下jsp脚本语言confirm脚本语言删除数据库中数据怎么做?

问题描述 spring框架下jsp脚本语言confirm脚本语言删除数据库中数据怎么做? spring框架下jsp脚本语言confirm脚本语言删除数据库中数据怎么做,具体说一说怎么写confirm脚本代码?求大神解答啊 解决方案 你的意思是点删除的时候弹出个确认对话框么?再跳一个Action实现真正的删除工作.还是想用ajax的方式实现无刷新删除-- 解决方案二: $(document).ready(function(){ //为注销用户超链接绑定click事件 $(".logoutid&qu