golang开发的一些注意事项(二·)

0 阅读4分钟

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)  // 处理数据
    }
}()
  1. 任务队列
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 退出
}
  1. 使用布尔类型 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 退出
}