【Go语言学习系列29】并发编程(二):channel基础

Go并发编程实战 第2版 PDF (中文版带书签) Go并发编程实战 第2版 目录 第1章 初识Go语言 11.1 语言特性 11.2 安装和设置 21.3 工程结构 31.3.1 工作区 31.3.2 GOPATH 41.3.3 源码文件 51.3.4 代码包 81.4 标准命令简述 111.5 问候程序 131.6 小结 14第2章 语法概览 152.1 基本构成要素 152.1.1 标识符 152.... 阅读详情

📚 原创系列: “Go语言学习系列”

🔄 转载说明: 本文最初发布于"Gopher部落"微信公众号,经原作者授权转载。

🔗 关注原创: 欢迎扫描文末二维码,关注"Gopher部落"微信公众号获取第一手Go技术文章。

📑 Go语言学习系列导航

本文是【Go语言学习系列】的第29篇,当前位于第三阶段(进阶篇)

🚀 第三阶段:进阶篇
  1. 并发编程(一):goroutine基础
  2. 并发编程(二):channel基础 👈 当前位置
  3. 并发编程(三):select语句
  4. 并发编程(四):sync包
  5. 并发编程(五):并发模式
  6. 并发编程(六):原子操作与内存模型
  7. 数据库编程(一):SQL接口
  8. 数据库编程(二):ORM技术
  9. Web开发(一):路由与中间件
  10. Web开发(二):模板与静态资源
  11. Web开发(三):API开发
  12. Web开发(四):认证与授权
  13. Web开发(五):WebSocket
  14. 微服务(一):基础概念
  15. 微服务(二):gRPC入门
  16. 日志与监控
  17. 第三阶段项目实战:微服务聊天应用

📚 查看完整Go语言学习系列导航

📖 文章导读

在本文中,您将了解:

  • channel的基本概念与作用
  • 无缓冲与缓冲channel的区别与适用场景
  • channel的发送与接收操作
  • 如何正确关闭channel
  • 使用channel进行goroutine间通信的最佳实践
  • 常见的channel错误与陷阱

本文是掌握Go并发编程的关键一步,channel与goroutine共同构成了Go语言并发模型的核心。通过理解channel的工作原理,您将能够设计出更加高效、安全的并发程序。

Go channel示意图


并发编程(二):channel基础

在上一篇文章中,我们介绍了Go语言并发编程的核心机制之一——goroutine。而今天,我们将深入探讨Go并发模型的另一个核心组件:channel(通道)。channel是goroutine之间进行通信和同步的主要机制,它实现了CSP(通信顺序进程)模型中"通过通信来共享内存"的理念。

一、channel概念

1.1 什么是channel

channel是Go语言提供的一种数据结构,它像一个管道,可以在不同的goroutine之间安全地传递数据。channel有以下特点:

  • 类型安全:每个channel只能传递指定类型的数据
  • 并发安全:channel的操作是原子的,不需要额外的锁
  • FIFO顺序:数据按照先进先出的顺序从channel中读取
  • 阻塞机制:可以用于goroutine之间的同步

从本质上讲,channel是一个数据结构,内部包含一个缓冲区、一个互斥锁以及两个等待队列(发送者队列和接收者队列)。

1.2 为什么需要channel

在并发编程中,我们通常需要解决以下问题:

  1. 数据共享:多个goroutine需要安全地共享数据
  2. 工作分发:将任务分配给多个worker goroutine
  3. 信号通知:发送事件信号(如完成、取消等)
  4. 同步控制:协调不同goroutine的执行顺序

传统的共享内存并发模型通常使用锁来解决这些问题,但锁机制容易导致复杂性增加、死锁和性能问题。channel提供了一种更简洁、更符合Go语言哲学的方案。

1.3 channel类型

在Go中,channel类型表示为chan T,其中T是channel中传递的数据类型。channel有三种基本类型:

  1. 双向channel:可以发送和接收数据

    var ch chan int // 双向channel,可发送和接收int
    
  2. 只发送channel:只能发送数据,不能接收

    var sendCh chan<- int // 只发送channel
    
  3. 只接收channel:只能接收数据,不能发送

    var recvCh <-chan int // 只接收channel
    

