Go 并发编程测试题----
gopackage main
import (
"fmt"
"sync"
)
var mu sync.Mutex
var chain string
func main() {
chain = "main"
A()
fmt.Println(chain)
}
func A() {
mu.Lock()
defer mu.Unlock()
chain = chain + " --> A"
B()
}
func B() {
chain = chain + " --> B"
C()
}
func C() {
mu.Lock()
defer mu.Unlock()
chain = chain + " --> C"
}
A: 不能编译
B: 输出 main --> A --> B --> C
C: 输出 main
D: panic
gopackage main
import (
"fmt"
"sync"
"time"
)
var mu sync.RWMutex
var count int
func main() {
go A()
time.Sleep(2 * time.Second)
mu.Lock()
defer mu.Unlock()
count++
fmt.Println(count)
}
func A() {
mu.RLock()
defer mu.RUnlock()
B()
}
func B() {
time.Sleep(5 * time.Second)
C()
}
func C() {
mu.RLock()
defer mu.RUnlock()
}
A: 不能编译
B: 输出 1
C: 程序hang住
D: panic
gopackage main
import (
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
time.Sleep(time.Millisecond)
wg.Done()
wg.Add(1)
}()
wg.Wait()
}
A: 不能编译
B: 无输出,正常退出
C: 程序hang住
D: panic
gopackage doublecheck
import (
"sync"
)
type Once struct {
m sync.Mutex
done uint32
}
func (o *Once) Do(f func()) {
if o.done == 1 {
return
}
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
o.done = 1
f()
}
}
A: 不能编译
B: 可以编译,正确实现了单例
C: 可以编译,有并发问题,f函数可能会被执行多次
D: 可以编译,但是程序运行会panic
gopackage main
import (
"fmt"
"sync"
)
type MyMutex struct {
count int
sync.Mutex
}
func main() {
var mu MyMutex
mu.Lock()
var mu2 = mu
mu.count++
mu.Unlock()
mu2.Lock()
mu2.count++
mu2.Unlock()
fmt.Println(mu.count, mu2.count)
}
A: 不能编译
B: 输出 1, 1
C: 输出 1, 2
D: panic
gopackage main
import (
"bytes"
"fmt"
"runtime"
"sync"
"time"
)
var pool = sync.Pool{New: func() interface{} { return new(bytes.Buffer) }}
func main() {
go func() {
for {
processRequest(1 << 28) // 256MiB
}
}()
for i := 0; i < 1000; i++ {
go func() {
for {
processRequest(1 << 10) // 1KiB
}
}()
}
var stats runtime.MemStats
for i := 0; ; i++ {
runtime.ReadMemStats(&stats)
fmt.Printf("Cycle %d: %dB\n", i, stats.Alloc)
time.Sleep(time.Second)
runtime.GC()
}
}
func processRequest(size int) {
b := pool.Get().(*bytes.Buffer)
time.Sleep(500 * time.Millisecond)
b.Grow(size)
pool.Put(b)
time.Sleep(1 * time.Millisecond)
}
A: 不能编译
B: 可以编译,运行时正常,内存稳定
C: 可以编译,运行时内存可能暴涨
D: 可以编译,运行时内存先暴涨,但是过一会会回收掉
gopackage main
import (
"fmt"
"runtime"
"time"
)
func main() {
var ch chan int
go func() {
ch = make(chan int, 1)
ch <- 1
}()
go func(ch chan int) {
time.Sleep(time.Second)
<-ch
}(ch)
c := time.Tick(1 * time.Second)
for range c {
fmt.Printf("#goroutines: %d\n", runtime.NumGoroutine())
}
}
A: 不能编译
B: 一段时间后总是输出 #goroutines: 1
C: 一段时间后总是输出 #goroutines: 2
D: panic
gopackage main
import "fmt"
func main() {
var ch chan int
var count int
go func() {
ch <- 1
}()
go func() {
count++
close(ch)
}()
<-ch
fmt.Println(count)
}
A: 不能编译
B: 输出 1
C: 输出 0
D: panic
gopackage main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
m.LoadOrStore("a", 1)
m.Delete("a")
fmt.Println(m.Len())
}
A: 不能编译
B: 输出 1
C: 输出 0
D: panic
gopackage main
var c = make(chan int)
var a int
func f() {
a = 1
<-c
}
func main() {
go f()
c <- 0
print(a)
}
A: 不能编译
B: 输出 1
C: 输出 0
D: panic
D
会产生死锁panic,因为Mutex 是互斥锁。在 A 函数中,获取了互斥锁 mu,然后调用 B 函数。在 B 函数中又调用了 C 函数。
在 C 函数中,再次尝试获取已经被 A 函数获取且尚未释放的互斥锁 mu,这会导致死锁,从而引发 panic 。
因为一个互斥锁在同一时刻不能被多次获取,除非先释放之前获取的锁。
综上所述,这段代码会 panic 而不是完成正常的执行并输出特定结果,所以选择 D 选项。
D
首先,在 main 函数中启动了一个 goroutine 执行 A 函数,然后 main 函数睡眠 2 秒后尝试获取写锁(mu.Lock)。
在 A 函数中获取了读锁(mu.RLock),然后调用 B 函数。
在 B 函数中睡眠 5 秒后调用 C 函数。
在 C 函数中又获取了读锁。
由于读锁可以被多个 goroutine 同时持有,所以 A、B、C 中的读锁获取都能成功。
但是,当 main 函数在 2 秒后尝试获取写锁时,由于此时还有读锁未释放(A、B、C 中的读锁),所以写锁会被阻塞等待所有读锁释放。而读锁的操作还在进行并且需要 5 秒以上,所以会导致 main 函数在此处一直等待,从而使程序 hang 住。
综上所述,程序会因为写锁等待读锁释放而 hang 住,所以选择 D 选项。
D
sync.WaitGroup 的 Wait 方法用于等待所有的任务完成(即 Add 增加的计数通过 Done 减为 0)。
在给定的代码中,启动的 goroutine 中先调用 wg.Done() 减少计数,然后又调用 wg.Add(1) 增加计数。
然而,当 wg.Wait() 被调用时,如果此时之前增加的计数已经通过 Done 减为 0 了,再去调用 Add(1) 就会导致 panic ,因为这违反了 WaitGroup 的使用规则,即在 Wait 已经开始等待之后,不应该再修改计数。
综上所述,这段代码会导致 panic,所以选择 D 选项。
C
在 Do 方法中,首先进行了一次 done 状态的检查(if o.done == 1)。这是第一次检查,如果 done 已经为 1,表示函数 f 已经执行过,直接返回。
然后获取锁(o.m.Lock()),再次检查 done 的状态(if o.done == 0)。这是第二次检查,确保在获取锁之后,done 仍然为 0 ,才执行函数 f 并将 done 设置为 1 。
这种双重检查的目的是为了减少获取锁的开销。在大多数情况下,如果第一次检查 done 为 1 ,就不需要获取锁,从而提高性能。
然而,这种双重检查锁在没有适当的同步机制下可能会出现问题。在 Go 语言中,由于内存模型的原因,对 done 的读和写操作可能不会被其他 goroutine 按照预期的顺序看到,可能导致不一致的结果。
但在上述代码中,通过使用 sync.Mutex 来保证了对 done 变量的读写操作的原子性和可见性,使得双重检查锁的实现是正确有效的,从而避免了并发访问导致的错误,所以选择 C 选项。
D
当创建 mu2 并将 mu 赋值给 mu2 时,这是值拷贝,而不是引用拷贝。
mu2 拥有自己独立的 Mutex 锁和 count 变量。
当对 mu 进行加锁、修改 count 、解锁的操作后,再对 mu2 进行加锁、修改 count 、解锁的操作,这两组操作是完全独立的,彼此之间没有影响。
所以最终 mu.count 和 mu2.count 都是各自独立增加了 1 ,结果都为 1 。
综上所述,最终打印的结果是 1 1 ,选择 D 选项。
C
在这段代码中:
首先,代码是可以编译成功的,所以 A 选项不正确。
程序中启动了一个 Goroutine 不断处理 256 MiB 的内存请求,同时启动了 1000 个 Goroutine 不断处理 1 KiB 的内存请求。
虽然使用了 sync.Pool 来复用 bytes.Buffer 对象,但是对于 256 MiB 这样的大内存请求,sync.Pool 可能无法及时有效地回收和复用这些内存。
由于不断有新的 256 MiB 的内存分配需求,而且这些大内存块可能不会很快被回收和复用,导致内存的使用可能会快速增长,出现内存暴涨的情况。
虽然周期性地触发了垃圾回收,但也不能保证能够及时有效地回收这些大内存块,从而使得内存可能会持续增长,而不一定会像 D 选项描述的那样过一会就回收掉。
综上所述,运行时内存可能会暴涨,选择 C 选项。
C
首先,代码可以编译成功,所以 A 选项不正确。
在 main 函数中,启动了两个协程:
第一个协程中创建了一个有缓冲的通道 ch 并向其中发送了一个值。
第二个协程在等待 1 秒后从通道 ch 接收一个值。
主程序中有一个 time.Tick 每 1 秒触发一次的循环,用于打印当前的协程数量。
由于第二个协程在等待 1 秒后才接收通道的值,而在这 1 秒内,第一个协程已经在运行并且还未完成(因为还没有被接收),同时主程序的循环也在运行,所以一段时间后总是会有两个协程在运行,输出 #goroutines: 2 。
B 选项不正确,因为不会总是只有 1 个协程在运行。
D 选项不正确,代码中没有会导致 panic 的情况。
综上所述,选择 C 选项。
D
在这个程序中,首先定义了一个未初始化的 chan int 类型的通道 ch 。
然后启动了两个协程,第一个协程尝试向未初始化的通道 ch 发送值,这会导致运行时错误,引发 panic 。
在向一个未初始化(即 nil )的通道发送或接收值时,Go 语言会抛出运行时恐慌。
A 选项,代码可以编译,但运行时会出问题。
B 选项和 C 选项,由于在向通道发送值时就已经发生了 panic ,所以无法执行到后续打印 count 的部分。
综上所述,选择 D 选项。
A
sync.Map 的 Len 方法并不存在。所以这段代码无法通过编译。
sync.Map 没有提供直接获取元素数量的方法,因此无法直接获取其长度或元素个数。
B 选项,因为无法获取长度,所以不会输出 1 。
C 选项,同理,也不会输出 0 。
D 选项,代码由于语法错误无法编译,而不是在运行时发生 panic 。
综上所述,选择 A 。
B
在 main 函数中,启动了一个 go 协程 f,在 f 函数中,先将变量 a 赋值为 1,然后等待从通道 c 接收数据。
在 main 函数中,向通道 c 发送数据 0。
当发送数据到通道 c 后,f 函数中的 <-c 操作得以继续,此时 a 已经被赋值为 1 。
最后在 main 函数中打印 a 的值,所以输出 1 。
A 选项,代码可以编译成功。
C 选项,因为在 f 函数中 a 已经被修改为 1 ,所以不会输出 0 。
D 选项,代码运行过程中没有导致 panic 的情况。
综上所述,选择 B 。
本文作者:han
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!