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

百度后端开发一面

在这里插入图片描述

mutex, rwmutex

在Go语言中,Mutex(互斥锁)和RWMutex(读写锁)是用于管理并发访问共享资源的核心工具。以下是它们的常见问题、使用场景及最佳实践总结:


1. Mutex 与 RWMutex 的区别

  • Mutex:
    • 互斥锁,保证同一时刻只有一个 goroutine 访问共享资源。
    • 适用于读写操作都需要独占的场景(如计数器)。
  • RWMutex:
    • 读写锁,允许多个读操作并行,但写操作完全独占。
    • 适用于读多写少的场景(如配置信息读取)。

2. 常见问题及解决方案

2.1 死锁
  • 原因: 未释放锁、重复加锁或锁竞争导致永久阻塞。
  • 解决:
    • 使用 defer mu.Unlock() 确保释放锁。
    • 避免在同一个 goroutine 中重复加锁(不可重入)。
2.2 数据竞争
  • 原因: 未对共享资源的所有访问路径加锁。
  • 解决:
    • 明确锁的保护范围,确保所有访问共享数据的操作都被锁覆盖。
    • 使用 go test -race 检测数据竞争。
2.3 锁拷贝
  • 原因: 复制包含锁的结构体导致锁状态异常。
  • 解决:
    • 通过指针传递包含锁的结构体。
    • 避免直接复制 sync.Mutexsync.RWMutex
2.4 写饥饿(RWMutex)
  • 原因: 大量读操作阻塞写操作。
  • 解决:
    • 评估场景是否需要 RWMutex,或通过优先级队列优化写操作。
    • Go 1.18+ 的 Mutex 支持饥饿模式,避免长时间等待。

3. 使用场景

  • Mutex:
    var counter int
    var mu sync.Mutexfunc increment() {mu.Lock()defer mu.Unlock()counter++
    }
    
  • RWMutex:
    var config map[string]string
    var rwmu sync.RWMutexfunc readConfig(key string) string {rwmu.RLock()defer rwmu.RUnlock()return config[key]
    }func updateConfig(key, value string) {rwmu.Lock()defer rwmu.Unlock()config[key] = value
    }
    

4. 最佳实践

  1. 锁的作用域:
    • 锁应保护数据而非代码,确保所有访问共享资源的路径都被覆盖。
  2. 优先使用 defer:
    • 避免忘记解锁,尤其在复杂逻辑或异常处理中。
  3. 替代方案:
    • 对简单数值操作(如计数器)使用 atomic 包。
    • 通过 Channel 实现“通过通信共享内存”。
  4. 性能优化:
    • 减少锁的持有时间(如仅在读写共享数据时加锁)。
    • 在高并发场景中,评估锁竞争是否成为瓶颈。

5. 注意事项

  • 不可重入: Go 的锁不支持重入,同一 goroutine 重复加锁会导致死锁。
  • 零值可用: sync.Mutexsync.RWMutex 的零值可直接使用,无需初始化。
  • 避免嵌套锁: 多个锁的嵌套使用可能导致复杂死锁,需按固定顺序加锁。

通过合理选择 MutexRWMutex,并遵循最佳实践,可以有效避免并发问题,编写高效且安全的 Go 代码。

协程线程区别

协程(Coroutine)和线程(Thread)都是用于实现并发执行的机制,但它们在调度方式、资源消耗、通信机制等方面有显著区别。线程是操作系统级别的并发单位,由内核调度;而协程是用户态的轻量级线程,由程序自身调度。

  • 解答思路
  1. 首先明确两者的基本定义和使用场景。
  2. 对比它们的调度机制:线程由操作系统调度器管理,而协程由用户程序或运行时系统管理。
  3. 比较它们的开销:线程切换代价高,需要操作系统参与;协程切换快,仅需保存寄存器状态。
  4. 分析资源占用:线程拥有独立的栈空间,内存占用较大;协程共享线程资源,更节省内存。
  5. 总结适用场景:CPU密集型适合多线程,IO密集型或多任务协作适合协程。
  • 深度知识讲解

一、基本概念

  • 线程(Thread)
    线程是进程内的一个执行单元,多个线程共享同一进程的地址空间和资源。每个线程有自己独立的栈和寄存器上下文。线程由操作系统负责创建、销毁和调度。

  • 协程(Coroutine)
    协程是一种用户态的非抢占式多任务机制,可以看作是“轻量级线程”。它不像线程那样被操作系统调度,而是由程序员显式控制其切换。协程之间通常是协作式的,即当前协程主动让出控制权给下一个协程。