类型转换关系:

  • 双向channel可以转换为单向channel,但反之不行
  • 这种转换通常用于函数参数,限制函数对channel的操作
func send(ch chan<- int) {
    ch <- 42 // 只能发送
    // <-ch  // 编译错误:不能从只发送channel接收
}

func receive(ch <-chan int) {
    v := <-ch // 只能接收
    // ch <- 42 // 编译错误:不能向只接收channel发送
}

func main() {
    ch := make(chan int) // 双向channel
    go send(ch)    // 可以将双向channel传给只发送channel参数
    go receive(ch) // 可以将双向channel传给只接收channel参数
}

二、无缓冲与缓冲channel

Go语言中的channel分为两种:无缓冲channel和缓冲channel,它们有着不同的行为特性。

2.1 无缓冲channel

无缓冲channel没有存储容量,发送操作必须等待接收操作,反之亦然。因此,无缓冲channel提供了goroutine之间的同步保证。

创建方式:

ch := make(chan int) // 无缓冲channel
// 或
ch := make(chan int, 0) // 显式指定缓冲大小为0

行为特点:

  • 发送操作会阻塞,直到有另一个goroutine执行接收操作
  • 接收操作会阻塞,直到有另一个goroutine执行发送操作
  • 发送和接收操作同时就绪时,数据交换才会发生

示例:

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan string) // 无缓冲channel
    
    go func() {
        fmt.Println("Goroutine starts...")
        time.Sleep(2 * time.Second)
        fmt.Println("Goroutine sending data...")
        ch <- "Hello from goroutine!" // 发送数据到channel
        fmt.Println("Goroutine continues after sending...")
    }()
    
    fmt.Println("Main goroutine waiting for data...")
    msg := <-ch // 接收数据,会阻塞直到有数据发送
    fmt.Println("Received:", msg)
    
    // 给goroutine一点时间输出
    time.Sleep(time.Second)
}

输出:

Main goroutine waiting for data...
Goroutine starts...
Goroutine sending data...
Received: Hello from goroutine!
Goroutine continues after sending...

注意发送和接收的阻塞行为。无缓冲channel常用于需要精确同步的场景。

2.2 缓冲channel

缓冲channel有一个内部缓冲区,可以存储一定数量的元素。只有缓冲区满时,发送操作才会阻塞;只有缓冲区空时,接收操作才会阻塞。

创建方式:

ch := make(chan int, 3) // 缓冲大小为3的channel

行为特点:

  • 当缓冲区未满时,发送操作不会阻塞
  • 当缓冲区非空时,接收操作不会阻塞
  • 当缓冲区满时,发送操作会阻塞,直到有空间
  • 当缓冲区空时,接收操作会阻塞,直到有数据

示例:

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int, 3) // 缓冲大小为3的channel
    
    // 发送方
    go func() {
        for i := 1; i <= 5; i++ {
            fmt.Printf("Sending: %d\n", i)
            ch <- i
            fmt.Printf("Sent: %d\n", i)
        }
        close(ch) // 关闭channel
    }()
    
    // 给发送方一些时间来填充channel
    time.Sleep(time.Second)
    
    // 接收方
    for v := range ch {
        fmt.Printf("Received: %d\n", v)
        time.Sleep(500 * time.Millisecond) // 故意慢一点接收
    }
}

可能的输出:

Sending: 1
Sent: 1
Sending: 2
Sent: 2
Sending: 3
Sent: 3
Sending: 4
Received: 1
Received: 2
Sent: 4
Sending: 5
Received: 3
Sent: 5
Received: 4
Received: 5

注意:前3个值可以立即发送(缓冲区未满),而第4个值需要等待接收方接收后才能发送。

2.3 选择合适的channel类型

两种channel类型适用于不同场景:

场景推荐类型原因
需要同步的操作无缓冲提供"会合点"同步
异步或批量操作缓冲允许发送方和接收方独立工作
限制并发数量缓冲缓冲大小可以限制并发操作数
事件通知无缓冲简化事件通知逻辑

三、发送与接收

channel的基本操作是发送和接收数据。Go语言提供了简洁的语法来执行这些操作。

3.1 基本语法

发送数据:

ch <- value // 将value发送到channel ch

接收数据:

value := <-ch // 从channel ch接收数据并赋值给value
<-ch          // 接收数据但丢弃

