面试题答案
一键面试并发安全问题分析
- 竞态条件:在高并发场景下,多个协程同时对已关闭通道进行操作,可能导致数据竞争。例如,一个协程在通道关闭后尝试发送数据,而另一个协程可能正在进行接收操作,这会导致未定义行为。
- 恐慌(Panic):Go 语言中,向已关闭的通道发送数据会导致程序恐慌(panic),这可能使整个程序崩溃。
解决方案
- 使用
sync.Mutex
:通过互斥锁来保护对通道的操作,确保同一时间只有一个协程能对通道进行发送或接收操作。 - 使用
select
语句:结合select
语句和ok
检测,在接收操作时判断通道是否关闭,避免向已关闭通道发送数据。 - 使用
sync.Cond
:条件变量可以用于在通道关闭时通知所有相关协程,以便它们采取相应措施,如停止对通道的操作。
代码示例
- 使用
sync.Mutex
package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.Mutex
ch := make(chan int)
var wg sync.WaitGroup
// 模拟多个协程
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
mu.Lock()
select {
case ch <- id:
fmt.Printf("协程 %d 发送数据 %d\n", id, id)
default:
fmt.Printf("协程 %d 通道已满,无法发送\n", id)
}
mu.Unlock()
}(i)
}
go func() {
// 模拟一段时间后关闭通道
defer close(ch)
wg.Wait()
}()
for val := range ch {
fmt.Printf("接收数据 %d\n", val)
}
}
- 使用
select
语句
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
var wg sync.WaitGroup
// 模拟多个协程
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
select {
case ch <- id:
fmt.Printf("协程 %d 发送数据 %d\n", id, id)
default:
fmt.Printf("协程 %d 通道已满,无法发送\n", id)
}
}(i)
}
go func() {
// 模拟一段时间后关闭通道
defer close(ch)
wg.Wait()
}()
for {
val, ok := <-ch
if!ok {
break
}
fmt.Printf("接收数据 %d\n", val)
}
}
- 使用
sync.Cond
package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.Mutex
cond := sync.NewCond(&mu)
ch := make(chan int)
var wg sync.WaitGroup
// 模拟多个协程
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
mu.Lock()
for {
select {
case ch <- id:
fmt.Printf("协程 %d 发送数据 %d\n", id, id)
default:
fmt.Printf("协程 %d 通道已满,无法发送\n", id)
}
cond.Wait()
if closed(ch) {
break
}
}
mu.Unlock()
}(i)
}
go func() {
// 模拟一段时间后关闭通道
defer close(ch)
wg.Wait()
mu.Lock()
cond.Broadcast()
mu.Unlock()
}()
for val := range ch {
fmt.Printf("接收数据 %d\n", val)
}
}
func closed(c <-chan struct{}) bool {
select {
case <-c:
return true
default:
}
return false
}
通过上述方法,可以有效避免在高并发场景下对已关闭通道操作时出现的并发安全问题。