Go语言八股之并发详解

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。

非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。💝💝💝 ✨✨ 欢迎订阅本专栏 ✨✨

前言

小郑最近在准备Go语言的面试题,通过github和b站等各种学习网站上学习go语言的八股文,并且整理出自己觉得面试可能会问到的知识点,希望通过做笔记的方式来巩固自己的知识点,并且也希望可以帮助到大家在面试的时候更加得心应手一些,那么从现在开始,和我一起加入八股学习之旅吧!

1.Go 中主协程如何等待其余协程退出?

答:Go 的 sync.WaitGroup 是等待一组协程结束,sync.WaitGroup 只有 3 个方法,Add()是添加计数,Done()减去一个计数,Wait()阻塞直到所有的任务完成。Go 里面还能通过有缓冲的 channel 实现其阻塞等待一组协程结束,这个不能保证一组 goroutine 按照顺序执行,可以并发执行协程。Go 里面能通过无缓冲的 channel 实现其阻塞等待一组协程结束,这个能保证一组 goroutine 按照顺序执行,但是不能并发执行。

Add(n int):设置需要等待的协程数量。通常在启动协程之前调用,传入一个整数,表示需要等待的协程数量。

Done():每个协程完成时调用,通常在协程的最后,减少等待的计数。

Wait():主协程通过调用 Wait() 来阻塞,直到计数器减少到零,表示所有协程都完成。

2.多个 goroutine 对同一个 map 写会 panic,异常是否可以用 defer 捕获?

**可以捕获异常,但是只能捕获一次,**Go语言,可以使用多值返回来返回错误。不要用异常代替错误,更不要用来控制流程。在极个别的情况下,才使用Go中引入的Exception处理:defer, panic, recover Go中,对异常处理的原则是:多用error包,少用panic

3.golang实现多并发请求(发送多个get请求)

go语言中其实有两种方法进行协程之间的通信。一个是共享内存、一个是消息传递
共享内存(互斥锁)

复制代码
//基本的GET请求
package main
 
import (
    "fmt"
    "io/ioutil"
    "net/http"
    "time"
    "sync"
    "runtime"
)
 
// 计数器
var counter int = 0
 
func httpget(lock *sync.Mutex){
    lock.Lock()
    counter++
    resp, err := http.Get("http://localhost:8000/rest/api/user")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
    fmt.Println(resp.StatusCode)
    if resp.StatusCode == 200 {
        fmt.Println("ok")
    }
    lock.Unlock()
}
 
func main() {
    start := time.Now()
    lock := &sync.Mutex{}
    for i := 0; i < 800; i++ {
        go httpget(lock)
    }
    for  {
        lock.Lock()
        c := counter
        lock.Unlock()
        runtime.Gosched()
        if c >= 800 {
            break
        }
    }
    end := time.Now()
    consume := end.Sub(start).Seconds()
    fmt.Println("程序执行耗时(s):", consume)
}

问题

我们可以看到共享内存的方式是可以做到并发,但是我们需要利用共享变量来进行协程的通信,也就需要使用互斥锁来确保数据安全性,导致代码啰嗦,复杂话,不易维护。我们后续使用go的消息传递方式避免这些问题。
消息传递(管道)

复制代码
//基本的GET请求
package main
 
import (
    "fmt"
    "io/ioutil"
    "net/http"
    "time"
)
// HTTP get请求
func httpget(ch chan int){
    resp, err := http.Get("http://localhost:8000/rest/api/user")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
    fmt.Println(resp.StatusCode)
    if resp.StatusCode == 200 {
        fmt.Println("ok")
    }
    ch <- 1
}
// 主方法
func main() {
    start := time.Now()
    // 注意设置缓冲区大小要和开启协程的个人相等
    chs := make([]chan int, 2000)
    for i := 0; i < 2000; i++ {
        chs[i] = make(chan int)
        go httpget(chs[i])
    }
    for _, ch := range chs {
        <- ch
    }
    end := time.Now()
    consume := end.Sub(start).Seconds()
    fmt.Println("程序执行耗时(s):", consume)
}

总结:

我们通过go语言的管道channel来实现并发请求,能够解决如何避免传统共享内存实现并发的很多问题而且效率会高于共享内存的方法。

4.sync.pool

<font style="color:rgb(5, 7, 59);">sync.Pool</font> 是 Go 语言在标准库 <font style="color:rgb(5, 7, 59);">sync</font> 包中提供的一个类型,它主要用于存储和复用临时对象,以减少内存分配的开销,提高性能。以下是对 <font style="color:rgb(5, 7, 59);">sync.Pool</font> 的详细解析:

4.1基本概念

<font style="color:rgb(5, 7, 59);">sync.Pool</font> 是一个可以存储任意类型的临时对象的集合。当你需要一个新的对象时,可以先从 <font style="color:#DF2A3F;">sync.Pool</font> 中尝试获取;如果 <font style="color:#DF2A3F;">sync.Pool</font> 中有可用的对象,则直接返回该对象;如果没有,则需要自行创建。使用完对象后,可以将其放回 <font style="color:#DF2A3F;">sync.Pool</font> 中,以供后续再次使用。

