扫码订阅《 》或入驻星球,即可阅读文章!

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go友会
  • 城市
  • 校园
Go学院
  • Go小课
  • Go小考
  • Go实战
  • 精品课
Go求职
  • 求职辅导🔥
  • Offer收割社群
  • 企业题库
  • 面试宝典
Go宝典
  • 在线宝典
  • B站精选
  • 推荐图书
  • 每日博文
Go仓库
实验区
  • Go周边
  • Go下载
  • Go月刊
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
author-avatar

GOLANG ROADMAP


首页
Go友会
  • 城市
  • 校园
Go学院
  • Go小课
  • Go小考
  • Go实战
  • 精品课
Go求职
  • 求职辅导🔥
  • Offer收割社群
  • 企业题库
  • 面试宝典
Go宝典
  • 在线宝典
  • B站精选
  • 推荐图书
  • 每日博文
Go仓库
实验区
  • Go周边
  • Go下载
  • Go月刊
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
  • Go真实面试题汇总系列

    • 《Go基础篇》
  • 宝典内容

    • 3.Go语言中是如何实现继承的?
    • 4.数组怎么转集合?
    • 13.map取一个key,然后修改这个值,原map数据的值会不会变化
    • 16. go struct 能不能比较
    • 19. 数组是如何实现用下标访问任意元素的
    • 25. 深拷贝和浅拷贝
    • 29. go 的优势
    • 30. 如何判断channel是否关闭?
    • 31. make 与 new 的区别
    • 32. Slice 与 Array, Append()
    • 36. go语言的引用类型有什么?
    • 44. 获取不到锁会一直等待吗?
    • 45. 如何实现一个 timeout 的锁?
    • 55. make可以初始化哪些结构
    • 59. 空结构体占不占内存空间? 为什么使用空结构体?
    • 61. defer 是怎么用的
    • 62. Context 包的作用
    • 65. go 语言的 panic 如何恢复
    • 66. defer 的执行顺序
    • 71. go的init函数是什么时候执行的?
    • 72. 多个init函数执行顺序能保证吗?
    • 73. gin框架的路由是怎么处理的?
    • 74. 用火焰图的优势?
    • 75. struct的传递场景
    • 77. go的profile工具
    • 78. 怎么检查go问题
    • 79. context包内部如何实现的?
    • 80. syncpool的实现原理
    • 81. go什么场景使用接口
    • 83. go怎么实现封装继承多态
    • 84. 为什么go的变量申请类型是为了什么?
    • 85. Go和JAVA垃圾回收机制有啥区别
    • 88. node.js和go是基于什么样的考虑是用这两种语言的?
    • 89. golang垃圾回收机制了解吗?
    • 100. 怎么确定走go语言技术栈的
    • 102. 介绍Gin框架
    • 108. 什么时候会触发线程切换
    • 110. defer关键字后的函数在什么时候调用 主函数return前还是return后
    • 113. golang http库设计原理,为什么不池化
    • 114. golang gc
    • 116. 关闭一个已经关闭的 Channel 会发生什么?Channel 有缓存和没缓存的区别是什么?
    • 122. 类型断言用过吗,说说实现,如何判断断言成功?
    • 123. for true {time.Sleep(1)} 有什么问题
    • 124. sleep底层实现原理
    • 126. interface 的底层实现
    • 127. STW 在 go 的哪些阶段发生?了解1.8版本的改进吗?
    • 130. go test test 和 benchmark
    • 144. for range坑输出
    • 145. go结构体和结构体指针的区别
    • 147. go如何避免panic
    • 148. 结构体创建优化
    • 152. golang interface底层实现,使用场景
    • 153. golang类型断言,怎么用
    • 154. 听说go有什么什么的缺陷,你怎么看
    • 155. 对go有哪些认识
    • 156. go和java的区别
    • 158. 对go的中间件和工作机制有了解吗?
    • 175. Go string底层实现?
    • 177. 了解HTTP协议吗?golang HTTP库实现?
    • 186. 使用range输出一个数组,需要注意的问题
    • 187. Go管理依赖go mod命令,go mod最后的版本号如果没有tag,是怎么生成的
    • 188. 进程、线程、协程的区别?
    • 195. 说一说go的defer和chan
    • 196. golang多态、父类方法重写
    • 198. 线程和协程的区别
    • 201. Golang 的结构体的组合(实现java继承的特性)
    • 202. Golang interface的设计
    • 204. context包的用途?
    • 208. Go的数据结构的零值是什么?
    • 218. 进程线程协程的区别
    • 219. go协程的好处
    • 220. byte和rune有什么区别
    • 228. 进程线程协程区别
    • 236. go中的struct 能不能比较
    • 237. go defer
    • 238. select可以用于什么
    • 242. 用go构造一个链表怎么做,想要从链表尾部插入,怎么做
    • 245. Go语言有缓冲Channel与无缓冲Channel区别
    • 253. go channel close后读的问题
    • 256. defer的执行顺序
    • 259. go 怎么实现func的自定义参数
    • 261. defer的执行顺序
    • 262. golang的调试
    • 263. defer recover panic 执行顺序
    • 267. copy是操作符还是内置函数
    • 276. Go有哪些数据结构
    • 293. defer关键字使用
    • 294. channel有缓冲、无缓冲区别
    • 295. defer和recover的配合
    • 304. 写一个东西:一个字符串json,转成一个直接可用的map,字符串可能是任何形式
    • 305. go的通信实现
    • 306. go interface的底层实现
    • 309. go func与method之前的那个receiver的作用
    • 311. 一个函数传参一个 slice,先 append 再赋值和另一个函数先赋值再append,哪个会发生变化?
    • 312. 有没有什么线程安全的办法?
    • 321. go map时间复杂度
    • 322. go 从源码到二进制代码的整个流程
    • 324. select、epoll
    • 329. make原理
    • 330. string类型转为[]byte过程发生了什么
    • 340. runtime
    • 348. go语言中结构体指针为空,赋给一个interface{}为什么interface不为空
    • 349. defer问题
    • 350. 你能介绍一下go的包管理工具吗?除了gomod还知道哪些?
    • 355. go的值传递和引用传递
    • 357. Go的闭包语法
    • 371. go的反射
    • 374. 判断下面代码的输出
    • 377. 对象是什么,面向对象有什么好处,go 中如何实现多态
    • 379. go 的执行顺序
    • 380. golang的基础问题,比如包管理,比如值传递,比如协程
    • 383. 问了golang的interface的区别,继承,gc的原理、区别,双向链表等。
    • 408. go range 的陷阱
    • 411. 考察defer和panic执行顺序的问题
    • 414. Python和Go的区别
    • 416. go的oop与传统的oop的区别
    • 417. go里面interface是什么概念
    • 418. 相比于java、c++,go的interface有什么区别吗?
    • 421. go和node的区别
    • 422. 程序计数器作用,为什么是私有的
    • 423. PHP和 Go 对比
    • 424. defer如何实现
    • 429. 讲讲go的启动过程
    • 430. Go mod主要解决了什么问题
    • 431. Go sum里面是什么内容
    • 437. Go结构体内嵌后的命名冲突
    • 440. Go 的面向对象特性
    • 442. go init 的执行顺序,注意是不按导入规则的(这里是编译时按文件名的顺序执行的)
    • 443. interface和nil 比较。
    • 454. 一个a+b程序从编译到运行都发生了什么(从预编译到print显示到屏幕上)
    • 455. Go中struct组合与Java继承的区别
    • 458. 使用过哪些 golang 的 String 类库
    • 459. golang 断言

