首页 > 其他分享 >介绍Go中的for select case和chan 和goroutine

介绍Go中的for select case和chan 和goroutine

时间:2024-11-12 09:46:53浏览次数:3  
标签:case sensorChan 写入 goroutine chan msg select

好的,让我们详细分析您提供的代码片段:

for {
    select {
    case msg := <-sensorChan:
        // 处理消息的代码
    }
}

1. 整体结构概述

这段代码使用了 Go 语言的并发控制结构,结合无限循环 (for)、选择语句 (select)、以及 case 分支来处理从 sensorChan 通道接收的数据。让我们逐步拆解每个部分的功能和作用。

2. 逐行解析

2.1 for { ... }
  • 作用

    • 这是一个无限循环,意味着它会一直运行,直到程序被显式终止(如通过 breakreturnpanic,或外部信号)。
  • 用途

    • 在并发程序中,常用来持续监听和处理来自通道的数据或其他事件。
2.2 select { ... }
  • 作用

    • select 语句用于等待多个通道操作中的一个完成。
    • 它会阻塞当前 goroutine,直到其中一个 case 分支的通信操作可以进行,然后执行该 case 分支中的代码。
    • 如果有多个 case 分支同时满足条件,Go 语言会随机选择其中一个执行,以避免偏向某个 case
  • 用途

    • 在需要同时监听多个通道或处理多种事件时,select 提供了一种优雅的方式来管理这些并发操作。
2.3 case msg := <-sensorChan:
  • 作用

    • 这是一个接收操作,从 sensorChan 通道中接收一个消息并赋值给变量 msg
    • 如果 sensorChan 里有数据,select 会选择这个 case 分支并执行相应的代码。
    • 如果 sensorChan 为空且没有关闭(即仍然可能接收数据),case 操作会阻塞,直到有数据可接收。
  • 用途

    • sensorChan 通道通常用于在不同的 goroutine 之间传递数据。在这个上下文中,sensorChan 可能用于传递来自传感器的数据消息。

3. 详细流程说明

让我们结合整个代码片段,说明它的运行流程:

  1. 启动无限循环

    • 程序进入 for 循环,并持续运行。
  2. 进入 select 语句

    • 在每次循环迭代中,程序进入 select 语句,准备监听多个通道操作。
  3. 监听 sensorChan 通道

    • select 语句中有一个 case 分支,是监听 sensorChan 通道的接收操作。
    • 如果 sensorChan 中有数据,程序会接收该数据,并将其赋值给变量 msg,然后执行对应的处理代码。
    • 如果 sensorChan 中没有数据,且通道未关闭,程序会在此 case 分支处阻塞,直到有数据可接收。
  4. 处理接收到的消息

    • 一旦有数据从 sensorChan 被接收,msg 就包含了该数据。
    • 接着,程序会执行 case 分支中的处理代码,对 msg 进行相应的处理(例如,写入数据库、发送 HTTP 请求等)。
  5. 循环继续

    • 处理完当前消息后,循环继续,重新进入 select 语句,等待下一个消息的到来。

4. 并发与单线程的关系

虽然 for 循环和 select 语句本身运行在一个 goroutine 中(可能是主 goroutine 或另一个专门的处理 goroutine),但整个程序可能包含多个 goroutine:

  • 消息生产者

    • 例如,MQTT 客户端在独立的 goroutine 中接收消息,并将其发送到 sensorChan
  • 消息消费者

    • 这里的 for { select { ... } } 结构通常运行在一个专门的 goroutine 中,负责从 sensorChan 接收并处理消息。

5. 可能的后续代码行为

假设您的 case 分支代码如下:

