golang后端面试宝典

golang以及中间件相关

1、sync.Map与普通map区别:

  1. 并发安全性

    • sync.Map是为并发访问设计的,它内部使用读写锁(RWLock)或其他机制来保证并发操作的安全性。
    • 普通map在并发环境中使用时,需要外部同步,如使用互斥锁(Mutex)来避免并发读写导致的数据竞争。
  2. 读写时的锁策略

    • sync.Map在读操作时不需要加锁,它允许多个读操作并发进行,只有在写操作时(如添加、删除或更新键值对)才会加锁。
    • 普通map在进行读写操作时,通常需要全局加锁,这可能会降低并发性能。
  3. 性能

    • sync.Map在读多写少的场景下性能较好,因为读操作不需要加锁。
    • 普通map在并发读写时,由于需要外部同步,可能会因为锁竞争而导致性能下降。
  4. 迭代器

    • sync.Map不保证迭代顺序,且迭代过程中对sync.Map的并发修改可能会导致迭代器失效。
    • 普通map也不保证迭代顺序,但在迭代过程中,如果对map进行了并发的修改,可能会导致运行时 panic。
  5. 内置函数

    • sync.Map提供了LoadStoreDeleteRange等方法,这些方法都是并发安全的。
    • 普通map的操作方法不是线程安全的,需要额外的同步措施。
  6. 内存使用

    • sync.Map可能会比普通map使用更多的内存,因为它需要存储额外的锁信息和可能的多个副本(为了减少锁争用)。
  7. 使用场景

    • sync.Map适合在高并发的应用程序中使用,尤其是在需要频繁进行并发读写操作的场景。
    • 普通map适合在并发需求不高或可以通过其他方式保证同步的场景。

2、数组跟切片的区别:

数组(Array):

  1. 固定长度:数组的长度是固定的,在声明时必须指定数组的长度,例如 arr [5]int 表示一个包含5个整数的数组。

  2. 类型不变:数组的类型在声明时确定,并且在整个数组中是一致的。

  3. 值语义:数组作为值传递时,会复制整个数组数据,这可能导致性能问题,尤其是在处理大型数组时。

  4. 无内置方法:数组本身没有内置的方法,如追加、删除等操作。

  5. 栈上分配:数组通常在栈上分配,这限制了它们的大小时和生命周期。

  6. 声明var arr [5]intarr := [5]int{1, 2, 3, 4, 5}

切片(Slice):

  1. 动态长度:切片的初始长度可以是零,可以在运行时增长和缩减。

  2. 可变:切片可以被重新切片(slice),以创建一个包含原始切片一部分的新切片。

  3. 引用语义:切片是引用类型,它们指向底层数组,对切片的修改会影响底层数组。

  4. 内置方法:切片提供了多种内置方法,如 append(), delete(), copy(), len()cap()

  5. 堆上分配:切片背后的数组通常在堆上分配,这允许更灵活的内存管理。

  6. 声明var s []ints := []int{1, 2, 3, 4, 5}

一些关键点:

  • 性能:对于小型数据集,使用数组可能更高效,因为它们在栈上分配,访问速度更快。对于大型数据集,切片可能更合适,因为它们可以动态地增长。

  • 灵活性:切片比数组更灵活,因为它们可以动态地调整大小,并且可以很容易地通过内置方法进行操作。

  • 传递函数:当需要传递一个可变长度的序列给函数时,使用切片更合适,因为它们可以通过引用传递,减少了复制的开销。

  • 数组转换为切片:可以通过 arr[:n] 将数组的前n个元素转换为切片,其中n是数组的长度。

  • 内存管理:虽然切片在内存管理上更灵活,但这也意味着开发者需要更小心地管理切片的生命周期,以避免内存泄漏。

3、go语言中,如何去管理控制协程并发数量? 

1. 同步等待组(sync.WaitGroup)

sync.WaitGroup 是Go语言提供的一个同步原语,可以用来等待一组并发操作完成。通过Add方法增加计数,然后在每个goroutine中调用Done方法减少计数,主goroutine通过调用Wait方法阻塞等待直到计数为零。

var wg sync.WaitGroup

// 启动一定数量的goroutine
for i := 0; i < maxGoroutines; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        // 执行任务
    }()
}

wg.Wait() // 等待所有goroutine完成

2. 通道(Channels)和缓冲区

