
Once
这个once的实现有没有什么问题?

有。讨论见这里:#issuecomment-490738912
正确的姿势是使用 原子操作 ,原子操作在修改变量的值后,会也让其他核立马看到数据的变动。Once.Do的官方实现就使用的原子操作:

关于缓存,可以看鸟窝的《cacheline 对 Go 程序的影响》和知乎《细说Cache-L1/L2/L3/TLB》。
Wait Group

会panic:
panic: sync: WaitGroup is reused before previous Wait has returned goroutine 1 [running]: sync.(*WaitGroup).Wait(0xc000018090) / Users /shitaibin/go/src/ github .com/golang/go/src/sync/waitgroup.go:132 +0xae main.main() /Users/shitaibin/Workspace/golang_step_by_step/problems/concurrent/waitgroup0.go:16 +0x79 exit status 2
原因:第13行执行wg.Done()后,wg的计数已经变成了0,wg.Wait()实际以及完成并返回,14行再次使用此wg.Add()报错。
Mutex

结果panic:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [semacquire]:
sync. runtime _SemacquireMutex(0xc0000180ac, 0x100ae00)
/Users/shitaibin/go/src/github.com/golang/go/src/runtime/sema.go:71 +0x3d
sync.(*Mutex).Lock(0xc0000180a8)
/Users/shitaibin/go/src/github.com/golang/go/src/sync/mutex.go:134 +0x109
main.main()
/Users/shitaibin/Workspace/golang_step_by_step/problems/concurrent/mutex0.go:19 +0xb4
原因:MyMutex和sync.Mutex都是结构体,不包含指针,第16行根据mu新建了mu2对象,2者占用不同的内存区域,但2者的“内容”是相同的,所以mu2新建后就已经是Lock状态。第19行mu2.Lock()所以会死锁。
修改:

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 ......
sync.Pool用来存放经常使用的临时对象,如果每次这些内存被GC回收,会加大GC的压力,Pool的出现就是为 减缓 GC的压力,而不是完全不让GC回收Pool的内存。
关于Pool不可错过Dave在高性能Go程序的这段介绍。
channel 1

2个闭包goroutine可运行并结束。最后只有main和定时器协程,所以最终有2个协程在运行,持续打印#goroutines: 2。
channel 2

ch只声明,未进行初始化,所以panic:
panic: close of nil channel goroutine 34 [running]: main.main.func2(0xc000096000, 0x0) /Users/shitaibin/Workspace/golang_step_by_step/problems/concurrent/channel1.go:13 +0x33 created by main.main /Users/shitaibin/Workspace/golang_step_by_step/problems/concurrent/channel1.go:11 +0x87 exit status 2
修改为下面这样,还有问题吗?:

同样会panic,典型的channel由非发送者关闭,造成在关闭的channel上写数据。
panic: send on closed channel goroutine 4 [running]: main.main.func1(0xc000070060) /Users/shitaibin/Workspace/golang_step_by_step/problems/concurrent/channel1.go:10 +0x37 created by main.main /Users/shitaibin/Workspace/golang_step_by_step/problems/concurrent/channel1.go:9 +0x80 exit status 2
Map 1

无法编译,因为Map没有Len()方法。
Map 2

能正常编译和运行。map不是协程安全的,需要锁的保护,但Len()的实现并没有加锁,当map写数据时,并且调用Len读长度,则存在map的并发读写问题,因为不是同时读写map所存的内容,所以可以编译和运行,但存在读取的map内存长度不准确问题。map定义和len的声明如下:
// A header for a Go map. type hmap struct { // Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go. // Make sure this stays in sync with the compiler's definition. count int // # live cells == size of map. Must be first (used by len() builtin) flags uint8 B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items) noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details hash0 uint32 // hash seed buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0. oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated) extra *mapextra // optional fields } // The len built-in function returns the length of v, according to its type: //Array: the number of elements in v. //Pointer to array: the number of elements in *v (even if v is nil). //Slice, or map: the number of elements in v; if v is nil, len(v) is zero. //String: the number of bytes in v. //Channel: the number of elements queued (unread) in the channel buffer; //if v is nil, len(v) is zero. // For some arguments, such as a string literal or a simple array expression, the // result can be a constant. See the Go language specification's "Length and // capacity" section for details. func len(v Type) int
slice

首先,slice不是协程安全的,自身也又没锁的保护,多协程访问存在并发问题:
type slice struct { array unsafe.Pointer len int cap int }
其次,append中有可能还会分配新的内存空间,切片可能指向了新的内存区域:
// The append built-in function appends elements to the end of a slice. If // it has sufficient capacity, the destination is resliced to accommodate the // new elements. If it does not, a new underlying array will be allocated. // Append returns the updated slice. It is therefore necessary to store the // result of append, often in the variable holding the slice itself: //slice = append(slice, elem1, elem2) //slice = append(slice, anotherSlice...) // As a special case, it is legal to append a string to a byte slice, like this: //slice = append([]byte("hello "), "world"...) func append(slice []Type, elems ...Type) []Type
所以,两个协程同时写,是不安全的,并且大概率可能存在数据丢失,所以结果可能不是2000。
源码
如果这篇文章对你有帮助,不妨关注下我的Github,有文章会收到通知。本文作者:大彬,原创授权发布如果喜欢本文,随意转载,但请保留此原文链接: