Go 不可重复协程安全队列

代码实现

Go 复制代码
package dataStruct

import (
	"errors"
	"sync"
)

// GenericQueue 是一个支持泛型的不可重复队列,具有最大长度限制
// T 是泛型参数
type GenericQueue[T comparable] struct {
	items   map[T]struct{} // 使用 map 来存储元素
	order   []T            // 使用切片来记录元素的顺序
	lock    sync.RWMutex   // 读写锁,保证并发安全
	maxSize int            // 最大队列长度
}

// NewGenericQueue 创建一个新的泛型队列,并指定最大长度
func NewGenericQueue[T comparable](maxSize int) *GenericQueue[T] {
	return &GenericQueue[T]{
		items:   make(map[T]struct{}), // 初始化 items map
		order:   []T{},                // 初始化 order 切片
		maxSize: maxSize,              // 设置最大长度
	}
}

// Add 添加元素到队列
// 如果队列已满,默认移除最旧的元素来腾出空间
func (q *GenericQueue[T]) Add(item T) error {
	q.lock.Lock()         // 加写锁,防止并发修改
	defer q.lock.Unlock() // 解锁

	// 判断元素是否已经存在
	if _, exists := q.items[item]; exists {
		return errors.New("item already exists in the queue")
	}

	// 如果队列已满,移除最旧的元素
	if len(q.order) >= q.maxSize {
		return errors.New("Out of length Queue ")
	}

	// 将元素添加到 map 中,标记该元素已存在
	q.items[item] = struct{}{}
	// 将元素添加到队列顺序中
	q.order = append(q.order, item)
	return nil
}

// Remove 删除队列中的指定元素
// 如果元素不存在,则返回错误
func (q *GenericQueue[T]) Remove(item T) error {
	q.lock.Lock()         // 加写锁,防止并发修改
	defer q.lock.Unlock() // 解锁

	// 判断元素是否存在
	if _, exists := q.items[item]; !exists {
		return errors.New("item not found in the queue")
	}

	// 从 map 中删除该元素
	delete(q.items, item)

	// 从顺序切片中删除该元素
	for i, v := range q.order {
		if v == item {
			q.order = append(q.order[:i], q.order[i+1:]...) // 删除指定位置的元素
			break
		}
	}
	return nil
}

// Update 修改队列中的元素
// 如果元素不存在,则返回错误
func (q *GenericQueue[T]) Update(oldItem, newItem T) error {
	q.lock.Lock()         // 加写锁,防止并发修改
	defer q.lock.Unlock() // 解锁

	// 判断 oldItem 是否存在
	if _, exists := q.items[oldItem]; !exists {
		return errors.New("old item not found in the queue")
	}

	// 判断 newItem 是否已存在
	if _, exists := q.items[newItem]; exists {
		return errors.New("new item already exists in the queue")
	}

	// 从 map 中删除 oldItem,并添加 newItem
	delete(q.items, oldItem)
	q.items[newItem] = struct{}{}

	// 更新顺序切片中的 oldItem 为 newItem
	for i, v := range q.order {
		if v == oldItem {
			q.order[i] = newItem
			break
		}
	}
	return nil
}

// Contains 查询队列中是否存在指定元素
func (q *GenericQueue[T]) Contains(item T) bool {
	q.lock.RLock()         // 加读锁,防止并发修改
	defer q.lock.RUnlock() // 解锁
	_, exists := q.items[item]
	return exists
}

// Traverse 遍历队列中的元素
func (q *GenericQueue[T]) Traverse() []T {
	q.lock.RLock()         // 加读锁,防止并发修改
	defer q.lock.RUnlock() // 解锁
	// 返回元素的副本,防止并发问题
	result := make([]T, len(q.order))
	copy(result, q.order)
	return result
}

// Iterator 返回一个可以用于 for range 的通道
func (q *GenericQueue[T]) Iterator() <-chan T {
	ch := make(chan T)
	go func() {
		q.lock.RLock()         // 加读锁,防止并发修改
		defer q.lock.RUnlock() // 解锁
		defer close(ch)        // 关闭通道
		for _, item := range q.order {
			ch <- item
		}
	}()
	return ch
}

介绍

1. 支持泛型

  • 队列支持泛型参数 T,因此可以存储任何类型的元素,只要该类型是 comparable(可比较的)。这意味着你可以使用这个队列来存储数字、字符串、结构体等类型的数据。

2. 不可重复

  • 队列内部通过 map 来存储元素,因此它自动确保元素的唯一性。也就是说,队列中不会有重复的元素。

3. 顺序存储

  • 使用切片 order 来记录元素的顺序,保证在遍历时元素的顺序是加入队列时的顺序。

4. 并发安全

  • 队列实现了并发控制,使用了 sync.RWMutex 读写锁来保证在多线程环境下的安全操作。多个 goroutine 可以并发地读取队列,或者一个 goroutine 可以安全地修改队列。

5. 常见操作

  • 添加元素(Add) : 如果元素已经存在,则无法再次添加;如果队列已满,返回 Out of length Queue 错误,不会自动移除最旧元素
  • 删除元素(Remove): 可以删除队列中的某个元素。
  • 更新元素(Update): 可以将队列中的某个元素更新为新的元素,前提是新元素不存在。
  • 检查元素是否存在(Contains): 查询队列中是否包含某个元素。
  • 遍历队列(Traverse): 返回队列中所有元素的副本,供遍历使用。
  • 支持 for range 遍历 : 通过 Iterator 方法,队列可以返回一个只读通道,可以直接在 for range 循环中使用。

6. 适用场景

  • 适用于需要按顺序处理且确保元素唯一的场景,例如任务队列、事件队列等。
  • 适用于并发环境,确保多个操作的安全性。

示例用法:

复制代码
Go 复制代码
type Ta struct {
	Name string
}

func main() {
	impl := test.XueTestingImpl
	impl.StartTest()

	// 示例:创建一个支持整数的泛型队列
	queue := dataStruct.NewGenericQueue[Ta](1024)
	queue.Add(Ta{Name: "xzm"})
	queue.Add(Ta{Name: "yyds"})
	queue.Add(Ta{Name: "id"})

	//修改
	queue.Update(Ta{Name: "id"}, Ta{Name: "ids"})

	//遍历
	for ta := range queue.Iterator() {
		fmt.Println(ta)
	}

	//查找
	contains := queue.Contains(Ta{
		Name: "yyds",
	})

	//删除
	queue.Remove(Ta{
		Name: "yyds",
	})

	fmt.Println(contains)

	fmt.Println(queue)
	fmt.Println(queue.Traverse())
	impl.EndTest()
}
相关推荐
Ciderw23 分钟前
Go中的三种锁
开发语言·c++·后端·golang·互斥锁·
C++小厨神1 小时前
C#语言的学习路线
开发语言·后端·golang
梁雨珈3 小时前
PL/SQL语言的图形用户界面
开发语言·后端·golang
Ciderw3 小时前
MySQL为什么使用B+树?B+树和B树的区别
c++·后端·b树·mysql·面试·golang·b+树
齐雅彤4 小时前
Bash语言的并发编程
开发语言·后端·golang
峰子20124 小时前
B站评论系统的多级存储架构
开发语言·数据库·分布式·后端·golang·tidb
Quantum&Coder7 小时前
Objective-C语言的计算机基础
开发语言·后端·golang
五味香7 小时前
Java学习,List 元素替换
android·java·开发语言·python·学习·golang·kotlin
Code侠客行8 小时前
Scala语言的编程范式
开发语言·后端·golang