面试题答案
一键面试- 常见问题:
- 死锁问题:
- 举例:
- 死锁问题:
package main
import "fmt"
func main() {
ch := make(chan int)
ch <- 10
fmt.Println(<-ch)
}
在上述代码中,主函数尝试向无缓冲通道 ch
发送数据,但没有其他 goroutine 从该通道接收数据,这就导致了死锁。因为无缓冲通道的发送操作会阻塞,直到有接收者准备好接收数据。
- 数据竞争问题:
- 举例:
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
var count int
func increment(ch chan int) {
defer wg.Done()
for i := 0; i < 1000; i++ {
ch <- 1
count += <-ch
}
}
func main() {
ch := make(chan int)
for i := 0; i < 10; i++ {
wg.Add(1)
go increment(ch)
}
wg.Wait()
close(ch)
fmt.Println("Final count:", count)
}
在这个例子中,多个 goroutine 并发地通过通道 ch
对 count
进行读写操作。虽然通过通道传递了增量值,但由于 count
是共享变量,多个 goroutine 同时读写 count
可能会导致数据竞争,使得最终的 count
值不准确。
- 处理方法:
- 使用带缓冲通道避免死锁: 在上述死锁的例子中,可以修改为带缓冲通道:
package main
import "fmt"
func main() {
ch := make(chan int, 1)
ch <- 10
fmt.Println(<-ch)
}
这里创建了一个带缓冲为 1 的通道,发送操作不会立即阻塞,因为通道有足够的缓冲区来容纳数据,从而避免了死锁。
- 使用互斥锁解决数据竞争:
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
var count int
var mu sync.Mutex
func increment(ch chan int) {
defer wg.Done()
for i := 0; i < 1000; i++ {
ch <- 1
mu.Lock()
count += <-ch
mu.Unlock()
}
}
func main() {
ch := make(chan int)
for i := 0; i < 10; i++ {
wg.Add(1)
go increment(ch)
}
wg.Wait()
close(ch)
fmt.Println("Final count:", count)
}
通过引入 sync.Mutex
,在对共享变量 count
进行读写操作前加锁,操作完成后解锁,确保同一时间只有一个 goroutine 能够访问 count
,从而避免数据竞争。
- 使用 select 语句处理多通道场景:
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
time.Sleep(2 * time.Second)
ch1 <- 10
}()
go func() {
time.Sleep(1 * time.Second)
ch2 <- 20
}()
select {
case val := <-ch1:
fmt.Println("Received from ch1:", val)
case val := <-ch2:
fmt.Println("Received from ch2:", val)
case <-time.After(3 * time.Second):
fmt.Println("Timeout")
}
}
select
语句可以在多个通道操作(接收或发送)之间进行选择。在这个例子中,它等待从 ch1
或 ch2
接收数据,或者等待超时。如果 ch2
先接收到数据,就会打印相应信息;如果 ch1
先接收到数据,也会打印相应信息;如果在 3 秒内都没有接收到数据,就会打印超时信息。这种方式可以有效地处理多个通道的并发操作,避免死锁和其他并发问题。