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

每日八股文5.30

每日八股-5.30

  • Go
    • 1.Golang中的select语句
    • 2.Select的用途(单次,随机执行完一个case即结束)
    • 3.For-select的使用(多次,直到收到done信号或quit信号才return)
    • 4.Context
    • 5.Context的用途
    • 6.Go程序启动时发生了什么?
    • 7.数据竞争go race
    • 7.Go语言实现优雅退出
    • 8.uintptr和unsafe.Pointer的区别
    • 9.Go语言的限流

Go

1.Golang中的select语句

select是一种go可以处理多个通道之间的机制,看起来和switch语句很相似,但是select其实和IO机制中的select一样,多路复用通道,随机选取一个进行执行,如果说通道(channel)实现了多个goroutine之前的同步或者通信,那么select则实现了多个通道(channel)的同步或者通信,并且select具有阻塞的特性。

每个 case 必须是一个通信操作,要么是发送要么是接收
select 随机执行一个可运行的 case(这样也可以避免饿死的情况)。如果没有case 可运行,它将阻塞,直到有 case 可运行

select {case <-ch1:// 如果从 ch1 信道成功接收数据,则执行该分支代码case ch2 <- 1:// 如果成功向 ch2 信道成功发送数据,则执行该分支代码default:// 如果上面都没有成功,则进入 default 分支处理流程
}

2.Select的用途(单次,随机执行完一个case即结束)

  1. 多路非阻塞通信,使用select语句可以同时监听多个channel,一旦有一个channel满足条件,就可以执行相关操作,而不会像普通的channel那样发生阻塞
func main() {channel1 := make(chan string)channel2 := make(chan int64)go func() {channel1 <- "Message from channel"}()go func() {channel2 <- 100}()// 使用 select 同时监听两个通道,响应第一个就绪的通道select {case msg1 := <-channel1:fmt.Println("Received from channel 1:", msg1)case msg2 := <-channel2:fmt.Println("Received from channel 2:", msg2)}
}
  1. 超时控制,可以结合time.After来设置超时机制,避免无限阻塞