检查接收状态:

value, ok := <-ch // ok为true表示成功接收数据,为false表示channel已关闭且为空

3.2 阻塞行为

理解channel的阻塞行为对于正确使用channel至关重要:

无缓冲channel:

  • 发送操作会阻塞,直到另一个goroutine执行对应的接收操作
  • 接收操作会阻塞,直到另一个goroutine执行对应的发送操作

缓冲channel:

  • 当缓冲区未满时,发送操作不会阻塞
  • 当缓冲区非空时,接收操作不会阻塞
  • 当缓冲区满时,发送操作会阻塞,直到有空间
  • 当缓冲区空时,接收操作会阻塞,直到有数据

3.3 非阻塞操作

有时候我们需要进行非阻塞的channel操作,这可以通过select语句和default分支实现:

// 非阻塞发送
select {
case ch <- x:
    fmt.Println("发送成功")
default:
    fmt.Println("发送失败,channel已满或没有接收者")
}

// 非阻塞接收
select {
case x, ok := <-ch:
    if ok {
        fmt.Println("接收成功:", x)
    } else {
        fmt.Println("channel已关闭")
    }
default:
    fmt.Println("接收失败,channel为空或没有发送者")
}

3.4 遍历channel

可以使用for-range结构遍历channel中的所有值,直到channel关闭:

ch := make(chan int, 3)
go func() {
    ch <- 1
    ch <- 2
    ch <- 3
    close(ch) // 必须关闭,否则下面的range循环会死锁
}()

for v := range ch {
    fmt.Println(v)
}

四、关闭channel

关闭channel是Go并发编程中的一个重要操作,它通常用于发送方通知接收方没有更多数据要发送。

4.1 关闭语法与行为

关闭channel:

close(ch) // 关闭channel ch

关闭后的行为:

  • 向已关闭的channel发送数据会引发panic
  • 从已关闭的channel接收数据会立即返回对应类型的零值,第二个布尔值返回false
  • 关闭一个已经关闭的channel会引发panic
  • 关闭nil channel会引发panic

4.2 检测channel是否关闭

可以使用接收操作的第二个返回值来检测channel是否已关闭:

value, ok := <-ch
if !ok {
    fmt.Println("channel已关闭")
}

或者在for-range循环中隐式检测关闭状态:

for v := range ch {
    // 当ch关闭且为空时,循环自动结束
    fmt.Println(v)
}

4.3 关闭channel的最佳实践

关闭channel时应遵循以下原则:

  1. 发送方负责关闭:通常,发送数据的一方应该负责关闭channel
  2. 不要关闭只接收的channel:从类型安全的角度,不应该关闭只接收的channel
  3. 使用defer关闭:在适当的地方使用defer确保channel被关闭
  4. 不确定时不要关闭:如果不确定是否需要关闭,或者不知道何时关闭,可以不关闭它

示例 - 优雅地关闭channel:

func producer(ch chan<- int) {
    defer close(ch) // 确保channel在函数返回时关闭
    for i := 0; i < 5; i++ {
        ch <- i
    }
    // 函数结束时,ch会被关闭
}

func consumer(ch <-chan int) {
    for v := range ch {
        fmt.Println(v)
    }
    fmt.Println("channel已关闭,消费完毕")
}

func main() {
    ch := make(chan int)
    go producer(ch)
    consumer(ch)
}

五、channel应用模式

channel在Go并发编程中有很多常见的应用模式,这些模式可以帮助我们解决各种并发问题。

5.1 信号通知

使用channel发送信号通知其他goroutine某个事件已经发生:

func worker(done chan struct{}) {
    fmt.Println("工作开始...")
    time.Sleep(3 * time.Second)
    fmt.Println("工作完成")
    done <- struct{}{} // 发送完成信号
}

func main() {
    done := make(chan struct{})
    go worker(done)
    
    <-done // 等待工作完成
    fmt.Println("收到完成信号,主程序继续执行")
}

5.2 超时控制

结合select和time.After实现超时控制:

func main() {
    ch := make(chan string)
    
    go func() {
        time.Sleep(2 * time.Second)
        ch <- "操作完成"
    }()
    
    select {
    case result := <-ch:
        fmt.Println(result)
    case <-time.After(1 * time.Second):
        fmt.Println("操作超时")
    }
}