使用通道(Channels)可以控制协程的启动数量。通过设置通道的缓冲区大小,可以限制同时运行的goroutine数量。

ch := make(chan struct{}, maxGoroutines)

for i := 0; i < totalTasks; i++ {
    go func() {
        ch <- struct{}{} // 进入通道,如果缓冲区满了,这里会阻塞
        defer func() { <-ch }() // 完成后从通道中移除
        // 执行任务
    }()
}

3. 带缓冲区的通道作为信号量

同样,可以使用带缓冲区的通道来实现一个简单的信号量,控制并发执行的goroutine数量。

sem := make(chan struct{}, maxGoroutines)

for i := 0; i < totalTasks; i++ {
    go func(task int) {
        <-sem // 等待获取信号量
        defer func() { sem <- struct{}{} }() // 完成任务后释放信号量
        // 执行任务
    }(i)
}

4. 并发控制库

Go标准库中的sync包提供了一些并发控制的工具,例如sync.Mutex(互斥锁)和sync.RWMutex(读写锁),可以用来控制对共享资源的访问。还有一些第三方库,如concurrencylimiter,提供了更高级的并发控制功能。

  1. 互斥锁(sync.Mutex): 互斥锁用于保护对共享资源的访问,确保同时只有一个 goroutine 可以执行临界区的代码。它通过 Lock()Unlock() 方法来控制对共享资源的访问。

    var mu sync.Mutex
    var shared int

    func main() {
        for i := 0; i < 10; i++ {
            go func(i int) {
                mu.Lock()
                shared++
                mu.Unlock()
            }(i)
        }
    }

  2. 读写锁(sync.RWMutex): 读写锁是互斥锁的一个变种,它允许多个读操作同时进行,但在写操作时,会阻止其他所有读和写操作。这在读取操作远多于写入操作的情况下很有用。

    var rwmu sync.RWMutex
    var data map[string]int
    
    func read(key string) int {
        rwmu.RLock()
        defer rwmu.RUnlock()
        return data[key]
    }
    
    func write(key string, value int) {
        rwmu.Lock()
        defer rwmu.Unlock()
        data[key] = value
    }

4、go的垃圾回收机制的底层实现原理,以及为什么go的垃圾回收机制对并发友好

Go语言的垃圾回收(Garbage Collection,GC)机制是一种自动内存管理策略,其底层实现原理主要包括以下几个方面:

  1. 标记-清除(Mark-Sweep)算法: 初始阶段,Go使用传统的标记-清除算法。这个算法分为两个主要阶段:标记阶段和清除阶段。在标记阶段,GC从根对象开始遍历所有可达对象,将它们标记为活跃的。在清除阶段,未被标记的对象被认为是垃圾,随后被回收。

  2. 分代回收: 为了解决标记-清除算法存在的问题,Go采用了分代回收策略。它将内存分为多个代,新创建的对象会被分配到新生代,而存活时间较长的对象会晋升到老生代。GC会优先处理新生代对象,这样可以减少GC的次数和时间。

  3. 并发垃圾回收: Go从1.5版本开始引入了并发垃圾回收机制,将垃圾回收任务分为两个阶段:扫描和清理。在扫描阶段,GC标记所有被引用的对象;在清理阶段,并发执行清理任务,回收未被引用的对象。这种并发执行减少了程序暂停的时间。

  4. 三色标记法: 为了避免长时间STW(Stop-The-World)问题,Go使用三色标记法,将对象分为白色(潜在垃圾)、黑色(确定存活)和灰色(待确认,有指向白色对象的指针)。通过这种分类,GC可以并发地进行对象的标记和回收,而不会影响程序的执行。

  5. 写屏障(Write Barrier): 为了维护三色不变性,在并发垃圾回收中,Go使用写屏障技术。写屏障是一种在执行写操作时插入的额外代码,它确保了在并发环境下,对象图的变动不会干扰垃圾回收的标记过程。Go 1.8版本引入了混合写屏障,结合了插入写屏障和删除写屏障的优点,以保证在并发环境下垃圾回收的正确性。

  6. 优化策略: Go的GC还包括了一系列优化措施,如快速垃圾回收,它通过优化标记-清除算法来减少GC的暂停时间和降低对程序执行的影响。