func main() {ch := make(chan struct{})go func() {//time.Sleep(2 * time.Second) // 模拟一个耗时操作time.Sleep(500 * time.Millisecond) // 模拟一个耗时操作ch <- struct{}{}}()select {case <-ch:fmt.Println("成功执行耗时操作")case <-time.After(1 * time.Second):fmt.Println("超时")}
}
  1. 在通道上进行非阻塞通信,通过使用default语句,在没有任何channel操作就绪时执行默认操作,可以避免死锁
func main() {channel := make(chan string)// 向通道中发送数据的 Goroutinego func() {channel <- "Hello, Concurrent World!"}()//time.Sleep(time.Second)// 使用 select 在通道上进行非阻塞读写select {case <-channel: // 试图从通道中接收数据fmt.Println("Received message from channel.")default: // 当通道没有数据时执行默认操作fmt.Println("No message received. Performing default operation.")}// 这里假设有其他操作,让程序继续运行time.Sleep(3 * time.Second)fmt.Println("Program continues to run.")
}

3.For-select的使用(多次,直到收到done信号或quit信号才return)

当在 Go 语言中结合使用 for 和 select 时,通常是为了持续监听多个通道并执行相应的操作。这种结合使用可以在一个循环中处理多个通道上的非阻塞操作,实现更复杂的并发逻辑。

func forSelectUsage() {done := make(chan struct{}, 0) //一个通道 (done) 用于接收外部的终止信号,以优雅地退出循环dataChan := make(chan int)  //一个通道 (dataChan) 用于接收常规数据go func() {for i := 0; i < 20; i++ {dataChan <- iif i == 18 { // 结束条件done <- struct{}{}break}}}()ticker := time.NewTicker(time.Nanosecond)defer ticker.Stop()for {select {case <-ticker.C:  //一个通道 (ticker.C) 用于处理周期性的事件(如心跳、定时任务等)fmt.Printf("tick ")case data := <-dataChan:fmt.Printf("%d ", data)case <-done:fmt.Println("结束循环")return}}
}
// Output
// 0 1 2 tick tick tick tick tick tick tick tick tick tick 3 tick 4 tick tick tick tick tick tick 5 tick 6 tick tick 7 8 9 tick 10 11 tick 12 13 tick tick 14 15 16 17 18 结束循环

4.Context

Context是go1.7后新增的标准库的接口,该接口定义了四个方法,分别如下

  1. Deadline()返回context被取消的截止时间
  2. Done()返回一个只读的通道,多次调用Done()会返回相同的通道,这个channel会在工作完成或者取消上下文时被关闭
  3. Err()返回context.Context结束的原因,只有Done()方法返回的channel关闭时返回非空的值,如果context.Context 被取消,会返回 1. Canceled 错误;如果context.Context超时,会返回 DeadlineExceeded 错误
  4. Value — 从 context.Context中获取键对应的值,对于同一个上下文来说,多次调用 Value 并传入相同的 Key 会返回相同的结果,该方法可以用来传递请求特定的数据;
type Context interface{Deadline() (deadline time.Time, ok bool)Done() <-chan struct{}Err() errorValue(key any) any
}

5.Context的用途

Go 语言中的 context 包提供了一种在进程中跨 API 和包传递截止时间、取消信号和其他请求范围的值的机制。 context.Context 类型是 Go 语言中用于控制请求的取消操作、截止时间、以及其他跨API和包的请求范围的值的核心接口。以下是 context 的主要用途:

  1. 取消操作(Cancellation): context.Context 提供了一个取消信号,可以用于通知启动的goroutine停止工作。这在处理长时运行的或阻塞型操作时非常有用,比如网络请求或数据库操作。
  2. 截止时间(Deadlines): context.Context 可以设置截止时间,当截止时间到达时,可以触发取消操作。这有助于避免长时间挂起的请求,提高系统的响应性。
  3. 传递请求范围的值(Values): context.Context 可以存储请求相关的值,这些值可以跨API和包传递,而不需要在每个函数调用中显式传递。这使得代码更加清晰,减少了参数列表。
  4. 控制并发(Concurrency control): 当你有多个goroutine在并发执行时, context.Context 可以帮助你优雅地管理这些goroutine的生命周期。
  5. 超时控制(Timeouts): context.Context 可以用于设置操作的超时时间,这比使用单独的计时器和取消操作更加方便。
  6. 父子关系(Parent-child relationships): context.Context 可以创建父子关系,当父上下文被取消时,所有从该父上下文派生的子上下文也会被取消。
  7. 错误处理(Error handling): 在某些情况下, context.Context 可以用来传递错误信息,比如 context.Canceled 和 context.DeadlineExceeded 错误,这些错误可以用来指示操作被取消或超时。
  8. 资源管理(Resource management): context.Context 可以用来管理资源,比如数据库连接或文件句柄,确保在请求结束时释放资源。
  9. 请求隔离(Request isolation): 在Web服务器或API服务中,每个请求可以有自己的 context.Context ,这样可以隔离不同请求的状态,防止请求之间的干扰。
  10. 日志记录(Logging): context.Context 可以用来传递日志记录相关的信息,比如请求ID,这样可以在日志中保持请求的上下文信息。

6.Go程序启动时发生了什么?

  1. 初始化runtime,包括内存分配器,垃圾回收器,栈,goroutine调度器
  2. 初始化所有全局变量,基础数据类型初始化为对应的0值,指针,channel这种数据类型初始化为nil
  3. 注册信号处理器来处理SIGINT(ctrl+c)这种中断
  4. 初始化标准库中的一些包,如runtime和syscall
  5. 调用所有引入包的init函数
  6. 从main函数入口进入,开始执行程序
  7. 如果有goroutine,执行goroutine
  8. 如果没有,按照代码逻辑往下执行
  9. main函数结束,开始退出过程,包括关闭文件描述符,网络连接等等
  10. 调用exit函数,程序正常退出,os会调用exit函数,该函数会终止程序并返回状态码给os
  11. 垃圾回收和内存清理,正常退出之前,go的垃圾回收器会回收所有未引用的内存

7.数据竞争go race

概念:数据竞争,即多个goroutine同时对同一对象进行操作,并且至少包括一个写操作,这会对程序造成不可预测的影响
监测:编译时用go build -race -o 运行时用go run -race main.go
使用-race,可以有效地监测相关goroutine的堆栈跟踪,便于开发者进行定位和解决问题
解决(三种方法)

  1. 使用sync.WaitGroup:通过 sync.WaitGroup 等待所有 goroutine 完成,确保对共享变量的写操作完成后再进行读操作
func main() {var wg sync.WaitGroupvar i intwg.Add(1)go func() {i = 5wg.Done()}()wg.Wait()
}
  1. 使用 Mutex 锁:使用 sync.Mutex 来保护共享变量,确保同一时间只有一个 goroutine 可以访问共享资源
func main() {var mutex sync.Mutexvar i intgo func() {mutex.Lock()i = 5mutex.Unlock()}()
}
  1. 使用通道传递数据:避免在 goroutine 之间共享内存,而是通过通道传递数据,这样可以避免直接的内存访问冲突
func main() {ch := make(chan int)go func() {ch <- 5}()i := <-ch
}

7.Go语言实现优雅退出

在计算机术语中,优雅关机(Graceful Shutdown)通常指的是在关闭系统或程序时,确保所有的操作都被正确地完成,资源得到释放,数据保持一致性,不会导致数据丢失或损坏。对于操作系统来说,优雅关机意味着结束所有运行的程序,关闭所有服务,然后安全地关闭硬件。

下面是bluebell实现优雅关机的源码

srv := &http.Server{Addr:    fmt.Sprintf(":%d", viper.GetInt("app.port")),Handler: r,
}go func() {if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {zap.L().Fatal("listen:%s\n", zap.Error(err))}
}()//创建一个接受系统信号的通道
quit := make(chan os.Signal, 1)
// kill 默认会发送 syscall.SIGTERM 信号
// kill -2 发送 syscall.SIGINT 信号,我们常用的Ctrl+C就是触发系统SIGINT信号
// kill -9 发送 syscall.SIGKILL 信号,但是不能被捕获,所以不需要添加它
// signal.Notify把收到的 syscall.SIGINT或syscall.SIGTERM 信号转发给quit
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit //当收到上述两种信号才会往下执行
zap.L().Info("Shutdown Server ...")
//创建一个5秒超时的context
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
//5秒内优雅关闭服务(将未处理完的请求处理完在关闭服务),超过五秒就超时退出
if err := srv.Shutdown(ctx); err != nil {zap.L().Fatal("Server Shutdown:", zap.Error(err))
}
zap.L().Info("Server exiting ...")

8.uintptr和unsafe.Pointer的区别

  1. unsafe.Pointer只是单纯的通用指针类型,用于转换不同类型指针,它不可以参与指针运算;
  2. 而uintptr是用于指针运算的,GC 不把 uintptr 当指针,也就是说 uintptr 无法持有对象, uintptr 类型的目标会被回收;
  3. unsafe.Pointer 可以和 普通指针 进行相互转换;
  4. unsafe.Pointer 可以和 uintptr 进行相互转换。

9.Go语言的限流

限流器是后台服务中的非常重要的组件,可以用来限制请求速率,保护服务,以免服务过载。Golang 标准库中自带了限流算法的实现,即 golang.org/x/time/rate。该限流器基于令牌桶算法(Token Bucket Algorithm)。这个算法的核心思想是有一个令牌桶,以固定的速率填充令牌,每个请求必须消耗一个令牌才能被处理。如果桶中没有令牌,请求就会等待直到桶中有令牌可用,或者直接被拒绝,取决于具体的处理逻辑。下面是go限流器的代码示例。

package mainimport ("context""fmt""golang.org/x/time/rate""time"
)func main() {// 创建一个新的限流器,每秒2个请求,最多10个请求的突发limiter := rate.NewLimiter(2, 10)// 模拟100个请求for i := 0; i < 100; i++ {// Wait阻塞直到获取一个令牌或者上下文超时if err := limiter.Wait(context.Background()); err != nil {fmt.Println("请求被拒绝或超时")continue}// 模拟请求处理fmt.Printf("处理请求 %d", i+1)// 假设每个请求处理需要一些时间time.Sleep(100 * time.Millisecond)}
}

在这个例子中,rate.NewLimiter 函数创建了一个新的限流器,第一个参数是每秒可以处理的请求数(令牌填充速率),第二个参数是桶的大小,也就是可以突发的最大请求数。limiter.Wait(context.Background()) 会阻塞当前goroutine直到获取一个令牌或者上下文超时。如果成功获取令牌,代码会输出正在处理的请求编号,然后模拟请求处理时间。

相关文章:

  • C++17新特性 类型推导
  • 【C语言编译与链接】--翻译环境和运行环境,预处理,编译,汇编,链接
  • @Pushgateway配置与使用
  • 工商业储能站能量管理系统
  • 2014药柜设计问题
  • MOT challenge使用方法及数据集说明
  • 我的3种AI写作节奏搭配模型,适合不同类型写作者
  • 【js逆向】某某省过验证码逆向
  • 从印巴空战看数据制胜密码:元数据如何赋能数字战场
  • N2语法 状態
  • for(auto a:b)和for(auto a:b)的区别
  • leetcode动态规划—完全背包系列
  • 一篇文章玩转CAP原理
  • 鸿蒙OSUniApp滑动锁屏实战:打造流畅优雅的移动端解锁体验#三方框架 #Uniapp
  • 无线通信模块简介
  • Vue2 与 Vue3对比
  • Python字典键的使用与应用:从基础到高级实践
  • AWS WAF设置IP白名单
  • Python Day38
  • QT-Creator安装教程(windows)
  • 网站建设发展趋势/怎么做表格
  • 自己做网站 有名/正规软件开发培训学校
  • 做网站需要用到哪些编程知识/淘宝运营培训机构
  • 网站建设招标网/网站运营方案
  • 网上买名表最靠谱的网站/公关
  • 网站seo公司/seo培训优化课程