5.3 工作池模式

使用带缓冲的channel实现工作池,限制并发数量:

func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        fmt.Printf("worker %d started job %d\n", id, job)
        time.Sleep(time.Second) // 模拟工作耗时
        fmt.Printf("worker %d finished job %d\n", id, job)
        results <- job * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)
    
    // 启动3个worker
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }
    
    // 发送9个任务
    for j := 1; j <= 9; j++ {
        jobs <- j
    }
    close(jobs)
    
    // 收集结果
    for a := 1; a <= 9; a++ {
        <-results
    }
}

5.4 流水线模式

使用channel组合多个处理阶段,形成数据流水线:

func generator(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for _, n := range nums {
            out <- n
        }
    }()
    return out
}

func square(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range in {
            out <- n * n
        }
    }()
    return out
}

func main() {
    // 组合成处理流水线: generator -> square -> consumer(打印)
    for n := range square(generator(1, 2, 3, 4, 5)) {
        fmt.Println(n)
    }
}

六、常见错误与最佳实践

6.1 常见错误

使用channel时常见的错误包括:

  1. 向已关闭的channel发送数据:会导致panic

    ch := make(chan int)
    close(ch)
    ch <- 1 // panic: send on closed channel
    
  2. 读取已关闭的空channel:会立即返回零值,可能导致逻辑错误

    ch := make(chan int)
    close(ch)
    val := <-ch // val将为0,不会阻塞
    
  3. 忘记关闭channel:可能导致goroutine泄漏或死锁

    ch := make(chan int)
    go func() {
        for i := 0; i < 5; i++ {
            ch <- i
        }
        // 忘记关闭channel
    }()
    
    for range ch { // 永远不会结束,导致死锁
        // ...
    }
    
  4. 死锁:所有goroutine都在等待,导致程序无法继续执行

    ch := make(chan int)
    <-ch // 阻塞等待数据,但没有其他goroutine会发送数据
    

6.2 最佳实践

使用channel的一些最佳实践:

  1. 清晰的所有权模型:明确哪个goroutine负责关闭channel

  2. 使用单向channel:在函数参数中使用chan<-和<-chan限制操作,提高类型安全性

  3. 使用buffered channel减少阻塞:对于已知生产速度和消费速度的场景,使用适当大小的缓冲区

  4. 结合select和超时:避免永久阻塞

  5. 使用context包进行取消控制:配合channel实现可取消的操作

  6. 考虑channel的替代方案:某些场景下,sync.Mutex或atomic可能更合适

示例 - 避免死锁的超时处理:

func doWork(ctx context.Context) (string, error) {
    ch := make(chan string)
    
    go func() {
        // 模拟耗时操作
        time.Sleep(time.Second * 2)
        ch <- "工作结果"
    }()
    
    select {
    case result := <-ch:
        return result, nil
    case <-ctx.Done():
        return "", ctx.Err()
    case <-time.After(time.Second): // 超时机制
        return "", errors.New("操作超时")
    }
}

func main() {
    ctx := context.Background()
    result, err := doWork(ctx)
    if err != nil {
        fmt.Println("错误:", err)
        return
    }
    fmt.Println("结果:", result)
}

👨‍💻 关于作者与Gopher部落

"Gopher部落"专注于Go语言技术分享,提供从入门到精通的完整学习路线。

🌟 为什么关注我们?

  1. 系统化学习路径:本系列56篇文章循序渐进,带你完整掌握Go开发
  2. 实战驱动教学:理论结合实践,每篇文章都有可操作的代码示例
  3. 持续更新内容:定期分享最新Go生态技术动态与大厂实践经验
  4. 专业技术社区:加入我们的技术交流群,与众多Go开发者共同成长

📱 关注方式

  1. 微信公众号:搜索 “Gopher部落” 或 “GopherTribe”
  2. 优快云专栏:点击页面右上角"关注"按钮

💡 读者福利

关注公众号回复 “Go学习” 即可获取:

  • 完整Go学习路线图
  • Go面试题大全PDF
  • Go项目实战源码
  • 定制学习计划指导

期待与您在Go语言的学习旅程中共同成长!