二、核心区别

  1. 调度机制不同
  • 线程是抢占式的,操作系统根据优先级、时间片等策略决定哪个线程运行。
  • 协程是协作式的,必须由当前协程主动 yield 控制权,才能切换到下一个协程。
  1. 上下文切换开销
  • 线程切换涉及内核态与用户态的切换,需要保存/恢复更多的寄存器和状态信息,开销大。
  • 协程切换完全在用户态进行,只需保存少量寄存器,开销极小。
  1. 资源占用
  • 线程通常默认分配较大的栈空间(如1MB),因此不能大量创建。
  • 协程栈空间较小(可配置为几KB),支持同时运行成千上万个协程。
  1. 同步与通信
  • 线程间通信需要互斥锁、信号量等机制,容易引发竞态条件。
  • 协程可以通过通道(channel)、事件循环等方式进行安全高效的通信。
  1. 并发模型
  • 多线程属于并行模型,适用于 CPU 密集型任务。
  • 协程属于异步/协作式并发模型,适用于 IO 密集型任务(如网络请求、文件读写)。

GMP调度

Go语言的GPM调度模型是Go运行时中用于处理并发的核心机制之一,它将Goroutine(轻量级线程)有效地映射到系统线程上,以最大化并发性能。GPM模型主要由三个部分组成:G(Goroutine)、P(Processor)、M(Machine)。让我们逐一详细介绍:

1. G(Goroutine)

  • Goroutine 是Go语言中用于并发执行的轻量级线程,每个Goroutine都有自己的栈和上下文信息。
  • Goroutine相对于操作系统的线程更加轻量级,可以在同一时间内运行成千上万的Goroutine。

2. P(Processor)

  • P 是处理Goroutine的调度器的上下文,每个P包含一个本地运行队列(Local Run Queue),用于存储需要运行的Goroutine。
  • P的数量由GOMAXPROCS设置决定,它决定了并行执行的最大线程数。
  • P不仅管理Goroutine,还负责与M协作,将Goroutine分配给M执行。

3. M(Machine)

  • M 代表操作系统的线程,负责执行Goroutine。一个M一次只能执行一个Goroutine。
  • M是实际执行代码的工作单元,M与P绑定后才能执行Goroutine。
  • M可以通过调度器从全局运行队列中拉取新的Goroutine,也可以与其他M协作完成工作。

4. GPM模型的调度过程

  • 调度器工作机制:Goroutine创建后会被放入P的本地队列,P会从该队列中选择Goroutine,并将其分配给M执行。如果本地队列为空,P可以从全局运行队列或其他P的队列中窃取任务。
  • 工作窃取机制:如果一个P的本地队列为空,而另一个P的本地队列中有多个Goroutine,前者可以从后者中窃取任务,从而保持系统的高效利用率。
  • 阻塞与调度:当M执行的Goroutine阻塞(例如I/O操作)时,M会释放当前的P并等待P重新分配任务,从而避免资源浪费。

5. 模型优点

  • 高效的并发调度:GPM模型使得Go语言可以高效地管理数百万个Goroutine的并发执行。
  • 可伸缩性:通过P与M的动态调度,GPM模型可以充分利用多核处理器的性能。
  • 轻量级:Goroutine非常轻量,创建和切换的成本比系统线程要低得多。

redis 常见数据类型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

压缩列表

连续内存块组成的顺序型数据结构
在这里插入图片描述
在这里插入图片描述
O(1)定位首尾元素,其他需要遍历,不适合存储太多数据。
在这里插入图片描述
在这里插入图片描述

整数集合

在这里插入图片描述

跳表

跳表的优势是能支持平均O(logN)复杂度的节点查找
zset存储member和score
在这里插入图片描述
在这里插入图片描述

quicklist代替双向链表

在这里插入图片描述

在这里插入图片描述

listpack代替压缩列表

在这里插入图片描述
在这里插入图片描述

redis跳表的增删改查复杂度

O(logN)
在这里插入图片描述

redis跳表数据结构,高度创建,怎么删改

在这里插入图片描述
跳跃表(Skip List)的删除和修改操作需要结合其多层链表结构的特点进行调整,以下是具体实现步骤和原理:


一、删除操作

删除节点的核心步骤是:找到目标节点在所有层的引用,并更新这些层的指针以跳过该节点

