15分钟学 Go 第 47 天 :并发进阶——深入了解Go语言的并发模型!
第47天的学习:并发进阶——深入了解Go语言的并发模型!
目录
- Go并发模型简介
- Goroutines深度讲解
- Channels的进阶使用
- Select语句详解
- 并发模型设计模式
- 实战案例分析
- 常见问题与解决方案
1. Go并发模型简介
Go语言以其内置的并发支持而闻名。通过轻量级的goroutine和强大的channel,Go提供了一种易于使用且高效的并发编程方法。
并发与并行的区别:
- 并发:处理多件事情的能力,但不一定同时。
- 并行:同一时刻处理多件事情。
2. Goroutines深度讲解
Goroutine是Go语言的基本单位,它比传统线程更轻量。
创建Goroutine
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
fmt.Println(s)
time.Sleep(100 * time.Millisecond)
}
}
func main() {
go say("world")
say("hello")
}
运行流程图
main()
├─ goroutine A : say("world")
└─ goroutine B : say("hello")
Goroutine的特点
- 启动goroutine使用
go
关键字。 - 不阻塞当前程序的运行。
- 实际调度由Go运行时处理。
3. Channels的进阶使用
Channels用于goroutines之间的通信。它们是类型安全的管道。
Channel的基本操作
package main
import (
"fmt"
)
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c
fmt.Println(x, y, x+y)
}
Channel类型
- 无缓冲Channel:通信是同步的。
- 缓冲Channel:可以异步通信。
市场管理员求和的例子
- **设想场景:**市场末端有多个传感器会自动将商品数量推送到中央系统,由系统统计总和。
操作 | 解释 |
---|---|
创建Channel | c := make(chan int) |
发送数据 | c <- x (在goroutine中执行) |
接收数据 | x := <-c |
4. Select语句详解
select
语句类似于 switch
,但用于Channels操作。
package main
import (
"fmt"
"time"
)
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
作用:
- 多路复用:监听多个Channel。
- 处理超时:结合
time.After
实现超时控制。
5. 并发模型设计模式
工作池模型
用于限制同时运行的goroutines数目。
package main
import (
"fmt"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("worker %d started job %d\n", id, j)
time.Sleep(time.Second)
fmt.Printf("worker %d finished job %d\n", id, j)
results <- j * 2
}
}
func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
for a := 1; a <= numJobs; a++ {
<-results
}
}
Pipeline模式
用于串联多个处理阶段。
package main
import (
"fmt"
)
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func sq(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
func main() {
c := gen(2, 3, 4)
out := sq(c)
for n := range out {
fmt.Println(n)
}
}
6. 实战案例分析
为了进一步巩固理解,我们来看一个具体的并发应用示例。
案例:并发Web爬虫
- 目标:使用并发从多个URL抓取页面标题。
package main
import (
"fmt"
"net/http"
"io/ioutil"
"regexp"
"time"
)
func fetch(url string, ch chan<- string) {
start := time.Now()
resp, err := http.Get(url)
if err != nil {
ch <- fmt.Sprintf("Error: %s", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
ch <- fmt.Sprintf("Error reading body: %s", err)
return
}
re := regexp.MustCompile("<title>(.*?)</title>")
matches := re.FindStringSubmatch(string(body))
title := "No title found"
if len(matches) > 1 {
title = matches[1]
}
secs := time.Since(start).Seconds()
ch <- fmt.Sprintf("%.2f seconds: %s", secs, title)
}
func main() {
urls := []string{
"https://golang.org",
"https://godoc.org",
"https://gopl.io",
"https://play.golang.org",
}
ch := make(chan string)
for _, url := range urls {
go fetch(url, ch)
}
for range urls {
fmt.Println(<-ch)
}
}
7. 常见问题与解决方案
在学习并发时,你可能会遇到以下问题:
死锁问题
- 原因:两个goroutine相互等待对方释放资源。
- 解决方法:确保总是有一个goroutine能继续推进。
资源竞争
- 原因:多个goroutine试图同时访问同一个资源。
- 解决方法:使用channel同步,或者使用
sync.Mutex
。
Goroutine泄漏
- 原因:goroutine等待无法到达的事件。
- 解决方法:确保所有channels都能正确关闭。
总结
今天我们深入探讨了Go语言的并发模型。理解如何有效地创建和管理Goroutines和Channels是写出高效并发程序的关键。通过示例代码和设计模式,你学会了如何利用Go的并发特性来解决复杂的问题。
怎么样今天的内容还满意吗?再次感谢观众老爷的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!