当前位置: 首页 > article >正文

15分钟学 Go 第 47 天 :并发进阶——深入了解Go语言的并发模型!

第47天的学习:并发进阶——深入了解Go语言的并发模型!

目录

  1. Go并发模型简介
  2. Goroutines深度讲解
  3. Channels的进阶使用
  4. Select语句详解
  5. 并发模型设计模式
  6. 实战案例分析
  7. 常见问题与解决方案

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:可以异步通信。
市场管理员求和的例子
  • **设想场景:**市场末端有多个传感器会自动将商品数量推送到中央系统,由系统统计总和。
操作解释
创建Channelc := 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大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!


http://www.kler.cn/a/389807.html

相关文章:

  • ssm100医学生在线学习交流平台+vue(论文+源码)_kaic
  • SAP_MM_SD_PP_FICO_视频课程几乎免费送
  • wafw00f源码详细解析
  • 单元测试、集成测试、系统测试有什么区别
  • 超市里的货物架调整(算法解析)|豆包MarsCode AI刷题
  • LeetCode【0027】移除元素
  • 【Go】-gRPC入门
  • gitlab ci/cd搭建及使用笔记
  • 高效工位管理:Spring Boot企业级系统
  • 【K8S系列 】在K8S集群怎么查看各个pod占用的资源大小与详细解决方案【已解决】
  • itextpdf打印A5的问题
  • 【一步步开发AI运动小程序】二十一、如果将AI运动项目配置持久化到后端?
  • 【非关系型数据库】【IOT设备】InfluxDB、TimescaleDB、Cassandra和MongoDB
  • Qml 中的那些坑(七)---ComboBox嵌入Popup时,滚动内容超过其可见区域不会关闭ComboBox弹窗
  • C++ | Leetcode C++题解之第559题N叉树的最大深度
  • 蓝牙 SPP 协议详解及 Android 实现
  • 《深入理解 == 与 equals ():Java 中对象比较的奥秘》
  • GIN:逼近WL-test的GNN架构
  • 分布式数据库:深入探讨架构、挑战与未来趋势
  • 鸿蒙Flutter实战:13-鸿蒙应用打包上架流程
  • 随堂测微信小程序ssm+论文源码调试讲解
  • MongoDB 详解:深入理解与探索
  • IOS开发之MapKit定位国内不准的问题
  • LLaMA-Factory全流程训练模型
  • Flink输出算子
  • Tcp中的流量控制,拥塞控制,超时重传时间的选择,都附带相应例子说明