Go的垃圾回收机制对并发友好的原因主要包括:

  • 低停顿时间:通过并发执行GC任务,减少了程序因GC而产生的停顿。
  • 写屏障的应用:保证了在并发环境下对象图的一致性,避免了STW问题。
  • 三色标记法:允许GC与程序的其他部分同时运行,而不会相互干扰。
  • 内存分配策略:Go的内存分配策略(如使用sync.Pool)可以减少因内存分配导致的GC压力。

总的来说,Go的垃圾回收机制通过一系列优化措施和并发算法,实现了高效率的内存管理和对并发执行的友好支持。

5、再以操作系统的层面上说一下,go与java的并发的区别,以及go的高并发为啥比java快

在操作系统层面上,Go(Golang)和Java在并发模型上有几个关键的区别,这些区别影响了它们在高并发场景下的性能表现:

  1. 并发模型

    • Go 使用的是**协程(goroutine)**模型,它是一种轻量级的线程,由Go运行时(runtime)管理。Go的goroutine比传统操作系统线程更轻量,创建和销毁的开销小得多。
    • Java 使用的是线程模型,每个线程由操作系统管理,创建和销毁线程的开销相对较大。
  2. 调度器

    • Go 的调度器是Go运行时的一部分,它负责goroutine的调度。Go的调度器设计得非常高效,能够在用户态进行goroutine的创建、销毁和调度,减少了操作系统内核态切换的开销。
    • Java 的线程是由操作系统的调度器管理的,涉及到更多的上下文切换和内核态切换,这些操作相对更重。
  3. 并发控制

    • Go 提供了丰富的并发原语,如channel、sync.Mutex、sync.WaitGroup等,它们都是语言层面支持的,可以方便地在goroutine之间进行通信和同步。
    • Java 虽然也提供了并发控制的类和接口,如java.util.concurrent包中的类,但它们的使用相对更复杂一些,且需要开发者有较强的并发编程经验。
  4. 内存管理

    • Go 的垃圾回收机制对并发友好,它使用并发标记和并发清除,减少了垃圾回收对程序执行的影响。
    • Java 的垃圾回收通常会引起应用程序的暂停,尤其是在使用某些垃圾回收器时,如Serial GC或Parallel GC。
  5. 系统调用和上下文切换

    • Go 的goroutine上下文切换是在用户态进行的,不需要操作系统介入,因此上下文切换的开销小。
    • Java 的线程上下文切换通常涉及到操作系统内核,开销更大。
  6. 栈大小

    • Go 的goroutine默认栈大小较小(通常是2KB),但可以根据需要动态调整。
    • Java 的线程栈大小通常较大(如1MB或2MB),这也增加了线程创建和维护的开销。
  7. 生态和库

    • Go 的标准库中包含了许多针对并发设计的包和函数,如net/http用于编写高并发的网络服务器。
    • Java 虽然也有丰富的并发库,但使用起来可能更复杂,且需要更多的样板代码。

Go的高并发性能之所以比Java快,主要是因为它在设计上就考虑了并发和网络编程的需求,提供了轻量级的goroutine、高效的调度器和丰富的并发原语,这些都使得Go在高并发场景下的性能表现更优。而Java虽然也支持多线程和并发,但由于线程模型的开销、垃圾回收的暂停以及上下文切换的开销,其性能在高并发场景下可能不如Go。

6、git常见命令 

初始化和配置

  1. 初始化仓库

    git init

  2. 克隆仓库

    git clone [url]

  3. 设置用户信息

    git config --global user.name "[name]" git config --global user.email "[email address]"

文件操作

  1. 添加文件到暂存区

    git add [file]

  2. 从暂存区移除文件

    git reset [file]

  3. 删除文件

    git rm [file]

  4. 重命名文件

    git mv [old] [new]

提交和分支

  1. 提交到仓库

    git commit -m "commit message"

  2. 查看提交历史

    git log

  3. 创建新分支

    git branch [branch-name]

  4. 切换到分支

    git checkout [branch-name]

  5. 合并分支

    git merge [branch-name]

  6. 删除分支

    git branch -d [branch-name]

  7. 查看分支状态

    git status

远程仓库

  1. 添加远程仓库

    git remote add [shortname] [url]

  2. 查看远程仓库

    git remote -v

  3. 从远程仓库拉取代码

    git fetch [remote]

  4. 从远程仓库拉取特定分支

    git pull [remote] [branch]</

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EssRt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值