github.com/bruceshao/lockfree@v1.1.3-0.20230816090528-e89824c0a6e9/producer.go (about) 1 /* 2 * Copyright (C) THL A29 Limited, a Tencent company. All rights reserved. 3 * 4 * SPDX-License-Identifier: Apache-2.0 5 * 6 */ 7 8 package lockfree 9 10 import ( 11 "fmt" 12 "runtime" 13 "sync/atomic" 14 "time" 15 ) 16 17 // Producer 生产者 18 // 核心方法是Write,通过调用Write方法可以将对象写入到队列中 19 type Producer[T any] struct { 20 seqer *sequencer 21 rbuf *ringBuffer[T] 22 blocks blockStrategy 23 capacity uint64 24 status int32 25 } 26 27 func newProducer[T any](seqer *sequencer, rbuf *ringBuffer[T], blocks blockStrategy) *Producer[T] { 28 return &Producer[T]{ 29 seqer: seqer, 30 rbuf: rbuf, 31 blocks: blocks, 32 capacity: rbuf.cap(), 33 status: READY, 34 } 35 } 36 37 func (q *Producer[T]) start() error { 38 if atomic.CompareAndSwapInt32(&q.status, READY, RUNNING) { 39 return nil 40 } 41 return fmt.Errorf(StartErrorFormat, "Producer") 42 } 43 44 // Write 对象写入核心逻辑 45 // 首先会从序号产生器中获取一个序号,该序号由atomic自增,不会重复; 46 // 然后通过&运算获取该序号应该放入的位置pos; 47 // 通过循环的方式,判断对应pos位置是否可以写入内容,这个判断是通过available数组判断的; 48 // 如果无法写入则持续循环等待,直到可以写入为止,此处基于一种思想即写入的实时性,写入操作不需要等太久,因此此处是没有阻塞的, 49 // 仅仅通过调度让出的方式,进行一部分cpu让渡,防止持续占用cpu资源 50 // 获取到写入资格后将内容写入到ringbuffer,同时更新available数组,并且调用release,以便于释放消费端的阻塞等待 51 func (q *Producer[T]) Write(v T) error { 52 if q.closed() { 53 return ClosedError 54 } 55 next := q.seqer.wc.increment() 56 for { 57 // 判断是否可以写入 58 r := atomic.LoadUint64(&q.seqer.rc) - 1 59 if next <= r+q.capacity { 60 // 可以写入数据,将数据写入到指定位置 61 q.rbuf.write(next-1, v) 62 // 释放,防止消费端阻塞 63 q.blocks.release() 64 return nil 65 } 66 runtime.Gosched() 67 // 再次判断是否已关闭 68 if q.closed() { 69 return ClosedError 70 } 71 } 72 } 73 74 // WriteWindow 写入窗口 75 // 描述当前可写入的状态,如果不能写入则返回零值,如果可以写入则返回写入窗口大小 76 // 由于执行时不加锁,所以该结果是不可靠的,仅用于在并发环境很高的情况下,进行丢弃行为 77 func (q *Producer[T]) WriteWindow() int { 78 next := q.seqer.wc.atomicLoad() + 1 79 r := atomic.LoadUint64(&q.seqer.rc) 80 if next < r+q.capacity { 81 return int(r + q.capacity - next) 82 } 83 return -int(next - (r + q.capacity)) 84 } 85 86 // WriteTimeout 在写入的基础上设定一个时间,如果时间到了仍然没有写入则会放弃本次写入,返回写入的位置和false 87 // 使用方需要调用 WriteByCursor 来继续写入该位置,因为位置已经被占用,是必须要写的,不能跳跃性写入 88 // 在指定时间内写入成功会返回true 89 // 三个返回项:写入位置、是否写入成功及是否有error 90 func (q *Producer[T]) WriteTimeout(v T, timeout time.Duration) (uint64, bool, error) { 91 if q.closed() { 92 return 0, false, ClosedError 93 } 94 next := q.seqer.wc.increment() 95 96 // 先尝试写数据 (failfast) 97 ok := q.writeByCursor(v, next) 98 if ok { 99 return next, true, nil 100 } 101 102 // 创建定时器 103 waiter := time.NewTimer(timeout) 104 defer waiter.Stop() 105 106 for { 107 select { 108 case <-waiter.C: 109 // 超时触发,执行到此处表示未写入,返回对应结果即可 110 return next, false, nil 111 default: 112 ok = q.writeByCursor(v, next) 113 if ok { 114 return next, true, nil 115 } 116 runtime.Gosched() 117 } 118 119 if q.closed() { 120 return 0, false, ClosedError 121 } 122 } 123 } 124 125 // WriteByCursor 根据游标写入内容,wc是调用 WriteTimeout 方法返回false时对应的写入位置, 126 // 该位置有严格的含义,不要随意填值,否则会造成整个队列异常 127 // 函数返回值:是否写入成功和是否存在error,若返回false表示写入失败,可以继续调用重复写入 128 func (q *Producer[T]) WriteByCursor(v T, wc uint64) (bool, error) { 129 if q.closed() { 130 return false, ClosedError 131 } 132 133 return q.writeByCursor(v, wc), nil 134 } 135 136 func (q *Producer[T]) writeByCursor(v T, wc uint64) bool { 137 // 判断是否可以写入 138 r := atomic.LoadUint64(&q.seqer.rc) - 1 139 if wc <= r+q.capacity { 140 // 可以写入数据,将数据写入到指定位置 141 q.rbuf.write(wc-1, v) 142 // 释放,防止消费端阻塞 143 q.blocks.release() 144 // 返回写入成功标识 145 return true 146 } 147 return false 148 } 149 150 func (q *Producer[T]) close() error { 151 if atomic.CompareAndSwapInt32(&q.status, RUNNING, READY) { 152 return nil 153 } 154 return fmt.Errorf(CloseErrorFormat, "Producer") 155 } 156 157 func (q *Producer[T]) closed() bool { 158 return atomic.LoadInt32(&q.status) == READY 159 }