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  }