【Golang 面试 - 进阶题】每日 3 题(四)

avatar
作者
猴君
阅读量:0

✍个人博客:Pandaconda-CSDN博客

📣专栏地址:http://t.csdnimg.cn/UWz06

📚专栏简介:在这个专栏中,我将会分享 Golang 面试中常见的面试题给大家~
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪

10. G o 可重入锁如何实现?

概念:

可重入锁又称为递归锁,是指在同一个线程在外层方法获取锁的时候,在进入该线程的内层方法时会自动获取锁,不会因为之前已经获取过还没释放再次加锁导致死锁。

为什么 Go 语言中没有可重入锁?

Mutex 不是可重入的锁。Mutex 的实现中没有记录哪个 goroutine 拥有这把锁。理论上,任何 goroutine 都可以随意地 Unlock 这把锁,所以没办法计算重入条件,并且Mutex 重复 Lock 会导致死锁。

如何实现可重入锁?

实现一个可重入锁需要这两点:

  • 记住持有锁的线程

  • 统计重入的次数

package main import (     "bytes"     "fmt"     "runtime"     "strconv"     "sync"     "sync/atomic" ) type ReentrantLock struct {     sync.Mutex     recursion int32 // 这个goroutine 重入的次数     owner     int64 // 当前持有锁的goroutine id } // Get returns the id of the current goroutine. func GetGoroutineID() int64 {     var buf [64]byte     var s = buf[:runtime.Stack(buf[:], false)]     s = s[len("goroutine "):]     s = s[:bytes.IndexByte(s, )]     gid, _ := strconv.ParseInt(string(s), 10, 64)     return gid } func NewReentrantLock() sync.Locker {     res := &ReentrantLock{         Mutex:     sync.Mutex{},         recursion: 0,         owner:     0,     }     return res } // ReentrantMutex 包装一个Mutex,实现可重入 type ReentrantMutex struct {     sync.Mutex     owner     int64 // 当前持有锁的goroutine id     recursion int32 // 这个goroutine 重入的次数 } func (m *ReentrantMutex) Lock() {     gid := GetGoroutineID()     // 如果当前持有锁的goroutine就是这次调用的goroutine,说明是重入     if atomic.LoadInt64(&m.owner) == gid {         m.recursion++         return     }     m.Mutex.Lock()     // 获得锁的goroutine第一次调用,记录下它的goroutine id,调用次数加1     atomic.StoreInt64(&m.owner, gid)     m.recursion = 1 } func (m *ReentrantMutex) Unlock() {     gid := GetGoroutineID()     // 非持有锁的goroutine尝试释放锁,错误的使用     if atomic.LoadInt64(&m.owner) != gid {         panic(fmt.Sprintf("wrong the owner(%d): %d!", m.owner, gid))     }     // 调用次数减1     m.recursion--     if m.recursion != 0 { // 如果这个goroutine还没有完全释放,则直接返回         return     }     // 此goroutine最后一次调用,需要释放锁     atomic.StoreInt64(&m.owner, -1)     m.Mutex.Unlock() } func main() {     var mutex = &ReentrantMutex{}     mutex.Lock()     mutex.Lock()     fmt.Println(111)     mutex.Unlock()     mutex.Unlock() }

 11. Cond 是什么?

在 Go 语言中,sync.Cond 是一个条件变量的实现,它可以在多个 Goroutine 之间传递信号和数据。条件变量是一种同步机制,用于解决某些 Goroutine 需要等待某个事件或条件发生的问题。

sync.Cond 是基于 sync.Mutexsync.RWMutex 的,它提供了 Wait()Signal()Broadcast() 三个方法。

  • Wait():释放锁并阻塞当前 Goroutine,直到调用 Signal()Broadcast() 并重新获得锁。在阻塞期间,Goroutine 处于等待状态并且不会消耗 CPU 资源。

  • Signal():唤醒一个等待中的 Goroutine。

  • Broadcast():唤醒所有等待中的 Goroutine。

下面是一个简单的示例,展示了如何使用 sync.Cond 实现多个 Goroutine 之间的通信:

package main import (     "fmt"     "sync"     "time" ) func main() {     var mutex sync.Mutex     cond := sync.NewCond(&mutex)     ready := false     for i := 0; i < 5; i++ {         go func(i int) {             mutex.Lock()             defer mutex.Unlock()             for !ready {                 cond.Wait()             }             fmt.Printf("Goroutine %d is now running\n", i)         }(i)     }     time.Sleep(time.Second * 1)     mutex.Lock()     ready = true     cond.Broadcast()     mutex.Unlock()     time.Sleep(time.Second * 1) }

在这个例子中,我们创建了一个 sync.Cond 实例,将它和一个 sync.Mutex 关联起来。然后我们启动了 5 个 Goroutine,在其中每个 Goroutine 上等待一个条件变量。我们在主 Goroutine 中等待一秒钟,然后发送一个 Broadcast 信号,通知所有等待中的 Goroutine 可以开始执行了。在接收到 Broadcast 信号后,所有 Goroutine 将开始执行,并输出一些消息。

12. B r oadcast 和 Signal 区别

在 Go 语言中,sync.Cond 类型提供了 Broadcast()Signal() 两个方法来唤醒等待条件变量的 Goroutine。这两个方法的区别在于:

  • Signal() 方法只会唤醒等待条件变量的一个 Goroutine,具体哪个 Goroutine 会被唤醒是不确定的。如果多个 Goroutine 等待同一个条件变量,那么只会有一个 Goroutine 被唤醒,其他 Goroutine 仍然会继续等待条件变量。

  • Broadcast() 方法会唤醒所有等待条件变量的 Goroutine,使它们都开始运行。如果多个 Goroutine 等待同一个条件变量,那么所有 Goroutine 都会被唤醒。

一般来说,使用 Signal() 方法可以提高程序的效率,因为只需要唤醒一个 Goroutine,其他 Goroutine 仍然会等待条件变量,不会消耗 CPU 资源。但是,如果有多个 Goroutine 都需要同时等待条件变量,那么使用 Broadcast() 方法才能保证它们都能被唤醒,否则可能会出现死锁等问题。

总之,Broadcast() 方法是一种安全可靠的方法,但是可能会导致一些性能问题。而 Signal() 方法则可以提高程序的效率,但是需要确保程序的正确性。在实际应用中,应该根据具体情况选择合适的方法。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!