一文带你学会Go select语句轻松实现高效并发

目录
  • 前言
  • select 介绍
    • 什么是 select
    • 为什么需要 select
  • select 基础
    • 语法
    • 基本用法
  • 一些使用 select 与 channel 结合的场景
    • 实现超时控制
    • 实现多任务并发控制
    • 监听多个通道的消息
    • 使用 default 实现非阻塞读写
  • select 的注意事项
  • 总结

前言

Go 语言中,GoroutineChannel 是非常重要的并发编程概念,它们可以帮助我们解决并发编程中的各种问题。关于它们的基本概念和用法,前面的文章 一文初探 Goroutine 与 channel 中已经进行了介绍。而本文将重点介绍 select,它是协调多个 channel 的桥梁。

select 介绍

什么是 select

selectGo 语言中的一种控制结构,用于在多个通信操作中选择一个可执行的操作。它可以协调多个 channel 的读写操作,使得我们能够在多个 channel 中进行非阻塞的数据传输、同步和控制。

为什么需要 select

Go 语言中的 select 语句是一种用于多路复用通道的机制,它允许在多个通道上等待并处理消息。相比于简单地使用 for 循环遍历通道,使用 select 语句能够更加高效地管理多个通道。

以下是一些 select 语句的使用场景:

1.等待多个通道的消息(多路复用)

当我们需要等待多个通道的消息时,使用 select 语句可以非常方便地等待这些通道中的任意一个通道有消息到达,从而避免了使用多个goroutine进行同步和等待。

2.超时等待通道消息

当我们需要在一段时间内等待某个通道有消息到达时,使用 select 语句可以与 time 包结合使用实现定时等待。

3.在通道上进行非阻塞读写

在使用通道进行读写时,如果通道没有数据,读操作或写操作将会阻塞。但是使用 select 语句结合 default 分支可以实现非阻塞读写,从而避免了死锁或死循环等问题。

因此,select 的主要作用是在处理多个通道时提供了一种高效且易于使用的机制,简化了多个 goroutine 的同步和等待,使程序更加可读、高效和可靠。

select 基础

语法

select {
    case <- channel1:
        // channel1准备好了
    case data := <- channel2:
        // channel2准备好了,并且可以读取到数据data
    case channel3 <- data:
        // channel3准备好了,并且可以往其中写入数据data
    default:
        // 没有任何channel准备好了
}

其中, <- channel1 表示读取 channel1 的数据,data <- channel2 表示用 data 去接收数据;channel3 <- data 表示往 channel3 中写入数据。

select 的语法形式类似于 switch,但是它只能用于 channel 操作。在 select 语句中,我们可以定义多个 case,每个 case 都是一个 channel 操作,用于读取或写入数据。如果有多个 case 同时可执行,则会随机选择其中一个。如果没有任何可执行的 case,则会执行 default 分支(如果存在),或者阻塞等待直到至少有一个 case 可执行为止。

基本用法

package main

import (
   "fmt"
   "time"
)

func main() {
   ch1 := make(chan int)
   ch2 := make(chan int)

   go func() {
      time.Sleep(1 * time.Second)
      ch1 <- 1
   }()

   go func() {
      time.Sleep(2 * time.Second)
      ch2 <- 2
   }()
   for i := 0; i < 2; i++ {
      select {
      case data, ok := <-ch1:
         if ok {
            fmt.Println("从 ch1 接收到数据:", data)
         } else {
            fmt.Println("通道已被关闭")
         }
      case data, ok := <-ch2:
         if ok {
            fmt.Println("从 ch2接收到数据: ", data)
         } else {
            fmt.Println("通道已被关闭")
         }
      }
   }

   select {
   case data, ok := <-ch1:
      if ok {
         fmt.Println("从 ch1 接收到数据:", data)
      } else {
         fmt.Println("通道已被关闭")
      }
   case data, ok := <-ch2:
      if ok {
         fmt.Println("从 ch2接收到数据: ", data)
      } else {
         fmt.Println("通道已被关闭")
      }
   default:
      fmt.Println("没有接收到数据,走 default 分支")
   }
}

执行结果

从 ch1 接收到数据: 1
从 ch2接收到数据:  2
没有接收到数据,走 default 分支

上述示例中,首先创建了两个 channelch1ch2,分别在不同的 goroutine 中向两个 channel 中写入数据。然后,在主 goroutine 中使用 select 语句监听两个channel,一旦某个 channel 上有数据流动,就打印出相应的数据。由于 ch1 中的数据比 ch2 中的数据先到达,因此首先会打印出 "从 ch1 接收到数据: 1",然后才打印出 "从 ch2接收到数据: 2"