case msg := <-sensorChan:
    currentTime := time.Now()
    lastWrite, exists := sensorLastWrite[msg.SensorID]

    // 检查是否需要处理这条数据
    if !exists || currentTime.Sub(lastWrite) >= time.Minute {
        // 处理数据
        fiberData := FiberOpticInclinometerData{
            Number:   msg.Data.SensorID,
            Datetime: parseTime(msg.Data.Time),
            Deep:     fmt.Sprintf("%.6f", msg.Data.SensorPosition),
            Lateral:  fmt.Sprintf("%.6f", msg.Data.Value),
            IfDamage: false,
        }

        // 写入数据库
        if enabledMySQL {
            if err := writeToMySQL(fiberData); err == nil {
                sensorLastWriteMutex.Lock()
                sensorLastWrite[msg.SensorID] = currentTime
                sensorLastWriteMutex.Unlock()

                fmt.Printf("Successfully processed sensor %s at %v\n",
                    msg.SensorID, currentTime.Format("2006-01-02 15:04:05"))
            }
        }

        // 发送HTTP请求
        if enabledHTTP {
            singlepointData := FiberOpticInclinometerData{
                Number:   fiberData.Number,
                Datetime: fiberData.Datetime,
                Deep:     fiberData.Deep,
                Lateral:  fiberData.Lateral,
                IfDamage: fiberData.IfDamage,
            }
            SendSensorData(urlSensor+singlepointURI, tokenf, []FiberOpticInclinometerData{singlepointData})
        }
    } else {
        fmt.Printf("Skipping sensor %s, last write was %v ago\n",
            msg.SensorID, currentTime.Sub(lastWrite))
    }
5.1 处理流程
  1. 获取当前时间

    • currentTime := time.Now()
  2. 检查传感器的上次写入时间

    • 使用 sensorLastWrite 映射,获取传感器 msg.SensorID 的上次写入时间 lastWrite
    • 检查该传感器是否存在于 sensorLastWrite 中,以及当前时间与上次写入时间的差值是否超过一分钟。
  3. 条件判断

    • 如果传感器没有写入过数据 (!exists),或者 距离上次写入超过一分钟 (currentTime.Sub(lastWrite) >= time.Minute):
      • 处理数据
        • 创建 FiberOpticInclinometerData 结构体,填充传感器数据。
      • 写入数据库
        • 调用 writeToMySQL(fiberData) 函数,将数据写入 MySQL 数据库。
        • 如果写入成功,更新 sensorLastWrite 中对应传感器的最后写入时间。
      • 发送 HTTP 请求
        • 创建 singlepointData 结构体,发送 HTTP 请求到指定的 URL。
    • 否则
      • 打印跳过该传感器的数据处理日志,显示距离上次写入的时间差。
5.2 并发安全
  • 互斥锁 (sensorLastWriteMutex)
    • 由于 sensorLastWrite 映射可能被多个 goroutine 访问(虽然在单线程处理模式下不太常见),使用互斥锁确保读写操作的并发安全。

6. forselectcase 的关系

  • for 循环

    • 在无限循环中持续运行,确保程序不停地监听和处理来自 sensorChan 的消息。
  • select 语句

    • 随着 for 循环的每次迭代,select 会检查所有 case 分支的条件,并执行第一个满足条件的分支。
    • 在此情境下,只有一个 case 分支:接收 sensorChan 中的消息。
  • case 分支

    • case msg := <-sensorChan: 表示尝试从 sensorChan 中接收一个消息。
    • 如果有消息可接收,执行 case 中的代码。
    • 如果没有消息,且 sensorChan 未关闭,则阻塞等待。

7. 代码作为一个 goroutine 的运行

  • 单独的 goroutine

    • 通常,这样的消息处理循环会被放置在一个单独的 goroutine 中运行,以便与其他并发操作(如 MQTT 消息接收、HTTP 请求等)并行进行。
  • 示例

    func main() {
        // ... 初始化代码 ...
    
        // 启动 MQTT 订阅
        subscribeAndProcessBatch(mqttTopic)
    
        // 启动消息处理循环在一个单独的 goroutine 中
        go processDataBatch()
    
        // 主 goroutine 可能做其他事情,或者等待信号
        select {}
    }
    
  • 优点

    • 保证消息处理与其他操作(如消息接收、用户交互等)并行进行,提升程序的响应能力和效率。

8. 总结

这段代码的核心功能是:

  1. 持续监听

    • 通过无限循环和 select 语句,程序持续监听 sensorChan 通道中的消息。
  2. 消息接收

    • 一旦有新消息到达,程序会立即接收并处理该消息。
  3. 条件处理

    • 根据传感器的上次写入时间,决定是否处理当前数据,确保每个传感器的数据写入间隔至少为一分钟。
  4. 数据处理和存储

    • 处理符合条件的数据,写入数据库,并可能发送 HTTP 请求。
  5. 并发安全

    • 使用互斥锁确保共享资源 (sensorLastWrite) 的读写安全。

这样的设计确保了程序能够高效、稳定地处理来自传感器的数据,同时避免数据重复写入和处理延迟。