go语言学习笔记 — 进阶 — 并发编程(6):通道(channel)—— 使用channel收发数据 创建channel之后,我们可以使用channel进行数据收发操作。 使用channel发送(填充)数据 使用特殊的操作符<-,把数据通过channel发送。 格式 通道变量 <- 值 通道变量是上文通过make创建的通道实例;值可以是变量、常量、表达式或函数返回值,值类型必须与make(chan 数据类型)中的数据类型一致。 发送数据 在使用make创建channel后,就可以使用<-向通道发送数据。 ch := make(chan interface{}) // 创建一个空接 阅读详情

相关推荐

详解如何在Golang中监听多个channel

这篇文章主要为大家详细介绍了如何在Golang中实现监听多个channel,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下。

ai520wangzha的博客 1083

Go 并发编程基础:通道(Channel)的使用

Channel是 Go 语言中实现“通信顺序进程”(CSP)模型的关键组件。• 用于在多个 Goroutine 之间传递数据• 本质上是一个先进先出的队列• 保证并发安全var ch chan 数据类型。

tenkofx的博客 1422

[go]使用channel实现读写锁

这里建议直接看go的锁源码,实现的太优雅了!尤其是读写锁。通过对readerCount正负的控制就实现对读写操作的控制。我这里就是简单使用channel代替信号量的传递。 锁 type Mutex struct { mu chan struct{} } func NewMutex() *Mutex { return &Mutex{mu: make(chan struct{}, 1)} } func (m *Mutex) Lock() { m.mu <- struct{}{} } f

CRAJA的blog 1538

go语言高并发channel详解

go语言中channel可以理解是一个先进先出的队列,通过管道进行通信。

qq_30505673的博客 7284

Go语言并发编程:如何利用channel实现优雅的并发控制

Go语言的并发模型基于CSP(Communicating Sequential Processes)理论,通过goroutine和channel实现轻量级并发。本文聚焦channel的核心作用——在goroutine之间建立安全的通信桥梁,解决并发控制中的同步、资源调度和数据一致性问题。我们将从基础原理出发,逐步深入到复杂场景的工程实践,涵盖channel的设计哲学、使用模式和性能优化。核心概念:解析channel的本质、分类及与goroutine的协同机制原理剖析。

AI Agent Harness Engineering 566

浮点数运算的陷阱与启示:FP16加法器/乘法器开发中的常见误区

本文深入探讨FP16加法器和乘法器开发中的常见误区,包括FP16格式解析、阶码对齐陷阱、尾数运算风险及规格化处理。通过Verilog实战代码展示防御性编程技巧,帮助开发者避免边界条件处理和异常状态管理中的错误,提升硬件设计的可靠性和效率。

dell8的博客 250

Go 使用channel控制并发

为什么80%的码农都做不了架构师?>>> ...

weixin_34367845的博客 1244

串行通信原理及实验仿真

1. 处理器与外部设备通信的两种方式: 并行通信和串行通信 并行通信是指数据的各个位用多条数据线同时进行传输 优点:传输速度快 缺点:占用引脚资源多 串行通信是将数据分成一位一位的形式在一条传输线上逐个传输 优点:通信线路简单,占用引脚资源少 缺点:传输速度慢 2. 串行通信的通信方式: 同步通信和异步通信 同步通信:带有时钟同步信号的数据传输;发送方和接收方在同一时钟的控制下,同步传输数据。 异步通信:不同时钟同步信号的数据传输。发送方和接收方使用各自的时钟控制数据的发送和接收过程。 3. 串行通

gemengxia的博客 4322

Go并发编程实践

Go语言最大的特点就是并发编程,它实现了一种基于协程的并发模型。轻量级的协程模型减少了线程模型的切换消耗,大大增加了并发性能。本书这时对Go语言并发进行深入讲解的不可多得的好书。 书籍目录 第1章 初识Go语言 1 1.1 语言特性 1 1.2 安装和设置 2 1.3 工程结构 3 1.3.1 工作区 3 1.3.2 GOPATH 4 1.3.3 源码文件 5 1.3.4 代码包 ...

shuningzhang的专栏 1541

达妙电机MIT模式调参实战:如何从‘曲线偏离’到‘完美追踪’正弦波?