为了方便测试 default 分支,我写了两个 select 代码块,执行到第二个 select 代码块的时候,由于 ch1ch2 都没有数据了,因此执行 default 分支,打印 "没有接收到数据,走 default 分支"

一些使用 select 与 channel 结合的场景

实现超时控制

package main

import (
   "fmt"
   "time"
)

func main() {
   ch := make(chan int)
   go func() {
      time.Sleep(3 * time.Second)
      ch <- 1
   }()

   select {
   case data, ok := <-ch:
      if ok {
         fmt.Println("接收到数据: ", data)
      } else {
         fmt.Println("通道已被关闭")
      }
   case <-time.After(2 * time.Second):
      fmt.Println("超时了!")
   }
}

执行结果为:超时了!

在这个例子中,程序将在 3 秒后向 ch 通道里写入数据,而我在 select 代码块里设置的超时时间为 2 秒,如果在 2 秒内没有接收到数据,则会触发超时处理。

实现多任务并发控制

package main

import (
   "fmt"
)

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

   for i := 0; i < 10; i++ {
      go func(id int) {
         ch <- id
      }(i)
   }

   for i := 0; i < 10; i++ {
      select {
      case data, ok := <-ch:
         if ok {
            fmt.Println("任务完成:", data)
         } else {
            fmt.Println("通道已被关闭")
         }
      }
   }
}

执行结果(每次执行的顺序都会不一致):

任务完成: 1
任务完成: 5
任务完成: 2
任务完成: 3
任务完成: 4
任务完成: 0
任务完成: 9
任务完成: 6
任务完成: 7
任务完成: 8

在这个例子中,启动了 10 个 goroutine 并发执行任务,并使用一个 channel 来接收任务的完成情况。在主函数中,使用 select 语句监听这个 channel,每当接收到一个完成的任务时,就进行处理。

监听多个通道的消息

package main

import (
   "fmt"
   "time"
)

func main() {
   ch1 := make(chan int)
   ch2 := make(chan int)

   // 开启 goroutine 1 用于向通道 ch1 发送数据
   go func() {
      for i := 0; i < 5; i++ {
         ch1 <- i
         time.Sleep(time.Second)
      }
   }()

   // 开启 goroutine 2 用于向通道 ch2 发送数据
   go func() {
      for i := 5; i < 10; i++ {
         ch2 <- i
         time.Sleep(time.Second)
      }
   }()

   // 主 goroutine 从 ch1 和 ch2 中接收数据并打印
   for i := 0; i < 10; i++ {
      select {
      case data := <-ch1:
         fmt.Println("Received from ch1:", data)
      case data := <-ch2:
         fmt.Println("Received from ch2:", data)
      }
   }

   fmt.Println("Done.")
}

执行结果(每次执行程序打印的顺序都不一致):

Received from ch2: 5
Received from ch1: 0
Received from ch1: 1
Received from ch2: 6
Received from ch1: 2
Received from ch2: 7
Received from ch1: 3
Received from ch2: 8
Received from ch1: 4
Received from ch2: 9
Done.

该示例代码中,通过使用 select 多路复用,可以同时监听多个通道的数据,并避免了使用多个 goroutine 进行同步和等待的问题。

使用 default 实现非阻塞读写

import (
   "fmt"
   "time"
)

func main() {
   ch := make(chan int, 1)

   go func() {
      for i := 1; i <= 5; i++ {
         ch <- i
         time.Sleep(1 * time.Second)
      }
      close(ch)
   }()

   for {
      select {
      case val, ok := <-ch:
         if ok {
            fmt.Println(val)
         } else {
            ch = nil
         }
      default:
         fmt.Println("No value ready")
         time.Sleep(500 * time.Millisecond)
      }
      if ch == nil {
         break
      }
   }
}

执行结果(每次执行程序打印的顺序都不一致):

No value ready
1
No value ready
2
No value ready
No value ready
3
No value ready
No value ready
4
No value ready
No value ready
5
No value ready
No value ready

这个代码中,使用了 default 分支来实现非阻塞的通道读取和写入操作。在 select 语句中,如果有通道已经准备好进行读写操作,那么就会执行相应的分支。但是如果没有任何通道准备好读写,那么就会执行 default 分支中的代码。

select 的注意事项

以下是关于 select 语句的一些注意事项:

  • select 语句只能用于通信操作,如 channel 的读写,不能用于普通的计算或函数调用。
  • select 语句会阻塞,直到至少有一个 case 语句满足条件。 如果有多个 case 语句满足条件,则会随机选择一个执行。
  • 如果没有 case 语句满足条件,并且有 default 语句,则会执行 default 语句。
  • select 语句中使用 channel 时,必须保证 channel 是已经初始化的。
  • 如果一个通道被关闭,那么仍然可以从它中读取数据,直到它被清空,此时会返回通道元素类型的零值和一个布尔值,指示通道是否已关闭。