9. 推荐改进(可选)

  1. 优雅停止

    • 添加停止信号处理(如捕获 SIGINT)以允许程序优雅关闭,确保所有待处理的数据被正确处理和存储。
  2. 错误处理增强

    • 在消息处理和数据库写入过程中,添加更多的错误处理和重试机制,提升程序的鲁棒性。
  3. 日志记录

    • 使用日志框架(如 logrus)替代 fmt.Println,以便更好地管理和分析日志。
  4. 性能优化

    • 监控 sensorChan 的使用情况,根据需要调整缓冲区大小,避免频繁的消息丢失。

希望以上分析能够帮助您深入理解代码的工作机制,并指导您进行进一步的调试和优化。

标签:case,sensorChan,写入,goroutine,chan,msg,select
From: https://blog.csdn.net/u011027104/article/details/143690303

相关文章

  • Tomcat 应用实战案例(Practical Case Study of Tomcat Application)
      ......
  • MethodChannel的用法
    文章目录1知识回顾2示例代码3经验总结我们在上一章回中介绍了MethodChannel的使用方法,本章回中将介绍EventChannel的使用方法.闲话休提,让我们一起TalkFlutter吧。1知识回顾我们在前面章回中介绍了通道的概念和作用,并且提到了通道有不同的类型,本章回将其......
  • 使用MethodChannel与原生程序通信
    文章目录1知识回顾2示例代码3经验总结我们在上一章回中介绍了MethodChannel的使用方法,本章回中将介绍EventChannel的使用方法.闲话休提,让我们一起TalkFlutter吧。1知识回顾我们在前面章回中介绍了通道的概念和作用,并且提到了通道有不同的类型,本章回将其......
  • Playwright使用Typescript实现在测试case文件中调用另一个文件中的方法
    前提:(1)安装了nodejs(2)创建了测试目录(3)使用Vscode安装了Playwright插件可以参考官方文档:https://playwright.dev/docs/getting-started-vscode 在vscode界面最左侧的按钮选择Explorer,创建一个与tests目录同级的目录methods,并在methods目录下创建文件method1.ts,目录结构如......
  • Webrtc播放H265的技术探索(datachannel+wasm)
    通过这个帖子把我对通过webrtc方式播放H265视频的技术探索过程记录下来。虽然最终不一定能够形成产品进行实现,但觉得也是一个有意义的过程。很多事情不就是这样吗?也希望将来能够有同道中人看到这个帖子,一起分享交流~-----------------------------一。背景1)由于Webrtc......
  • 知识点:用例图(Use Case Diagram)
    知识点:该题目考查的是面向对象的分析与设计方法(Object-OrientedAnalysisandDesign,OOAD),特别是用例图(UseCaseDiagram)的相关知识点。用例图是UML(统一建模语言)中的一种图表,用于描述系统的功能需求,它展示了系统如何与外部用户或其他系统交互。知识点相关内容:用例(UseCase):用......
  • bert-base-uncased处理文档
    1.安装必要的库确保安装transformers和torch库:pipinstalltransformerstorch2.加载本地BERT模型和分词器由于已将模型和分词器下载到本地,可以指定文件路径加载。确保路径与本地文件结构一致。fromtransformersimportBertTokenizer,BertModel#指定模型和分......
  • GoLang协程Goroutiney原理与GMP模型详解
    本文原文地址:GoLang协程Goroutiney原理与GMP模型详解什么是goroutineGoroutine是Go语言中的一种轻量级线程,也成为协程,由Go运行时管理。它是Go语言并发编程的核心概念之一。Goroutine的设计使得在Go中实现并发编程变得非常简单和高效。以下是一些关于Goroutine的关键特性:轻量......
  • 90_api_intro_stock_currencyexchange
    国际货币汇率API数据接口支持多种货币对,每日精准汇率,多币种支持。1.产品功能支持多种货币汇率查询;支持部分加密货币汇率查询;数据为每日更新汇率数据;可一次查询源货币代码对应所有目标货币汇率;数据持续更新与维护;全接口支持HTTPS(TLSv1.0/v1.1/v1.2/v1.3);全面......
  • React.memo vs. useMemo: Major differences and use cases
    from:  https://blog.logrocket.com/react-memo-vs-usememo/ Memoization isoneofthewaystooptimizeperformance.Inthisarticle,we’llexplorehowitworksinReact.Whatismemoization?Insimpleterms,memoizationisaprocessthatallowsustocac......