Go 并发基础:goroutine 与 channel(面向 Java 开发者)

内容分享7小时前发布
0 0 0

如果你是 Java 后端,第一次接触 Go 并发,大致率会有一个感觉:
“怎么这么简单?是不是哪里藏了坑?”

这篇文章我们就用 Java → Go 的对照视角,把 Go 并发里最核心的两个概念讲清楚:

  • goroutine:Go 的并发执行单元
  • channel:Go 的通信与同步机制

一、先抛结论:Go 并发和 Java 并发“思路完全不同”

Java 并发的核心是:

  • 线程(Thread)
  • 锁(synchronized / Lock)
  • 共享内存 + 显式同步

一句话总结

Java:多个线程抢同一块内存,用锁保证安全


Go 并发的核心是:

  • goroutine(轻量级协程)
  • channel(通信管道)
  • 通信来共享数据(CSP 模型)

一句话总结:

Go:不共享内存,通过通信来传递数据

这不是语法差异,而是并发哲学的差异


二、goroutine:比 Java Thread 轻太多了

1️⃣ Java 创建一个线程

new Thread(() -> {
    System.out.println("hello");
}).start();

问题你必定很熟:

  • 线程创建成本高
  • 线程数多了就要线程池
  • 上下文切换昂贵

2️⃣ Go 创建一个 goroutine

go func() {
    fmt.Println("hello")
}()

只有一个 go 关键字。


3️⃣ goroutine 到底轻在哪里?

对比项

Java Thread

Go goroutine

初始栈大小

~1MB

~2KB

调度方式

OS 调度

Go Runtime 调度

创建成本

极低

数量级

几千就吃力

几十万没问题

所以在 Go 里:

for i := 0; i < 100000; i++ {
    go work(i)
}

这是完全正常的写法


三、channel:Go 并发的灵魂

如果说 goroutine 是“人”,
channel 就是 goroutine 之间说话的方式


1️⃣ 一个最简单的 channel 示例

ch := make(chan int)

go func() {
    ch <- 10 // 发送
}()

v := <-ch   // 接收
fmt.Println(v)

你可以把 channel 理解成:

一个类型安全、可阻塞的队列


2️⃣ channel = 数据通道 + 同步工具

这一点对 Java 程序员超级重大:

ch <- data

这行代码可能会阻塞,直到:

  • 有 goroutine 来接收
  • 或缓冲区未满(buffered channel)

➡️ Go 把“同步”内置进了通信模型


3️⃣ Java 视角下的类比

Go

Java

goroutine

Thread / Runnable

channel

BlockingQueue

<-ch

take()

ch <- x

put(x)

但要注意:

channel 不只是队列,更是并发设计工具


四、无缓冲 vs 有缓冲 channel

1️⃣ 无缓冲 channel(默认)

ch := make(chan int)

特点:

  • 发送和接收必须同时发生
  • 天然同步
  • 常用于信号通知
done := make(chan struct{})

go func() {
    doWork()
    done <- struct{}{}
}()

<-done // 等待完成

2️⃣ 有缓冲 channel

ch := make(chan int, 3)

特点:

  • 类似一个有容量的队列
  • 发送不必定阻塞
  • 常用于生产者-消费者模型

五、一个典型并发模式:fan-out / fan-in

多 goroutine 并发处理任务

jobs := make(chan int)
results := make(chan int)

for i := 0; i < 5; i++ {
    go worker(jobs, results)
}

for j := 0; j < 10; j++ {
    jobs <- j
}
close(jobs)

for i := 0; i < 10; i++ {
    <-results
}

你会发现:

  • 没有锁
  • 没有 synchronized
  • 没有线程池

但并发逻辑超级清晰


六、那 Go 还要不要锁?

要,但不是第一选择

Go 的推荐顺序是:

  1. 优先用 channel
  2. 实在不合适,再用 sync.Mutex
  3. 不要一上来就锁

这也是 Go 官方名言:

Don’t communicate by sharing memory; share memory by communicating.


七、给 Java 程序员的几个“避坑提醒”

⚠️ 1. main goroutine 结束,程序直接退出

go doWork()

如果 main 函数结束了,goroutine 不会等你

➡️ 用 WaitGroup 或 channel 控制生命周期。


⚠️ 2. 不要随意 close channel

  • 发送方关闭
  • 接收方不要关
  • 关闭的是 channel,不是 goroutine

⚠️ 3. 并发不是越多越好

goroutine 很轻,但:

  • I/O
  • CPU
  • 下游依赖

依然是瓶颈。


八、总结一句话

Go 用 goroutine 降低并发成本,用 channel 降低并发复杂度。

如果你是 Java 开发者,转 Go 最大的转变不是语法,而是:

从“锁思维”切换到“通信思维”

© 版权声明

相关文章

暂无评论

none
暂无评论...