Go并发编程小测验: 你能答对几道题?
https://colobu.com/2019/04/28/go-concurrency-quizzes/
- D
- D
- D
- B
- D
- 不熟悉Pool,不知道……
- C
- D
- A
- B
另:求正确答案……
正确答案加上了,回答的还不错。
@PureWhiteWu
- D
- D
- D
- B
- D
- 不熟悉Pool,不知道……
- C
- D
- A
- B
另:求正确答案……
鸟哥求教一下,为什么第四题是C呢? @smallnest
我感觉因为有mutex的保护,所以f一定只会被执行一次
因为o.Done没有保护,通过atomic可以保证多CPU情况下不会有问题。 类似的Java的双检查要求变量使用volatile声明。可以和Once的实现代码对照
@PureWhiteWu 鸟哥求教一下,为什么第四题是C呢? @smallnest
有道理,之前只考虑了IA64平台上的情况,在其它平台上不能保证没问题,多谢!
@smallnest 因为o.Done没有保护,通过atomic可以保证多CPU情况下不会有问题。 类似的Java的双检查要求变量使用volatile声明。可以和Once的实现代码对照
@PureWhiteWu 鸟哥求教一下,为什么第四题是C呢? @smallnest
IA64情况下也是有问题的,因为Lock并没有保护着第13行的读,所以即使本goroutine设置了done =1,其它的goroutine在这个时候还是可能读取到done == 0,所以必须通过atomic保证happen before关系(atomic基本可以认为保证happen before,虽然官方没有明确保证,但是认为目前使用起来还没遇到过问题)
@PureWhiteWu 有道理,之前只考虑了IA64平台上的情况,在其它平台上不能保证没问题,多谢!
@smallnest 因为o.Done没有保护,通过atomic可以保证多CPU情况下不会有问题。 类似的Java的双检查要求变量使用volatile声明。可以和Once的实现代码对照
@PureWhiteWu 鸟哥求教一下,为什么第四题是C呢? @smallnest
我认为在这里问题并不是happens before,而是缓存一致性。 Lock可以保证拿到锁的goroutine的write一定happens before 其它goroutine的19行的read。 但是由于其它goroutine在13行有读过done,所以可能会存在cpu的缓存中,导致19行中read是从CPU缓存读取的,就会导致脏读。
@smallnest IA64情况下也是有问题的,因为Lock并没有保护着第13行的读,所以即使本goroutine设置了
done =1,其它的goroutine在这个时候还是可能读取到done == 0,所以必须通过atomic保证happen before关系(atomic基本可以认为保证happen before,虽然官方没有明确保证,但是认为目前使用起来还没遇到过问题)@PureWhiteWu 有道理,之前只考虑了IA64平台上的情况,在其它平台上不能保证没问题,多谢!
@smallnest 因为o.Done没有保护,通过atomic可以保证多CPU情况下不会有问题。 类似的Java的双检查要求变量使用volatile声明。可以和Once的实现代码对照
@PureWhiteWu 鸟哥求教一下,为什么第四题是C呢? @smallnest
你的理解是对的,通过对第13行,第19行使用atomic,可以给它们之间建立一个happen before的关系,保证写对读可见,否则也可以把第13行放在Lock的临界区中,但是性能就不保证了。
Go的内存模型就是定义happen before,保证某一个时刻的w,确保r能读取到。
@PureWhiteWu 我认为在这里问题并不是happens before,而是缓存一致性。 Lock可以保证拿到锁的goroutine的write一定happens before 其它goroutine的19行的read。 但是由于其它goroutine在13行有读过done,所以可能会存在cpu的缓存中,导致19行中read是从CPU缓存读取的,就会导致脏读。
@smallnest IA64情况下也是有问题的,因为Lock并没有保护着第13行的读,所以即使本goroutine设置了
done =1,其它的goroutine在这个时候还是可能读取到done == 0,所以必须通过atomic保证happen before关系(atomic基本可以认为保证happen before,虽然官方没有明确保证,但是认为目前使用起来还没遇到过问题)@PureWhiteWu 有道理,之前只考虑了IA64平台上的情况,在其它平台上不能保证没问题,多谢!
@smallnest 因为o.Done没有保护,通过atomic可以保证多CPU情况下不会有问题。 类似的Java的双检查要求变量使用volatile声明。可以和Once的实现代码对照
@PureWhiteWu 鸟哥求教一下,为什么第四题是C呢? @smallnest
感谢大佬抽空指点! 经常看大佬博客,受益匪浅!
@smallnest 你的理解是对的,通过对第13行,第19行使用atomic,可以给它们之间建立一个happen before的关系,保证写对读可见,否则也可以把第13行放在Lock的临界区中,但是性能就不保证了。
Go的内存模型就是定义happen before,保证某一个时刻的w,确保r能读取到。
@PureWhiteWu 我认为在这里问题并不是happens before,而是缓存一致性。 Lock可以保证拿到锁的goroutine的write一定happens before 其它goroutine的19行的read。 但是由于其它goroutine在13行有读过done,所以可能会存在cpu的缓存中,导致19行中read是从CPU缓存读取的,就会导致脏读。
@smallnest IA64情况下也是有问题的,因为Lock并没有保护着第13行的读,所以即使本goroutine设置了
done =1,其它的goroutine在这个时候还是可能读取到done == 0,所以必须通过atomic保证happen before关系(atomic基本可以认为保证happen before,虽然官方没有明确保证,但是认为目前使用起来还没遇到过问题)@PureWhiteWu 有道理,之前只考虑了IA64平台上的情况,在其它平台上不能保证没问题,多谢!
@smallnest 因为o.Done没有保护,通过atomic可以保证多CPU情况下不会有问题。 类似的Java的双检查要求变量使用volatile声明。可以和Once的实现代码对照
@PureWhiteWu 鸟哥求教一下,为什么第四题是C呢? @smallnest
@smallnest 能帮我解读一下第2题吗? mu.Lock()阻塞我能理解, 为什么第二个mu.RLock()也会阻塞?
@meilihao @smallnest 能帮我解读一下第2题吗?
mu.Lock()阻塞我能理解, 为什么第二个mu.RLock()也会阻塞?
因为go为了防止写锁饥饿,当有写锁等待时,后来的读锁获取不了,会等待写锁完成后再获取读锁。
@smallnest 因为o.Done没有保护,通过atomic可以保证多CPU情况下不会有问题。 类似的Java的双检查要求变量使用volatile声明。可以和Once的实现代码对照
@PureWhiteWu 鸟哥求教一下,为什么第四题是C呢? @smallnest
是因为多核cpu缓存不一致吗?
第四个,都加锁了,当然可以保证happen before 了。 应该选B
鸟哥,@smallnest 第6题Pool的我跑的结果是这样的,不应该是内存先涨再回收吗:
Cycle 0: 0 MB
Cycle 1: 256 MB
Cycle 2: 513 MB
Cycle 3: 769 MB
Cycle 4: 1281 MB
Cycle 5: 1281 MB
Cycle 6: 1281 MB
Cycle 7: 1537 MB
Cycle 8: 1793 MB
Cycle 9: 2049 MB
Cycle 10: 2049 MB
......
Cycle 107: 14593 MB
Cycle 108: 15105 MB
Cycle 109: 2304 MB
Cycle 110: 0 MB
Cycle 111: 256 MB
Cycle 112: 513 MB
......
@smallnest 你的理解是对的,通过对第13行,第19行使用atomic,可以给它们之间建立一个happen before的关系,保证写对读可见,否则也可以把第13行放在Lock的临界区中,但是性能就不保证了。
Go的内存模型就是定义happen before,保证某一个时刻的w,确保r能读取到。
@PureWhiteWu 我认为在这里问题并不是happens before,而是缓存一致性。 Lock可以保证拿到锁的goroutine的write一定happens before 其它goroutine的19行的read。 但是由于其它goroutine在13行有读过done,所以可能会存在cpu的缓存中,导致19行中read是从CPU缓存读取的,就会导致脏读。
@smallnest IA64情况下也是有问题的,因为Lock并没有保护着第13行的读,所以即使本goroutine设置了
done =1,其它的goroutine在这个时候还是可能读取到done == 0,所以必须通过atomic保证happen before关系(atomic基本可以认为保证happen before,虽然官方没有明确保证,但是认为目前使用起来还没遇到过问题)@PureWhiteWu 有道理,之前只考虑了IA64平台上的情况,在其它平台上不能保证没问题,多谢!
@smallnest 因为o.Done没有保护,通过atomic可以保证多CPU情况下不会有问题。 类似的Java的双检查要求变量使用volatile声明。可以和Once的实现代码对照
@PureWhiteWu 鸟哥求教一下,为什么第四题是C呢? @smallnest
这个并没有缓存一致性的问题,17 行以后的代码都是加锁的,数据肯定是一致的。
Java 加 volatile 是为了禁止指令重排,和这儿没关系。对比官方实现加 atomic 是因为 13 行没有加锁,所以为了原子操作 o.done 才加 atomic,不然会有 data race。
所以这个实现,我觉得虽然有数据竞争,但是 f 肯定也只执行一次。
我觉得你说的有道理。
我应该把这个例子再设置复杂一点,不使用基本数据类型uint32,而是使用一个复杂的对象。 本意是测试java单例相同的问题:即使字段被赋值了,其他的goroutine也可能得到一个未完全初始化的对象
@zeast
@smallnest 你的理解是对的,通过对第13行,第19行使用atomic,可以给它们之间建立一个happen before的关系,保证写对读可见,否则也可以把第13行放在Lock的临界区中,但是性能就不保证了。
Go的内存模型就是定义happen before,保证某一个时刻的w,确保r能读取到。
@PureWhiteWu 我认为在这里问题并不是happens before,而是缓存一致性。 Lock可以保证拿到锁的goroutine的write一定happens before 其它goroutine的19行的read。 但是由于其它goroutine在13行有读过done,所以可能会存在cpu的缓存中,导致19行中read是从CPU缓存读取的,就会导致脏读。
@smallnest IA64情况下也是有问题的,因为Lock并没有保护着第13行的读,所以即使本goroutine设置了
done =1,其它的goroutine在这个时候还是可能读取到done == 0,所以必须通过atomic保证happen before关系(atomic基本可以认为保证happen before,虽然官方没有明确保证,但是认为目前使用起来还没遇到过问题)@PureWhiteWu 有道理,之前只考虑了IA64平台上的情况,在其它平台上不能保证没问题,多谢!
@smallnest 因为o.Done没有保护,通过atomic可以保证多CPU情况下不会有问题。 类似的Java的双检查要求变量使用volatile声明。可以和Once的实现代码对照
@PureWhiteWu 鸟哥求教一下,为什么第四题是C呢? @smallnest
这个并没有缓存一致性的问题,17 行以后的代码都是加锁的,数据肯定是一致的。
Java 加 volatile 是为了禁止指令重排,和这儿没关系。对比官方实现加 atomic 是因为 13 行没有加锁,所以为了原子操作 o.done 才加 atomic,不然会有 data race。
所以这个实现,我觉得虽然有数据竞争,但是 f 肯定也只执行一次。
https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!msg/golang-nuts/6HlK0Lz2O5Y 结论是,第四题中虽然没有正确实现单例,但是f也最多只能运行一次,而不会运行多次。
是的,避免误导,答案C我修改了。
目前看来第四题可能存在几个问题:
- 非原子操作,在某些架构下是有问题的
- 先于f执行标志设置
- 有可能一个goroutine看到字段已经被赋值,但是它的值还没有初始化: https://groups.google.com/forum/#!topic/golang-nuts/816FGr-mTUs
代码库中也修改了一下,使用复杂的struct代替基本数据类型int
@PureWhiteWu https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!msg/golang-nuts/6HlK0Lz2O5Y 结论是,第四题中虽然没有正确实现单例,但是f也最多只能运行一次,而不会运行多次。
go从哪个版本开始var c chan int可以直接使用,而不是像我最接触的c:=make(chan int,1)? 谢谢
go从哪个版本开始var c chan int可以直接使用,而不是像我最接触的c:=make(chan int,1)? 谢谢
这个是声明一个channel,没有为它分配内存,要使用它还是需要make的,看下面的闭包函数。
func TestC(t *testing.T) {
var c chan int
go func() {
<-c
t.Logf("read finished")
}()
c <- 1
t.Logf("write finished")
}
这种做法,读写都会卡主,而不会崩溃,
鸟哥,最后3题能给个分析么,没搞明白是为什么.....
@cxzgb123 鸟哥,最后3题能给个分析么,没搞明白是为什么.....
- map不是协程安全的
- slice不是协程安全的
- for循环是拷贝
@smallnest 正确答案加上了,回答的还不错。
@PureWhiteWu
- D
- D
- D
- B
- D
- 不熟悉Pool,不知道……
- C
- D
- A
- B
另:求正确答案……
5不正确,是程序阻塞,因为mu2的状态是lock状态
感谢。要是谁能够给每题答案加上注解就更好了
@smallnest 正确答案加上了,回答的还不错。
@PureWhiteWu
- D
- D
- D
- B
- D
- 不熟悉Pool,不知道……
- C
- D
- A
- B
另:求正确答案……
5不正确,是程序阻塞,因为mu2的状态是lock状态
死锁会报 panic dead lock 的
https://www.quora.com/Why-can-a-mutex-be-used-as-a-memory-barrier mutex should hold an memory barrier, case 4 should be OK
想问一下, sync.Pool 那段代码, quiz6, 是因为申请内存的频率和大小与GC的不一样吗?还是有其他的含义. 我改了原代码, 能看到内存占用比较低(有突刺).
基于 go1.13.1
package main
import (
"bytes"
"fmt"
"runtime"
"sync"
"time"
)
var pool = sync.Pool{New: func() interface{} { return new(bytes.Buffer) }}
func main() {
go func() {
for {
time.Sleep(1e9)
processRequest(1 << 28) // 256MiB
}
}()
for i := 0; i < 1000; i++ {
go func() {
for {
time.Sleep(1e9)
processRequest(1 << 10) // 1KiB
}
}()
}
var stats runtime.MemStats
for i := 0; ; i++ {
runtime.ReadMemStats(&stats)
before := stats.Alloc
fmt.Printf("Cycle %d, Before GC: %dB\n", i, before)
// time.Sleep(time.Second)
runtime.GC()
runtime.ReadMemStats(&stats)
after := stats.Alloc
fmt.Printf("Cycle %d, After GC: %dB\n", i, after)
fmt.Printf("%v\n", before < after)
}
}
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)
}
如果有人对sync.Pool那个例子感到困惑, 看看这个 https://github.com/golang/go/issues/23199