本文详细解析了达妙电机MIT模式调参实战,从曲线偏离到完美追踪正弦波的方法论。通过MATLAB实时曲线分析,深入探讨位置环(kp)和速度环(kd)的双重调节技巧,帮助工程师优化参数组合,提升电机控制精度和稳定性。

weixin_29053073的博客 731

RV1126双摄IMX577驱动移植避坑:从RK3588到RV1126的dts配置与内存崩溃解决实录

本文详细记录了在RV1126平台上移植RK3588的IMX577双摄驱动的完整过程,重点解决了dts配置与内存崩溃问题。通过分析硬件架构差异、驱动移植关键点及CMA内存越界问题,提供了两种有效解决方案,并分享了双摄同步优化与网络调试的实战经验,为嵌入式视觉系统开发提供实用参考。

weixin_42641741的博客 405

42-【Go语言-Day 42】并发通信的艺术:深入理解 Channel 的创建、使用与死锁

本文将深入探讨 Go 并发编程的核心利器——Channel。我们将从 Go 的并发哲学出发,系统学习 Channel 的创建、基本收发操作,并重点剖析其核心的阻塞机制。最后,我们将直面并发编程中最常见的陷阱——死锁,通过实例分析其产生的原因和场景,为下一篇学习更高级的 Channel 用法打下坚实的基础。

吴师兄大模型的博客 952

从仿真到实物:Realsense D435+ROS2焊接机械臂的视觉标定与数据验证指南

本文详细介绍了如何将Realsense D435相机与ROS2焊接机械臂从仿真环境迁移到实物部署的全过程。通过视觉标定、数据验证和焊接场景优化,确保系统在真实环境中精准可靠。重点讲解了硬件配置、驱动优化、数据一致性验证及焊接应用的特殊处理,为工业自动化开发者提供实用指南。

weixin_28701593的博客 407

go语言学习笔记 — 并发编程 — 通道channel(2):使用通道收发数据

创建channel之后,我们可以使用channel进行数据收发操作。 2.1 使用channel发送(填充)数据 使用特殊的操作符<-,把数据通过channel发送。 格式 通道变量 <- 值 通道变量是上文通过make创建的通道实例;值可以是变量、常量、表达式或函数返回值,值类型必须与make(chan 数据类型)中的数据类型一致。 发送数据 在使用make创建chann...

yjk13703623757的博客 856

Cursor使用教程,有点难

Cursor Code Editor 不仅仅是一个代码编辑器,它更是一个集成了 AI 的全功能开发环境,帮助你从代码编写到优化的整个过程变得更加智能和高效。它通过内置的 AI 代码补全、错误检测和自动修复功能,大大减少了手动编写代码的负担,帮助你快速完成开发任务。同时,Cursor 还与多种前沿的 AI 大模型集成,包括 OpenAI 的 GPT-4o、Claude 3.5 和 Meta 的 Llama 3.1。

xiaofeitunAI的博客 1万+

Gazebo仿真UUV水下机器人:从环境搭建到避障算法实战

本文详细介绍了使用Gazebo仿真平台进行UUV水下机器人开发的全流程,涵盖从环境搭建、uuv_simulator配置、传感器集成到核心避障算法实战。通过搭建水下仿真实验室并实现基于激光雷达数据的反应式避障算法,为水下机器人算法验证与开发提供了高效、低成本的解决方案。

weixin_28840591的博客 263

go中的数据结构通道-channel

1. channel的使用   很多文章介绍channel的时候都和并发揉在一起,这里我想把它当做一种数据结构来单独介绍它的实现原理。   channel,通道。golang中用于数据传递的一种数据结构。是golang中一种传递数据的方式,也可用作事件通知。 1.1 声明、传值、关闭   使用chan关键字声明一个通道,在使用前必须先创建,操作符<-用于指定通道的方向,发送或接收...

山有山的高度,海有海的胸怀。 1771

STM32N6570-DK ISP调试

将下载线接入STLINK V3EC接口,将BOOT1拨码拨至右边,即将其置为H,上电或已经上电的按复位,打开STM32CubeProgrammer,设置start address:0x70000000。板子消耗电流较大,即使缩短数据线接了USB供电依然不能正常点亮显示屏,最终通过板上CN2端子加了外部5V供电,仍然需要560mA的外部电流。3.烧录bin文件:STM32N6_ISP_IQTune_App_revC01-v1.1.0-trusted.bin。