1. 删除流程
  1. 查找目标节点

    • 从最高层开始,向右遍历,直到找到等于或大于目标值的节点。
    • 如果当前层的下一个节点等于目标值,记录该层的前驱节点(即指向目标节点的节点)。
    • 逐层向下重复此过程,直到最底层(Level 0)。
    • 时间复杂度:O(log n),与查找操作相同。
  2. 调整指针

    • 对每一层(从最高层到最底层):
      • 如果该层存在指向目标节点的前驱节点,将其指针指向目标节点的下一个节点。
      • 例如,若前驱节点在 Level 2 指向目标节点,则将前驱节点的 Level 2 指针指向目标节点的 Level 2 后继节点。
    • 操作示例
      原结构(删除节点 30):
      Level 2: 10 --------------------------> 50
      Level 1: 10 -------> 30 -------> 50
      Level 0: 10 -> 20 -> 30 -> 40 -> 50删除后:
      Level 2: 10 --------------------------> 50
      Level 1: 10 -------> 50
      Level 0: 10 -> 20 -> 40 -> 50
      
  3. 释放内存

    • 删除节点后,释放该节点的内存(在 Redis 等语言中可能由 GC 自动处理)。
2. 关键注意事项
  • 多线程安全:如果跳跃表支持并发操作,删除时需加锁(如 Redis 单线程模型无需考虑)。
  • 更新最大层高:若删除的节点是最高层的唯一节点,需降低跳跃表的最大层高。

二、修改操作

修改操作分为两种情况:仅修改值(Value)修改键(Score)

1. 仅修改值(Value)
  • 流程
    1. 查找目标节点:时间复杂度 O(log n)。
    2. 直接更新值:无需调整指针,直接修改节点的值字段。
  • 时间复杂度:O(log n)(仅查找时间)。
2. 修改键(Score)

由于跳跃表是按键(Score)有序排列的,修改键后需保证顺序性,因此需要先删除旧节点,再插入新节点

  • 流程

    1. 删除旧节点:O(log n)。
    2. 插入新节点:按新键值插入,O(log n)。
  • 总时间复杂度:O(log n) + O(log n) = O(log n).

  • 示例

    原结构(修改节点 30 的 Score 为 35):
    Level 1: 10 -------> 30 -------> 50
    Level 0: 10 -> 20 -> 30 -> 40 -> 50修改后:
    Level 1: 10 --------------------------> 50
    Level 0: 10 -> 20 -> 40 -> 50
    新插入节点 35:
    Level 1: 10 ----------> 35 -------> 50
    Level 0: 10 -> 20 -> 35 -> 40 -> 50
    

redis持久化AOF怎么做

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

数组中重复的数据

https://leetcode.cn/problems/find-all-duplicates-in-an-array/description/

func findDuplicates(nums []int) []int {n := len(nums)ans := []int{}for i:=0;i<n;i++{x := nums[i]if x<0{x = -x}if nums[x-1]<0{ans = append(ans, x)}else{nums[x-1] = -nums[x-1]}}return ans
}

相关文章:

  • vue3+ts项目 配置vue-router
  • HarmonyOS NEXT第一课——HarmonyOS介绍
  • Java实现区间合并算法详解
  • 2025A卷华为OD机试真题-数组二叉树(C++/Java/Python)-100分
  • 学习springboot-条件化配置@Conditional(条件注解)
  • 数字智慧方案5872丨智慧交通解决方案(54页PPT)(文末有下载方式)
  • AI大模型-RAG到底能做些什么?
  • 镜像和容器的深度介绍和关系
  • 使用xlwings计算合并单元格的求和
  • 改进算法超详细:双变异樽海鞘群算法:从最优性能设计到分析
  • MySQL数据库上篇
  • 怎么实现动态提示词,并提升准确率
  • 【IPMV】图像处理与机器视觉:Lec9 Laplace Blending 拉普拉斯混合
  • 这款软件的第三方评测:功能、易用性与性能表现如何?
  • map和set的遗留 + AVL树(1):
  • K8S - StatefulSet 与 DaemonSet - 有状态应用部署与节点管理策略
  • [面试]SoC验证工程师面试常见问题(二)
  • PyTorch_创建张量
  • 浅谈SpringBoot框架中的单例bean
  • 【KWDB 创作者计划】利用KWDB解决工业物联网场景中的海量数据管理难题的思考
  • 多地晒五一假期前两日成绩单,湖南单日客流同比增长逾三成
  • 客场不敌蓉城遭遇联赛首败,申花争冠需要提升外援能力
  • 高速变道致连环车祸,白车“骑”隔离栏压住另一车,交警回应
  • 山西太原一小区发生爆炸,太原:进一步深刻汲取教训
  • 韩国前国务总理韩德洙正式宣布参加总统选举
  • 共绘“彩色上海”,IP SH艺术共创沙龙首期圆满举办