在Golang的并发编程场景中,队列是常用的数据结构,用于协调多个goroutine之间的任务传递或数据流转。但原生的切片、链表等队列实现不具备并发安全特性,多个goroutine同时读写时会出现数据错乱、程序崩溃等问题,因此需要实现并发安全的队列。下面介绍几种常见的实现方案。

基于sync.Mutex的实现方案
互斥锁是最基础的并发控制手段,通过在队列的入队、出队操作前加锁,操作完成后解锁,保证同一时间只有一个goroutine能修改队列数据,从而实现并发安全。
首先定义队列结构体,包含底层数据切片和互斥锁:
package main
import (
"fmt"
"sync"
)
// MutexQueue 基于互斥锁实现的并发安全队列
type MutexQueue struct {
data []interface{}
mu sync.Mutex
}
// NewMutexQueue 初始化队列
func NewMutexQueue() *MutexQueue {
return &MutexQueue{
data: make([]interface{}, 0),
}
}
// Enqueue 入队操作
func (q *MutexQueue) Enqueue(val interface{}) {
q.mu.Lock()
defer q.mu.Unlock()
q.data = append(q.data, val)
}
// Dequeue 出队操作,返回值和是否成功
func (q *MutexQueue) Dequeue() (interface{}, bool) {
q.mu.Lock()
defer q.mu.Unlock()
if len(q.data) == 0 {
return nil, false
}
val := q.data[0]
q.data = q.data[1:]
return val, true
}
// Size 获取队列长度
func (q *MutexQueue) Size() int {
q.mu.Lock()
defer q.mu.Unlock()
return len(q.data)
}
这种方案实现简单,适合读写频率相对均衡的场景,但所有操作都需要竞争同一把锁,高并发下锁竞争会比较激烈,性能会有一定损耗。
基于sync.RWMutex的实现方案
如果队列的读操作远多于写操作,可以使用读写锁优化。读写锁允许多个goroutine同时读,但写操作时会独占锁,能减少读场景下的锁竞争。
package main
import (
"fmt"
"sync"
)
// RWMutexQueue 基于读写锁实现的并发安全队列
type RWMutexQueue struct {
data []interface{}
mu sync.RWMutex
}
// NewRWMutexQueue 初始化队列
func NewRWMutexQueue() *RWMutexQueue {
return &RWMutexQueue{
data: make([]interface{}, 0),
}
}
// Enqueue 入队操作,写操作加写锁
func (q *RWMutexQueue) Enqueue(val interface{}) {
q.mu.Lock()
defer q.mu.Unlock()
q.data = append(q.data, val)
}
// Dequeue 出队操作,写操作加写锁
func (q *RWMutexQueue) Dequeue() (interface{}, bool) {
q.mu.Lock()
defer q.mu.Unlock()
if len(q.data) == 0 {
return nil, false
}
val := q.data[0]
q.data = q.data[1:]
return val, true
}
// Size 获取队列长度,读操作加读锁
func (q *RWMutexQueue) Size() int {
q.mu.RLock()
defer q.mu.RUnlock()
return len(q.data)
}
该方案在读多写少的场景下性能优于互斥锁方案,但如果写操作频率较高,读写锁的开销反而会更大。
基于channel的实现方案
Golang的channel本身就是并发安全的,天生适合用于goroutine之间的通信,也可以直接作为并发安全队列使用。如果队列有固定容量需求,可以初始化带缓冲的channel,超出容量时入队操作会阻塞。
package main
import (
"fmt"
)
// ChannelQueue 基于channel实现的并发安全队列
type ChannelQueue struct {
ch chan interface{}
}
// NewChannelQueue 初始化队列,capacity为队列容量,0表示无缓冲
func NewChannelQueue(capacity int) *ChannelQueue {
return &ChannelQueue{
ch: make(chan interface{}, capacity),
}
}
// Enqueue 入队操作,队列满时会阻塞
func (q *ChannelQueue) Enqueue(val interface{}) {
q.ch <- val
}
// Dequeue 出队操作,队列空时会阻塞,可通过select配合default实现非阻塞出队
func (q *ChannelQueue) Dequeue() (interface{}, bool) {
val, ok := <-q.ch
return val, ok
}
// DequeueNonBlock 非阻塞出队
func (q *ChannelQueue) DequeueNonBlock() (interface{}, bool) {
select {
case val := <-q.ch:
return val, true
default:
return nil, false
}
}
// Size 获取队列长度
func (q *ChannelQueue) Size() int {
return len(q.ch)
}
// Close 关闭队列
func (q *ChannelQueue) Close() {
close(q.ch)
}
channel方案不需要手动处理锁逻辑,代码更简洁,而且支持和select语句配合实现超时、非阻塞等操作,适合任务传递类的队列场景。但channel不支持直接获取所有元素、遍历等操作,灵活性不如前两种方案。
不同方案对比
以下是三种方案的对比,可根据业务场景选择:
| 实现方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| sync.Mutex | 实现简单,逻辑清晰 | 锁竞争激烈时性能差 | 读写频率均衡的通用场景 |
| sync.RWMutex | 读多写少时性能更好 | 写频繁时开销更大 | 读操作远多于写操作的场景 |
| channel | 原生并发安全,支持阻塞/非阻塞操作 | 灵活性低,不支持遍历等操作 | goroutine间任务传递场景 |
注意事项
- 使用互斥锁或读写锁方案时,要注意锁的粒度,尽量只锁队列数据修改的部分,避免锁范围过大影响性能。
- 使用channel方案时,如果队列不再使用,要及时调用close方法关闭channel,避免goroutine泄漏。
- 出队操作要判断返回值的有效性,避免队列为空时获取到无效数据导致程序异常。
Golang并发安全队列goroutinesync_mutexchannel修改时间:2026-06-24 21:27:33