1。运行每个示例:不要只阅读代码。输入它,运行它,然后观察其行为。⚠️ 这个系列如何进行?
2。实验和打破常规: 删除睡眠并看看会发生什么,更改通道缓冲区大小,修改 goroutine 计数。
打破东西会教你它们是如何工作的
3。关于行为的原因: 在运行修改后的代码之前,尝试预测结果。当您看到意外行为时,请停下来思考原因。挑战解释。
4。建立心理模型:每个可视化代表一个概念。尝试为修改后的代码绘制自己的图表。
这是“掌握 Go 并发”系列的 第 1 部分,我们将介绍:
- goroutine 的工作原理及其生命周期
- goroutines 之间的通道通信
- 缓冲通道及其用例
- 实际示例和可视化
我们将从基础知识开始,逐步发展如何有效使用它们的直觉。
这会有点长,相当长,所以做好准备。
我们将亲力亲为完成整个过程。
Goroutine 的基础
让我们从一个下载多个文件的简单程序开始。
package main import ( "fmt" "time" ) func downloadFile(filename string) { fmt.Printf("Starting download: %s\n", filename) // Simulate file download with sleep time.Sleep(2 * time.Second) fmt.Printf("Finished download: %s\n", filename) } func main() { fmt.Println("Starting downloads...") startTime := time.Now() downloadFile("file1.txt") downloadFile("file2.txt") downloadFile("file3.txt") elapsedTime := time.Since(startTime) fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime) }
该程序总共需要 6 秒,因为每个 2 秒的下载必须在下一个开始之前完成。让我们想象一下:
我们可以缩短这个时间,让我们修改我们的程序以使用go例程:
注意:函数调用前使用 go 关键字
package main import ( "fmt" "time" ) func downloadFile(filename string) { fmt.Printf("Starting download: %s\n", filename) // Simulate file download with sleep time.Sleep(2 * time.Second) fmt.Printf("Finished download: %s\n", filename) } func main() { fmt.Println("Starting downloads...") // Launch downloads concurrently go downloadFile("file1.txt") go downloadFile("file2.txt") go downloadFile("file3.txt") fmt.Println("All downloads completed!") }
等等什么?没有打印任何内容?为什么?
让我们想象一下这一点,以了解可能发生的情况。
从上面的可视化中,我们了解到 main 函数在 goroutine 完成之前就存在了。一项观察结果是,所有 goroutine 的生命周期都依赖于 main 函数。
注意:main函数本身就是一个goroutine;)
为了解决这个问题,我们需要一种方法让主 goroutine 等待其他 goroutine 完成。有几种方法可以做到这一点:
- 等待几秒钟(黑客方式)
- 使用 WaitGroup(正确的方法,下一步)
- 使用频道(我们将在下面介绍)
让我们等待几秒钟让 go 例程完成。
package main import ( "fmt" "time" ) func downloadFile(filename string) { fmt.Printf("Starting download: %s\n", filename) // Simulate file download with sleep time.Sleep(2 * time.Second) fmt.Printf("Finished download: %s\n", filename) } func main() { fmt.Println("Starting downloads...") startTime := time.Now() downloadFile("file1.txt") downloadFile("file2.txt") downloadFile("file3.txt") elapsedTime := time.Since(startTime) fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime) }
问题是,我们可能不知道 goroutine 可能需要多长时间。在这种情况下,我们每个人都有固定的时间,但在实际场景中,我们知道下载时间会有所不同。
来了sync.WaitGroup
Go中的sync.WaitGroup是一种并发控制机制,用于等待一组goroutines执行完成。
在这里让我们看看它的实际效果并可视化:
package main import ( "fmt" "time" ) func downloadFile(filename string) { fmt.Printf("Starting download: %s\n", filename) // Simulate file download with sleep time.Sleep(2 * time.Second) fmt.Printf("Finished download: %s\n", filename) } func main() { fmt.Println("Starting downloads...") // Launch downloads concurrently go downloadFile("file1.txt") go downloadFile("file2.txt") go downloadFile("file3.txt") fmt.Println("All downloads completed!") }
让我们可视化这一点并了解sync.WaitGroup 的工作原理:
计数器机制:
- WaitGroup 维护一个内部计数器
- wg.Add(n) 将计数器增加 n
- wg.Done() 将计数器减 1
- wg.Wait() 阻塞,直到计数器达到 0
同步流程:
- 主 Goroutine 在启动 Goroutines 之前调用 Add(3)
- 每个 goroutine 完成时都会调用 Done()
- 主协程在 Wait() 处被阻塞,直到计数器达到 0
- 当计数器达到 0 时,程序继续并干净地退出
要避免的常见陷阱
package main
import (
"fmt"
"time"
)
func downloadFile(filename string) {
fmt.Printf("Starting download: %s\n", filename)
// Simulate file download with sleep
time.Sleep(2 * time.Second)
fmt.Printf("Finished download: %s\n", filename)
}
func main() {
fmt.Println("Starting downloads...")
startTime := time.Now() // Record start time
go downloadFile("file1.txt")
go downloadFile("file2.txt")
go downloadFile("file3.txt")
// Wait for goroutines to finish
time.Sleep(3 * time.Second)
elapsedTime := time.Since(startTime)
fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime)
}
渠道
这样我们就很好地理解了 goroutine 是如何工作的。不,两个 Go 例程如何通信?这就是频道发挥作用的地方。
Go 中的Channels 是一个强大的并发原语,用于 goroutine 之间的通信。它们为 goroutine 提供了一种安全共享数据的方法。
将通道视为管道:一个 goroutine 可以将数据发送到通道,另一个 goroutine 可以接收数据。
以下是一些属性:
- 通道本质上是阻塞的。
- A 发送到通道操作 ch 阻塞直到其他 Goroutine 从通道接收。
- 从通道接收操作阻塞直到其他goroutine发送到通道。
package main import ( "fmt" "time" ) func downloadFile(filename string) { fmt.Printf("Starting download: %s\n", filename) // Simulate file download with sleep time.Sleep(2 * time.Second) fmt.Printf("Finished download: %s\n", filename) } func main() { fmt.Println("Starting downloads...") startTime := time.Now() downloadFile("file1.txt") downloadFile("file2.txt") downloadFile("file3.txt") elapsedTime := time.Since(startTime) fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime) }
为什么 ch
让我们通过添加 goroutine 来解决这个问题
package main import ( "fmt" "time" ) func downloadFile(filename string) { fmt.Printf("Starting download: %s\n", filename) // Simulate file download with sleep time.Sleep(2 * time.Second) fmt.Printf("Finished download: %s\n", filename) } func main() { fmt.Println("Starting downloads...") // Launch downloads concurrently go downloadFile("file1.txt") go downloadFile("file2.txt") go downloadFile("file3.txt") fmt.Println("All downloads completed!") }
让我们想象一下:
这次消息从不同的 Goroutine 发送,因此主 Goroutine 在发送到通道时不会被阻塞,因此它会移动到 msg :=
使用通道修复 main 不等待其他问题
现在让我们使用channel来修复文件下载器问题(main不等待其他人完成)。
package main import ( "fmt" "time" ) func downloadFile(filename string) { fmt.Printf("Starting download: %s\n", filename) // Simulate file download with sleep time.Sleep(2 * time.Second) fmt.Printf("Finished download: %s\n", filename) } func main() { fmt.Println("Starting downloads...") startTime := time.Now() // Record start time go downloadFile("file1.txt") go downloadFile("file2.txt") go downloadFile("file3.txt") // Wait for goroutines to finish time.Sleep(3 * time.Second) elapsedTime := time.Since(startTime) fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime) }
可视化它:
让我们进行一次演练以更好地理解:
节目开始:
主协程创建完成通道
启动三个下载 goroutine
每个 goroutine 都会获得对同一通道的引用
下载执行:
- 所有三个下载同时运行
- 每个需要2秒
- 他们可能会以任何顺序完成
频道循环:
- 主协程进入循环: for i := 0;我
- 每个
- 循环确保我们等待所有三个完成信号
循环行为:
- 迭代 1:阻塞直至首次下载完成
- 迭代 2:阻塞直到第二次下载完成
- 迭代 3:阻塞直至最终下载完成
完成顺序并不重要!
观察:
⭐ 每次发送(完成 ⭐ 主协程通过循环协调一切
两个goroutine如何通信?
我们已经了解了两个 goroutine 如何进行通信。什么时候?一直以来。 我们不要忘记 main 函数也是一个 goroutine。
package main import ( "fmt" "time" ) func downloadFile(filename string) { fmt.Printf("Starting download: %s\n", filename) // Simulate file download with sleep time.Sleep(2 * time.Second) fmt.Printf("Finished download: %s\n", filename) } func main() { fmt.Println("Starting downloads...") startTime := time.Now() downloadFile("file1.txt") downloadFile("file2.txt") downloadFile("file3.txt") elapsedTime := time.Since(startTime) fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime) }
让我们想象一下并试运行一下:
试运行:
程序开始(t=0ms)
第一条消息(t=1ms)
第二条消息(t=101ms)
第三条消息(t=201ms)
通道关闭(t=301ms)
完成(t=302-303ms)
缓冲通道
为什么我们需要缓冲通道?
无缓冲的通道会阻塞发送方和接收方,直到另一方准备好为止。当需要高频通信时,无缓冲的通道可能会成为瓶颈,因为两个 goroutine 必须暂停来交换数据。
缓冲通道属性:
- FIFO(先进先出,类似于队列)
- 固定大小,在创建时设置
- 缓冲区已满时阻止发送者
- 当缓冲区为空时阻塞接收器
我们看到它的实际效果:
package main import ( "fmt" "time" ) func downloadFile(filename string) { fmt.Printf("Starting download: %s\n", filename) // Simulate file download with sleep time.Sleep(2 * time.Second) fmt.Printf("Finished download: %s\n", filename) } func main() { fmt.Println("Starting downloads...") startTime := time.Now() downloadFile("file1.txt") downloadFile("file2.txt") downloadFile("file3.txt") elapsedTime := time.Since(startTime) fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime) }
输出(在取消注释 ch
为什么它没有阻塞主协程?
缓冲通道允许发送至其容量而不阻塞发送者。
通道的容量为 2,这意味着它在阻塞之前可以在缓冲区中保存两个值。
缓冲区已经满了“第一”和“第二”。由于没有并发接收者来使用这些值,因此发送操作会无限期地阻塞。
因为主 goroutine 也负责发送,并且没有其他活动的 goroutine 从通道接收值,所以程序在尝试发送第三条消息时陷入死锁。
取消注释第三条消息会导致死锁,因为容量现在已满,第三条消息将阻塞,直到缓冲区释放。
何时使用缓冲通道与非缓冲通道
Aspect | Buffered Channels | Unbuffered Channels |
---|---|---|
Purpose | For decoupling sender and receiver timing. | For immediate synchronization between sender and receiver. |
When to Use | - When the sender can proceed without waiting for receiver. | - When sender and receiver must synchronize directly. |
- When buffering improves performance or throughput. | - When you want to enforce message-handling immediately. | |
Blocking Behavior | Blocks only when buffer is full. | Sender blocks until receiver is ready, and vice versa. |
Performance | Can improve performance by reducing synchronization. | May introduce latency due to synchronization. |
Example Use Cases | - Logging with rate-limited processing. | - Simple signaling between goroutines. |
- Batch processing where messages are queued temporarily. | - Hand-off of data without delay or buffering. | |
Complexity | Requires careful buffer size tuning to avoid overflows. | Simpler to use; no tuning needed. |
Overhead | Higher memory usage due to the buffer. | Lower memory usage; no buffer involved. |
Concurrency Pattern | Asynchronous communication between sender and receiver. | Synchronous communication; tight coupling. |
Error-Prone Scenarios | Deadlocks if buffer size is mismanaged. | Deadlocks if no goroutine is ready to receive or send. |
要点
使用 缓冲 通道如果:
- 你需要解耦发送者和接收者的时间。
- 批处理或排队消息可以提高性能。
- 当缓冲区已满时,应用程序可以容忍处理消息的延迟。
使用 无缓冲 通道,如果:
- goroutines 之间的同步至关重要。
- 您想要简单并立即传递数据。
- 发送者和接收者之间的交互必须即时发生。
这些基础知识为更高级的概念奠定了基础。在我们即将发布的帖子中,我们将探讨:
下一篇文章:
- 并发模式
- 互斥和内存同步
请继续关注我们,我们将继续加深对 Go 强大并发功能的理解!
以上是通过直观的视觉效果了解 Golang 中的 Goroutines 和 Channel的详细内容。更多信息请关注PHP中文网其他相关文章!

Golangisidealforbuildingscalablesystemsduetoitsefficiencyandconcurrency,whilePythonexcelsinquickscriptinganddataanalysisduetoitssimplicityandvastecosystem.Golang'sdesignencouragesclean,readablecodeanditsgoroutinesenableefficientconcurrentoperations,t

Golang在并发性上优于C ,而C 在原始速度上优于Golang。1)Golang通过goroutine和channel实现高效并发,适合处理大量并发任务。2)C 通过编译器优化和标准库,提供接近硬件的高性能,适合需要极致优化的应用。

选择Golang的原因包括:1)高并发性能,2)静态类型系统,3)垃圾回收机制,4)丰富的标准库和生态系统,这些特性使其成为开发高效、可靠软件的理想选择。

Golang适合快速开发和并发场景,C 适用于需要极致性能和低级控制的场景。1)Golang通过垃圾回收和并发机制提升性能,适合高并发Web服务开发。2)C 通过手动内存管理和编译器优化达到极致性能,适用于嵌入式系统开发。

Golang在编译时间和并发处理上表现更好,而C 在运行速度和内存管理上更具优势。1.Golang编译速度快,适合快速开发。2.C 运行速度快,适合性能关键应用。3.Golang并发处理简单高效,适用于并发编程。4.C 手动内存管理提供更高性能,但增加开发复杂度。

Golang在Web服务和系统编程中的应用主要体现在其简洁、高效和并发性上。1)在Web服务中,Golang通过强大的HTTP库和并发处理能力,支持创建高性能的Web应用和API。2)在系统编程中,Golang利用接近硬件的特性和对C语言的兼容性,适用于操作系统开发和嵌入式系统。

Golang和C 在性能对比中各有优劣:1.Golang适合高并发和快速开发,但垃圾回收可能影响性能;2.C 提供更高性能和硬件控制,但开发复杂度高。选择时需综合考虑项目需求和团队技能。

Golang适合高性能和并发编程场景,Python适合快速开发和数据处理。 1.Golang强调简洁和高效,适用于后端服务和微服务。 2.Python以简洁语法和丰富库着称,适用于数据科学和机器学习。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

ZendStudio 13.5.1 Mac
功能强大的PHP集成开发环境

适用于 Eclipse 的 SAP NetWeaver 服务器适配器
将Eclipse与SAP NetWeaver应用服务器集成。

DVWA
Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

VSCode Windows 64位 下载
微软推出的免费、功能强大的一款IDE编辑器

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)