我已经实现了一个Golang员工池,如下所示,其中sem和work是渠道.SEM是跟踪当前活动的工作线程(Goroutine)数量的渠道.工作是将功能传递给活动工作人员执行的渠道.超时将返回任何在超时持续时间内处于空闲状态的工作进程.

package main

import (
    "time"
)

type Pool struct {
    sem chan struct{}
    work chan func()
    timeout time.Duration
}

func NewPool(max, size, spawn int, timeout time.Duration) *Pool {
    if spawn <= 0 {
        panic("workpool spawn is <= 0")
    }
    if spawn > max {
        panic("workpool spawn > max workers")
    }
    p := &Pool{
        sem: make(chan struct{}, max),
        work: make(chan func(), size),
        timeout: timeout,
    }
    for i := 0; i < spawn; i++ {
        p.sem <- struct{}{}
        go p.worker(func() {})
    }
    return p

}

func (p *Pool) AddTask(task func()) {
    select {
        case p.work <- task:
            return
        case p.sem <- struct{}{}:
            go p.worker(task)
            return
    }
}

func (p *Pool) worker(task func()) {
    t := time.NewTimer(p.timeout)
    defer func() {
        t.Stop()
        <- p.sem
    }()

    task()

    for {
        select {
            case task := <- p.work:
                t.Reset(p.timeout)
                task()
            case <- t.C:
                return
        }
    }
}

我通过在for循环中打印i的值进行测试,方法是将i的值传递到匿名函数中包装的池中,如下所示:

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("Hello, world!")

    p := NewPool(3, 10, 1, time.Duration(5) * time.Second)

    for i:=0; i<30; i++ {
        p.AddTask(func () {
            fmt.Print(i, " ")
        })
    }
    time.Sleep(10 * time.Second)
    fmt.Println("End")

}

预期输出应为从0到29的序列号,但实际输出为

Hello, world!
12 12 12 12 12 12 12 12 12 12 12 12 13 25 25 25 25 25 25 25 25 25 25 25 26 25 30 30 30 30 End

我不明白为什么输出是这样的.

推荐答案

您的函数闭包都引用了相同的值i.这会产生争用情况,因为在调度函数时,它们正在读取一个不断变化的值--因此您看到的是不可预测的输出.

要确保闭包获得唯一值,请在循环中声明变量.要做到这一点,一个简单的技巧是通过卷影声明相同的变量名i := i

for i:=0; i<30; i++ {
    i:= i                // <- add this
    p.AddTask(func () {
        fmt.Print(i, " ")
    })
}

https://go.dev/play/p/o0Nyx5A46tp


顺便说一句,这项技术在Effective Go个文档中都有介绍,请参阅本节:

写这篇文章可能看起来很奇怪

req := req

但在围棋中这样做是合法的,也是惯用的.你会得到一个全新的版本 使用相同名称的变量,故意跟踪循环 局部可变,但对每个Goroutine是唯一的.

Go相关问答推荐

macOS上GoLand 2023.3.4中的代码导航

Golang中的泛型 struct /接口列表

在整个SQL事务中将使用上下文作为默认设置吗?

exec的可执行决议.命令+路径

从带有嵌套括号的字符串中提取值

如何将验证器标记添加到嵌套字段

golang gin 获取 cookie json

加载 docker 镜像失败

Golang Oauth2 服务帐户返回空刷新令牌字符串

如何在 Golang 中使用具有相同名称或特定关键字的行或列重新排列/排序 CSV

函数调用中的类型参数panic

Go AST:获取所有 struct

如何从 Go 1.18 中的单个方法返回两种不同的具体类型?

K8s 算子读取原始数据

如何在眼镜蛇(golang)中将标志作为参数传递?

如何从 docker-compose 命令运行 2 个不同的命令:

如何扩充 ResponseWriter 的 Header() 返回的 map

gopls 为 github.com/Shopify/sarama 返回错误gopls: no packages returned: packages.Load error

如何将类型转换为字节数组golang

Golang 与 Cassandra db 使用 docker-compose:cannot connect (gocql)