✍个人博客: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.Mutexrecursion int32 // 这个goroutine 重入的次数owner int64 // 当前持有锁的goroutine id
}
// Get returns the id of the current goroutine.
func GetGoroutineID() int64 {var buf [64]bytevar 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.Mutexowner int64 // 当前持有锁的goroutine idrecursion 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,调用次数加1atomic.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))}// 调用次数减1m.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.Mutex
或 sync.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.Mutexcond := sync.NewCond(&mutex)ready := falsefor 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 = truecond.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()
方法则可以提高程序的效率,但是需要确保程序的正确性。在实际应用中,应该根据具体情况选择合适的方法。