github.com/nsqio/nsq/nsqd/in_flight_pqueue.go #18
if n+1 > c {
npq := make(inFlightPqueue, n, c*2)
copy(npq, *pq)
*pq = npq
}
切片分配内存方式,切片有长度和容量2个属性,通过make第二(l)和第三(c)个参数设置。
当切片的容量不够,自动扩容,扩容的大小为(c * 2)。
切片在第二次以后扩容比上一次只多了2个长度,这对于一些容量比较大的切片,内存分配会变得频繁。所以对于需要大容量的切片,进行手动分配,可以减少内存分配次数。
type WaitGroupWrapper struct {
sync.WaitGroup
}
func (w *WaitGroupWrapper) Wrap(cb func()) {
w.Add(1)
go func() {
cb()
w.Done()
}()
}
// can be used as follows:
wg := WaitGroupWrapper{}
wg.Wrap(func() { n.statsdLoop() })
// ...
wg.Wait()
对waitGroup封装,简洁了代码。
统一管理waitGroup的计数器,避免计数器出现负数。
善用原子操作,它会比锁更为高效。原因:原子操作由底层硬件支持,而锁则由操作系统提供的API实现
golang提供了 5中类型的原子操作,nsqd也大量使用到,说明原子操作很常用。通过nsqd的使用,来熟悉golang的原子操作。
github.com/nsqio/nsq/nsqd/client_v2.go #344
func (c *clientV2) SendingMessage() {
atomic.AddInt64(&c.InFlightCount, 1)
atomic.AddUint64(&c.MessageCount, 1)
}
当有客户端消费队列消息时使用,记录缓存区有多少条消息,避免并发消费队列时,数值出错。
github.com/nsqio/nsq/nsqd/channel.go #144
func (c *Channel) exit(deleted bool) error {
......
if !atomic.CompareAndSwapInt32(&c.exitFlag, 0, 1) {
return errors.New("exiting")
}
......
}
用于退出标识。
保证CAS操作完成。
2个原子操作相对应
github.com/nsqio/nsq/nsqd/protocol_v2.go #590
func (p *protocolV2) SUB(client *clientV2, params [][]byte) ([]byte, error) {
if atomic.LoadInt32(&client.State) != stateInit {
return nil, protocol.NewFatalClientErr(nil, "E_INVALID", "cannot SUB in current state")
}
......
atomic.StoreInt32(&client.State, stateSubscribed)
......
}
用于处理当前协程的状态。
例子:在32位计算架构的计算机上写入一个64位的整数。如果在这个写操作未完成的时候有一个读操作被并发的进行了,那么这个读操作很可能会读取到一个只被修改了一半的数据。(工作中没有遇到过)
针对值的写操作改为原子操作,那么就不会出现针对此值的读操作因被并发的进行而读到修改了一半的值的情况了。