golang开发的一些注意事项(二·)
1. sync.Cond 同步
1.1 功能
协调多个 Goroutine,使得某个 Goroutine 能够在特定条件达成时被唤醒
1.2 核心方法
- NewCond(l Locker) *Cond:该方法用于创建一个条件变量,需要传入一个实现了 Locker 接口的对象,像 sync.Mutex 或者 sync.RWMutex 都可以。
- Wait():此方法会暂时释放锁,让当前 Goroutine 进入等待状态。当收到通知后,它会重新获取锁。
- Signal():该方法用于唤醒一个正在等待的 Goroutine。
- Broadcast():此方法会唤醒所有正在等待的 Goroutine。
1.3 示例:
生产者 - 消费者
package main
import (
"fmt"
"sync"
"time"
)
var (
cond = sync.NewCond(&sync.Mutex{})
storage []int // 共享存储
cap = 5 // 容量上限
)
// 生产者函数
func producer(id int) {
for {
cond.L.Lock()
// 等待存储有空间
for len(storage) >= cap {
cond.Wait()
}
// 生产数据
item := time.Now().UnixNano()
storage = append(storage, int(item))
fmt.Printf("生产者 %d 添加数据: %d, 存储: %v\n", id, item, storage)
cond.L.Unlock()
// 通知消费者有新数据
cond.Signal()
time.Sleep(time.Millisecond * 200)
}
}
// 消费者函数
func consumer(id int) {
for {
cond.L.Lock()
// 等待存储有数据
for len(storage) == 0 {
cond.Wait()
}
// 消费数据
item := storage[0]
storage = storage[1:]
fmt.Printf("消费者 %d 取出数据: %d, 存储: %v\n", id, item, storage)
cond.L.Unlock()
// 通知生产者有空间
cond.Signal()
time.Sleep(time.Millisecond * 500)
}
}
func main() {
// 启动生产者和消费者
go producer(1)
go consumer(1)
go consumer(2)
// 让主程序运行一段时间
time.Sleep(time.Second * 5)
}
Broadcast()必须在锁持有期间调用,确保状态变更的可见性
与Wait()配合时,必须使用循环检查条件(防止虚假唤醒)
示例如下:package main
import (
"fmt"
"sync"
"time"
)
var (
cond = sync.NewCond(&sync.Mutex{})
shutdown = false // 关闭标志
)
// 工作协程 - 监听任务并处理
func worker(id int) {
cond.L.Lock()
defer cond.L.Unlock()
for !shutdown {
// 等待任务或关闭通知
cond.Wait()
if shutdown {
fmt.Printf("Worker %d 收到关闭通知,退出\n", id)
return
}
// 处理任务 (这里简化为打印)
fmt.Printf("Worker %d 处理任务\n", id)
}
}
// 模拟系统关闭
func shutdownSystem() {
cond.L.Lock()
defer cond.L.Unlock()
fmt.Println("准备关闭系统...")
shutdown = true
cond.Broadcast() // 通知所有等待者
}
func main() {
var wg sync.WaitGroup
// 启动多个工作协程
for i := 1; i <= 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
worker(id)
}(i)
}
// 运行一段时间后关闭系统
time.Sleep(2 * time.Second)
shutdownSystem()
wg.Wait() // 等待所有协程退出
fmt.Println("系统已关闭")
}
2. channel缓冲有什么特点
2.1 功能
Channel(通道)是一种特殊的类型,用于在不同 Goroutine 之间安全地传递数据,有缓冲 Channel 的缓冲区未满时,发送操作不会阻塞;缓冲区未空时,接收操作不会阻塞。缓冲 Channel 允许发送者和接收者以不同的速率工作
2.2 使用场景
1. 生产者 - 消费者模型ch := make(chan int, 100) // 缓冲区大小为 100
// 生产者
go func() {
for i := 0; i < 1000; i++ {
ch <- i
}
close(ch) // 生产结束后关闭 Channel
}()
// 消费者
go func() {
for num := range ch {
process(num) // 处理数据
}
}()
- 任务队列
tasks := make(chan Task, 50) // 最多容纳 50 个任务
// 启动 5 个 Worker
for i := 0; i < 5; i++ {
go worker(tasks)
}
// 提交任务
for _, job := range jobs {
tasks <- job
}
结合 select 使用,避免 Channel 操作永久阻塞
select {
case data := <-ch:
process(data)
case <-time.After(1 * time.Second):
fmt.Println("操作超时")
}
3.定时任务结果收集
results := make(chan Result, 20) // 缓冲 20 个结果
// 启动多个定时任务
for _, task := range tasks {
go func(t Task) {
res := t.Execute()
results <- res // 结果发送到缓冲 Channel
}(task)
}
// 收集结果
for i := 0; i < len(tasks); i++ {
processResult(<-results)
}
3. 如何停止goroutine
由于 Goroutine 没有内置的 “终止” 方法,停止 Goroutine 需要通过协作式的方式实现
3.1 使用 Channel 通知机制
关闭 Channel 会触发所有接收操作立即返回零值。 适合广播通知多个 Goroutine 同时退出。
func worker(done chan struct{}) {
for {
select {
case <-done: // 接收到关闭信号
return
default:
// 执行任务...
time.Sleep(100 * time.Millisecond)
}
}
}
func main() {
done := make(chan struct{})
go worker(done)
// 一段时间后停止 Goroutine
time.Sleep(2 * time.Second)
close(done) // 发送关闭信号
time.Sleep(100 * time.Millisecond) // 等待 Goroutine 退出
}
- 使用布尔类型 Channel 发送信号
需要发送具体的值(如 true),适合一对一通知。 需确保接收方正确处理信号,避免阻塞
func worker(stop chan bool) {
for {
select {
case <-stop: // 接收到停止信号
return
default:
// 执行任务...
}
}
}
func main() {
stop := make(chan bool)
go worker(stop)
// 停止 Goroutine
stop <- true // 发送停止信号
}
3.2 使用 context.Context
context.Context 是 Go 标准库中用于传递取消信号的强大工具,尤其适合多级 Goroutine 嵌套的场景
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // 确保资源释放
go worker(ctx)
// 2 秒后自动取消
func worker(ctx context.Context) {
for {
select {
case <-ctx.Done(): // 接收到取消信号
return
default:
// 执行任务...
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go worker(ctx)
// 停止 Goroutine
cancel() // 发送取消信号
}
3.3 使用 sync.WaitGroup 等待退出
func worker(wg *sync.WaitGroup, stop chan struct{}) {
defer wg.Done() // 通知 WaitGroup 任务完成
for {
select {
case <-stop:
return
default:
// 执行任务...
}
}
}
func main() {
var wg sync.WaitGroup
stop := make(chan struct{})
wg.Add(1) // 增加一个等待的 Goroutine
go worker(&wg, stop)
// 停止 Goroutine
close(stop)
wg.Wait() // 等待所有 Goroutine 退出
}