4.2主要特点
  1. 减少内存分配和垃圾回收(GC)压力 :通过复用已经分配的对象,<font style="color:rgb(5, 7, 59);">sync.Pool</font> 可以显著减少内存分配的次数,从而减轻 GC 的压力,提高程序的性能。
  2. 并发安全<font style="color:rgb(5, 7, 59);">sync.Pool</font> 是 Goroutine 并发安全的,多个 Goroutine 可以同时从 <font style="color:rgb(5, 7, 59);">sync.Pool</font> 中获取和放回对象,而无需额外的同步措施。
  3. 自动清理 :Go 的垃圾回收器在每次垃圾回收时,都会清除 <font style="color:rgb(5, 7, 59);">sync.Pool</font> 中的所有对象。因此,你不能假设一个对象被放入 <font style="color:rgb(5, 7, 59);">sync.Pool</font> 后就会一直存在。
4.3使用场景

<font style="color:rgb(5, 7, 59);">sync.Pool</font> 适用于以下场景:

  • 对象实例创建开销较大的场景,如数据库连接、大型数据结构等。
  • 需要频繁创建和销毁临时对象的场景,如 HTTP 处理函数中频繁创建和销毁的请求上下文对象。
4.4使用方法
  1. 创建 Pool 实例 :首先,你需要创建一个 <font style="color:rgb(5, 7, 59);">sync.Pool</font> 的实例,并配置 <font style="color:rgb(5, 7, 59);">New</font> 方法。<font style="color:rgb(5, 7, 59);">New</font> 方法是一个无参函数,用于在 <font style="color:rgb(5, 7, 59);">sync.Pool</font> 中没有可用对象时创建一个新的对象。

    var pool = &sync.Pool{
    New: func() interface{} {
    return new(YourType) // 替换 YourType 为你的类型
    },
    }

  2. 获取对象 :使用 <font style="color:rgb(5, 7, 59);">Get</font> 方法从 <font style="color:rgb(5, 7, 59);">sync.Pool</font> 中获取对象。<font style="color:rgb(5, 7, 59);">Get</font> 方法会返回 <font style="color:rgb(5, 7, 59);">sync.Pool</font> 中已经存在的对象(如果存在的话),或者调用 <font style="color:rgb(5, 7, 59);">New</font> 方法创建一个新的对象。

obj := pool.Get().(*YourType) // 替换 YourType 为你的类型,并进行类型断言

  1. 使用对象:获取到对象后,你可以像使用普通对象一样使用它。
  2. 放回对象 :使用完对象后,使用 <font style="color:rgb(5, 7, 59);">Put</font> 方法将对象放回 <font style="color:rgb(5, 7, 59);">sync.Pool</font> 中,以供后续再次使用。

pool.Put(obj)

4.5注意事项
  1. 对象状态未知 :从 <font style="color:rgb(5, 7, 59);">sync.Pool</font> 中获取的对象的状态是未知的。因此,在使用对象之前,你应该将其重置到适当的初始状态。
  2. 自动清理 :由于 Go 的垃圾回收器会清理 <font style="color:rgb(5, 7, 59);">sync.Pool</font> 中的对象,因此你不能依赖 <font style="color:rgb(5, 7, 59);">sync.Pool</font> 来长期存储对象。
  3. 不适合所有场景<font style="color:rgb(5, 7, 59);">sync.Pool</font> 并不适合所有需要对象池的场景。特别是对于那些需要精确控制对象生命周期的场景,你可能需要实现自定义的对象池。

总的来说,<font style="color:rgb(5, 7, 59);">sync.Pool</font> 是 Go 语言提供的一个非常有用的工具,它可以帮助你减少内存分配和垃圾回收的开销,提高程序的性能。然而,在使用时需要注意其特性和局限,以免发生不可预见的问题。

❤️❤️❤️小郑是普通学生水平,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄

💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍

相关推荐
大G哥1 小时前
用 Go 和 TensorFlow 实现图像验证码识别系统
开发语言·后端·golang·tensorflow·neo4j
李匠20243 小时前
C++GO语言微服务项目之 go语言基础语法
开发语言·c++·后端·golang
一个W牛3 小时前
精选面试题
javascript·面试
独行soc4 小时前
2025年渗透测试面试题总结-网络安全、Web安全、渗透测试笔试总结(一)(附回答)(题目+回答)
linux·运维·服务器·安全·web安全·面试·职场和发展
sszmvb12345 小时前
unittest自动化测试实战
软件测试·面试
半桔7 小时前
定长滑动窗口---初阶篇
数据结构·c++·算法·leetcode·面试
朱颜辞镜花辞树‎9 小时前
Go Web 后台管理系统项目详解
开发语言·前端·golang
Paran-ia9 小时前
【2025年】MySQL面试题总结
数据库·mysql·面试
是垚不是土11 小时前
Go语言中的并发编程--详细讲解
java·运维·开发语言·算法·golang·运维开发