扫码订阅《 》或入驻星球,即可阅读文章!

238. select可以用于什么


企业题库解析小组

题目序号:(3242)

题目来源:腾讯

频次:1

# 答案1:(One)

Go 的通道有两种操作方式,一种是带 range 子句的 for 语句,另一种则是 select 语句,它是专门为了操作通道而存在的。这里主要介绍 select 的用法。

select的语法

select 语句的语法如下:

select {
    case <-ch1 :
       statement(s)   
    case ch2 <- 1 :
       statement(s)
    …
    default : /* 可选 */
       statement(s)
}
1
2
3
4
5
6
7
8
9

这里要注意:

  • 每个 case 都必须是一个通信。 由于 select 语句是专为通道设计的,所以每个 case 表达式中都只能包含操作通道的表达式,比如接收表达式。
  • 如果有多个 case 都可以运行,select 会随机公平地选出一个执行,其他不会执行。
  • 如果多个 case 都不能运行,若有 default 子句,则执行该语句,反之,select 将阻塞,直到某个 case 可以运行。
  • 所有 channel 表达式都会被求值。

用一个简单示例看一下:

package main

import (
    "fmt"
    "math/rand"
)

func main() {
    // 准备好几个通道。
    intChannels := [5]chan int{
        make(chan int, 1),
        make(chan int, 1),
        make(chan int, 1),
        make(chan int, 1),
        make(chan int, 1)
    }
    // 随机选择一个通道,并向它发送元素值。
    index := rand.Intn(5)
    fmt.Printf("The index: %d\n", index)
    intChannels[index] <- index
    // 哪一个通道中有可取的元素值,哪个对应的分支就会被执行。
    select {
        case <-intChannels[0]:
            fmt.Println("The first candidate case is selected.")
        case <-intChannels[1]:
            fmt.Println("The second candidate case is selected.")
        case elem := <-intChannels[2]:
            fmt.Printf("The third candidate case is selected. The element is %d.\n", elem)
        default:
            fmt.Println("No candidate case is selected!")
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

准备了5个通道,放到一个数组里,并用0-4的随机数作为数组的索引,向通道发送元素。用一个包含了三个候选分支的 select 语句,分别尝试从三个通道中接收元素值,哪一个通道中有值,哪一个对应的候选分支就会被执行。 执行结果如下:

The index: 1
The second candidate case is selected.
1
2

多次执行的话,会随机输出不同的字符串,如果随机值不是0、1、2,则会执行 default 语句。

select死锁

select 使用不当会发生死锁。

  • 如果通道没有数据发送,但 select 中有存在接收通道数据的语句,将发生死锁。
package main
    func main() {  
        ch := make(chan string)
        select {
            case <-ch:
        }
    }
1
2
3
4
5
6
7

报错如下:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
	/workspace/src/test.go:5 +0x52
exit status 2
1
2
3
4
5
6

可以添加 default 语句来避免产生死锁。

  • 空 select{}
package main
    func main() {  
        select {}
    }
1
2
3
4

报错如下:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [select (no cases)]:
main.main()
	/workspace/src/test.go:3 +0x20
exit status 2
1
2
3
4
5
6

select和for结合使用

select 语句只能对其中的每一个case表达式各求值一次。所以,如果想连续或定时地操作其中的通道的话,就需要通过在for语句中嵌入select语句的方式实现。

package main

import (
    "fmt"
    "time"
)

func main() {
    tick := time.Tick(time.Second)
    for {
        select {
            case t := <-tick:
                fmt.Println(t)
                break
            }
        }
        fmt.Println("end")
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

打印结果如下:

2021-10-10 23:40:59.664804 +0800 CST m=+1.001254136
2021-10-10 23:41:00.665263 +0800 CST m=+2.001696651
2021-10-10 23:41:01.665595 +0800 CST m=+3.002013571
2021-10-10 23:41:02.665293 +0800 CST m=+4.001699053
2021-10-10 23:41:03.665308 +0800 CST m=+5.001702570
2021-10-10 23:41:04.666859 +0800 CST m=+6.003244115
2021-10-10 23:41:05.665595 +0800 CST m=+7.001972958
……
1
2
3
4
5
6
7
8

你会发现 break 只跳出了 select,无法跳出for。 解决办法有两种:

  • 使用 goto 跳出循环
package main

import (
    "fmt"
    "time"
)

func main() {
    tick := time.Tick(time.Second)
    for {
        select {
            case t := <-tick:
                fmt.Println(t)
                //跳到指定位置
                goto END
            }
        }
END:
        fmt.Println("end")
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • 使用标签
package main

import (
    "fmt"
    "time"
)

func main() {
    tick := time.Tick(time.Second)
//这是标签
FOREND:
    for {
        select {
            case t := <-tick:
                fmt.Println(t)
                //跳出FOREND标签
                break ForEnd
            }
        }
END:
        fmt.Println("end")
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

select实现超时机制

主要使用的 time.After 实现超时控制。

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int)
    quit := make(chan bool)

    go func() {
        for {
            select {
            case num := <-ch:  //如果有数据,下面打印。但是有可能ch一直没数据
                fmt.Println("num = ", num)
            case <-time.After(3 * time.Second): //上面的ch如果一直没数据会阻塞,那么select也会检测其他case条件,检测到后3秒超时
                fmt.Println("超时")
                quit <- true  //写入
            }
        }

    }()

    for i := 0; i < 5; i++ {
        ch <- i
        time.Sleep(time.Second)
    }
    <-quit //这里暂时阻塞,直到可读
    fmt.Println("程序结束")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

执行后,可以观察到:依次打印出0-4,几秒过后打印出“超时”和“程序结束”,打印结果如下:

num =  0
num =  1
num =  2
num =  3
num =  4
超时
程序结束
1
2
3
4
5
6
7

#

  • 答案1:(One)