liu的博客 1065

GoLang之channel有哪些应用(12)

GoLang之channel有哪些应用(11)

weixin_52690231的博客 240

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

【Go语言-Day 43】Channel 进阶:解锁有缓冲、关闭、遍历与单向通道的并发神技

本文将作为上一篇的进阶,带你深入探索 Channel 的高级特性,包括**有缓冲 Channel**、**Channel 的关闭与遍历**,以及**单向 Channel**。掌握这些技巧,你将能编写出更高效、更健壮、更优雅的并发程序,真正发挥 Go 语言“为并发而生”的强大威力。

吴师兄大模型的博客 1332

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

golang 并发编程基础

转载:http://c2pblog.sinaapp.com/archives/450 当今硬件发展迅速,CPU早就变成多核心了,如何处理并发编程以适应多核CPU是每一种现代编程语言做重视的点。golang自出生起就宣扬着并发编程,原生的goroutines和channel 很简洁的支持了复杂的并行操作。 http://blog.youkuaiyun.com/gdutliuyun827/articl

余功鹏的专栏 1008

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

go并发编程实战 第二版 pdf_千锋Go语言视频教程之Golang进阶实战编程(进阶必看)...

Go是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。近两年,Go语言的发展速度非常快,一方面Go语言有强大的行业背书,另一方面Go语言在设计时加强了大数据量、高并发等应用场景的处理能力。想要学好Go语言需要理论与实战兼备,接下来千锋小编就给大家分享学完基础知识之后必看的实战教程。课程名称千锋Go语言视频教程之Golang进阶实战编程课程介绍《Go语言基础进阶视频》是在《Go语言基础...

weixin_39623620的博客 769

go select 与 for 区别_go语言学习笔记(29)goroutine和channel

goroutine基本介绍假如我们要计算 1-200000里哪些是素数?1:我们传统方法就是使用一个循环,循环判断各个数是不是素数。2:使用并发或者并行的方式,将统计素数的任务分配给多个goroutine去完成。进程和线程说明1:进程就是程序在操作系统中的一个执行过程,是系统进行资源分配和调度的基本单位。2:线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位。3:...

weixin_32620265的博客 360

【Go语言-Day 44】并发神器 select:从入门到精通,解锁 Channel 多路复用

在 Go 语言的并发世界里,Goroutine 是轻盈的执行单元,而 Channel 则是它们之间安全通信的桥梁。然而,当一个 Goroutine 需要同时与多个 Channel 交互时,简单的顺序读写会引发阻塞和效率问题。`select` 语句正是为解决这一挑战而生,它是 Go 并发编程中实现多路复用的核心利器。本文将从 `select` 的诞生背景出发,系统性地剖析其核心语法与工作机制,并通过三大经典应用场景——超时控制、非阻塞操作、优雅退出,深入展示其强大的功能。最后,我们将探讨 `select` 的

吴师兄大模型的博客 910

go channel OK pattern判断关闭

ok pattern 测试, func main() { c := make(chan []int) a := []int{1, 2, 3, 4} go func() { c <- a close(c) }() for i := 1; i <= 3; i++ { t, ok := <-...

wtan825的专栏 1546

《Go 语言快速入门:30 天掌握核心语法与实战逻辑》

欢迎来到《Go 语言快速入门:30 天掌握核心语法与实战逻辑》!作为专业智能创作助手,我将为你设计一个结构清晰、高效的学习计划。本计划基于Go语言的核心特性(如并发模型、简洁语法和实战应用),帮助你在30天内从零基础到掌握核心技能。每天聚焦一个主题,结合理论讲解和代码实战,确保内容真实可靠。计划强调循序渐进,每天学习时间约1-2小时,适合初学者。如果你有具体问题(如某天主题的细节),欢迎随时提问,我会进一步展开讲解。加油,30天后你将成为Go语言高手!

2501_93876619的博客 1048
上一篇: 【Go语言学习系列28】并发编程(一):goroutine基础
下一篇: 【Go语言学习系列30】并发编程(三):select语句
Gopher部落
博客等级 码龄2年 784粉丝 92原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gopher部落

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值