借助sync.Cond可以实现简单的chan
为避免内存频繁开辟,队列最佳实现是循环队列(为图方便,这里没有采用)。阅读本文前请了解队列和条件变量的知识
package main
import (
"fmt"
"strconv"
"sync"
"time"
)
type Queue struct {
queue []string
cond1 *sync.Cond
cond2 *sync.Cond
size int
}
func NewQueue(size int) *Queue {
var mux sync.Mutex
return &Queue{
cond1: sync.NewCond(&mux),
cond2: sync.NewCond(&mux),
size: size,
}
}
// producer
func (q *Queue) Enqueue(str string) {
for {
q.cond1.L.Lock()
if len(q.queue) >= q.size {
q.cond2.Wait()
}
if len(q.queue) >= q.size {
q.cond1.L.Unlock()
continue
}
q.queue = append(q.queue, str)
q.cond1.L.Unlock()
q.cond1.Signal()
break
}
}
// consumer
func (q *Queue) Dequeue() string {
str := ""
for {
q.cond1.L.Lock()
if len(q.queue) == 0 {
q.cond1.Wait()
}
// 为防止多个协程接收到条件成立信号,必须判断
if len(q.queue) == 0 {
q.cond1.L.Unlock()
continue
}
str = q.queue[0]
q.queue = q.queue[1:]
q.cond1.L.Unlock()
q.cond2.Signal()
break
}
return str
}
func main() {
q := NewQueue(10)
go func() {
for i := 0; i < 1000; i++ {
q.Enqueue(strconv.Itoa(i))
fmt.Println("写数据:",i)
}
}()
go func() {
for {
time.Sleep(time.Millisecond * 200)
val := q.Dequeue()
fmt.Println("读数据:",val)
}
}()
time.Sleep(time.Hour)
}
golang的chan的原理
- chan创建在堆中,返回指针
- 使用环形队列作为缓存区
- 每次操作都要加锁,并更新sendx或recvx(队列的头尾指针)
- 缓存满,进入等待队列,让出cpu
- 被唤醒后,重新进入G执行队列