总之,在使用 select 语句时,要仔细考虑每个 case 语句的条件和执行顺序,避免死锁和其他问题。

总结

本文主要介绍了 Go 语言中的 select 语句。在文章中,首先介绍了 select 的基本概念,包括它是一种用于在多个通道之间进行选择的语句,以及为什么需要使用 select

接下来,文章详细介绍了 select 的基础知识,包括语法和基础用法。在语法方面,讲解了 select 语句的基本结构以及如何使用 case 子句进行通道选择。在基础用法方面,介绍了如何使用 select 语句进行通道的读取和写入操作,并讲解了一些注意事项。

在接下来的内容中,文章列举了一些使用 selectchannel 结合的场景。这些场景包括实现超时控制、实现多任务并发控制、监听多个通道的消息以及使用 default 实现非阻塞读写。对于每个场景,文章都详细介绍了如何使用 select 语句实现。

最后,文章总结了 select 的注意事项,包括选择的通道必须是可读或可写的通道、select 语句中的 case 子句必须是通道操作或者空的 default 子句,不能是其他类型的语句等等。

到此这篇关于一文带你学会Go select语句轻松实现高效并发的文章就介绍到这了,更多相关Go select语句内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Go select使用与底层原理讲解

    目录 1. select的使用 2. 底层原理 3. 数据结构 4. 几种常见 case case 1 case2 case3 case4 1. select的使用 select 是 Go 提供的 IO 多路复用机制,可以用多个 case 同时监听多个 channl 的读写状态: case: 可以监听 channl 的读写信号 default:声明默认操作,有该字段的 select 不会阻塞 select { case chan <-: // TODO case <- chan: // TOD

  • golang中select语句的简单实例

    目录 前言 1.先举个简单例子 2. 避免造成死锁 3. select 随机性 4. select 的超时 5. 读取/写入都可以 6. 总结一下 前言 在golang语言中,select语句 就是用来监听和channel有关的IO操作,当IO操作发生时,触发相应的case动作.有了 select语句,可以实现 main主线程 与 goroutine线程 之间的互动. select { case <-ch1 : // 检测有没有数据可读 // 一旦成功读取到数据,则进行该case处理语句 cas

  • 一文带你了解Golang中select的实现原理

    目录 概述 结构 现象 非阻塞的收发 随机执行 编译 直接阻塞 独立情况 非阻塞操作 通用情况 运行时 初始化 循环 总结 概述 select是go提供的一种跟并发相关的语法,非常有用.本文将介绍 Go 语言中的 select 的实现原理,包括 select 的结构和常见问题.编译期间的多种优化以及运行时的执行过程. select 是一种与 switch 非常相似的控制结构,与 switch 不同的是,select 中虽然也有多个 case,但是这些 case 中的表达式都必须与 Channel

  • 详解Golang中select的使用与源码分析

    目录 背景 select 流程 背景 golang 中主推 channel 通信.单个 channel 的通信可以通过一个goroutine往 channel 发数据,另外一个从channel取数据进行.这是阻塞的,因为要想顺利执行完这个步骤,需要 channel 准备好才行,准备好的条件如下: 1.发送 缓存有空间(如果是有缓存的 channel) 有等待接收的 goroutine 2.接收 缓存有数据(如果是有缓存的 channel) 有等待发送的 goroutine 对channel实际使

  • Go语言select语句用法示例

    目录 用法 使用场景 实现收发功能 注意事项 用法 多个通道 Channel 中信息的发送和接受处理的专用的语句—select 语句.select 语句会阻塞,直到其中的一个发送/接收操作准备好.select 语句和 switch 语句有点相似,但 select 语句在被执行时会选择执行其中的一个分支,且选择分支的方法完全是不相同的. ch1 = make(chan string) ch2 = make(chan string) ch1 <- "server1" ch2 <

  • 深入浅出Golang中select的实现原理

    目录 概述 select实现原理 执行流程 case数据结构 执行select 循环 总结 概述 在go语言中,select语句就是用来监听和channel有关的IO操作,当IO操作发生时,触发相应的case操作,有了select语句,可以实现main主线程与goroutine线程之间的互动.需要的朋友可以参考以下内容,希望对大家有帮助. select实现原理 Golang实现select时,定义了一个数据结构表示每个case语句(包含default,default实际上是一种特殊的case),

  • 一文带你学会Go select语句轻松实现高效并发

    目录 前言 select 介绍 什么是 select 为什么需要 select select 基础 语法 基本用法 一些使用 select 与 channel 结合的场景 实现超时控制 实现多任务并发控制 监听多个通道的消息 使用 default 实现非阻塞读写 select 的注意事项 总结 前言 在 Go 语言中,Goroutine 和 Channel 是非常重要的并发编程概念,它们可以帮助我们解决并发编程中的各种问题.关于它们的基本概念和用法,前面的文章 一文初探 Goroutine 与

  • 一文带你学会C语言中的qsort函数

    目录 铺垫知识 使用qsort函数进行整型数组的排序 使用qsort函数进行浮点型数组的排序 使用qsort函数进行结构体数组的排序 铺垫知识 qsort函数 参数类型 void qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*)); 参数类型解释 参数1 待排序数组首元素的地址 参数2 数组内元素个数 参数3 数组内每个元素大小,单位是字节 参数4 函数指针,由自己实现,内容是两个元

  • 一文带你学会规则引擎Drools的应用

    目录 前言 引入依赖 Drools配置类 添加业务Model 定义drools 规则 添加Service层 添加Controller 测试一下 总结 前言 现在有这么个需求,网上购物,需要根据不同的规则计算商品折扣,比如VIP客户增加5%的折扣,购买金额超过1000元的增加10%的折扣等,而且这些规则可能随时发生变化,甚至增加新的规则.面对这个需求,你该怎么实现呢?难道是计算规则一变,就要修改业务代码,重新测试,上线吗. 其实,我们可以通过规则引擎来实现,Drools 就是一个开源的业务规则引擎

  • 一文带你学会MySQL的select语句

    目录 SQL概述 SQL背景知识 SQL语言排行榜 SQL 分类 SQL语言的规则与规范 基本规则 SQL大小写规范 (建议遵守) 注释 命名规则(暂时了解) 数据导入指令 基本的SELECT语句 SELECT... SELECT ... FROM 列的别名 去除重复行 空值参与运算 着重号 查询常数 总结 SQL概述 SQL背景知识 1946 年,世界上第一台电脑诞生,如今,借由这台电脑发展起来的互联网已经自成江湖.在这几十年里,无数的技术.产业在这片江湖里沉浮,有的方兴未艾,有的已经几幕兴衰

  • 一文带你学会Java事件机制

    目录 委托事件模型 核心组件 总结 相信做 Java 开发的朋友,大多都是学习过或至少了解过 Java GUI 编程的,其中有大量的事件和控件的绑定,当我们需要在点击某个按钮实现某些操作的时候,其实就是为这个按钮控件注册了一个合理处理点击事件的监听器.此外,Spring Framework 中也有许多用到事件处理机制的地方,如 ApplicationContextEvent 及其子类,代表着容器的启动.停止.关闭.刷新等事件.本文会为大家介绍 Java 的事件处理机制,也会用示例来说明,如何优雅

  • 一文带你轻松学会Go语言动态调用函数

    目录 前言 JavaScript 动态调用函数 Go 中动态调用方法 前言 经常在开发的时候会遇到这样的场景,几个模块的都有相同的方法,但会因为不同的类型的需要调用不同模块的方法.使用一个 switch 可以很方便的解决问题.但是当你遇到的场景是几个模块的方法都是需要的执行,同时它需要在不同的配置下执行相对应的方法. func m1(){} func m2(){} func m3(){} c := cron.New(cron.WithSeconds()) c.addFunc(config1,fu

  • 一文带你了解Golang中interface的设计与实现

    目录 前言 接口是什么 iface 和 eface 结构体 _type 是什么 itab 是什么 生成的 itab 是怎么被使用的 itab 关键方法的实现 根据 interfacetype 和 _type 初始化 itab 接口断言过程总览(类型转换的关键) panicdottypeI 与 panicdottypeE iface 和 eface 里面的 data 是怎么来的 convT* 方法 Java 里面的小整数享元模式 总结 在上一篇文章<go interface 基本用法>中,我们了

  • 一文带你了解如何正确使用MyBatisPlus

    目录 1.创建测试表 2.创建 Spring Boot 工程 3.导入依赖 4.编写数据库配置文件 5.编写代码 6.CRUD 测试 7.打印SQL语句 本篇文章,我们通过 MyBatis Plus 来对一张表进行 CRUD 操作,来看看是如何简化我们开发的. 1.创建测试表 创建 USER 表: DROP TABLE IF EXISTS `user`; CREATE TABLE `user` (   `ID` int(11) NOT NULL,   `USER_NAME` varchar(32

  • MySQL中select语句介绍及使用示例

    数据表都已经创建起来了,假设我们已经插入了许多的数据,我们就可以用自己喜欢的方式对数据表里面的信息进行检索和显示了,比如说:可以象下面这样把整个数据表内的内容都显示出来 select * from president; 也可以只选取某一个数据行里的某一个数据列 select birth from president where last_name='Eisenhower'; select语句的通用形式如下: select 你要的信息 from 数据表(一个或多个) where 满足的条件 sel

随机推荐