gpt4 book ai didi

go - 确保只检索一次值

转载 作者:IT王子 更新时间:2023-10-29 02:02:02 25 4
gpt4 key购买 nike

我正在开发一个 Go 包来访问网络服务(通过 HTTP)。每次我从该服务检索一页数据时,我也会得到可用页数的总数。获得此总数的唯一方法是获取其中一页(通常是第一页)。但是,请求此服务需要时间,我需要执行以下操作:

当在 Client 上调用 GetPage 方法并首次检索页面时,检索到的总数应存储在该客户端的某个位置。当调用 Total 方法并且尚未检索到总计时,应获取第一页并返回总计。如果之前通过调用 GetPageTotal 检索了总计,则应该立即返回,根本不需要任何 HTTP 请求。这需要被多个 goroutines 安全使用。我的想法与 sync.Once 类似,但传递给 Do 的函数返回一个值,然后缓存该值并在 Do 被调用。

我记得以前看到过类似的东西,但是现在我试了也找不到了。使用值和类似术语搜索 sync.Once 没有产生任何有用的结果。我知道我可能可以使用互斥量和大量锁定来做到这一点,但是互斥量和大量锁定似乎并不是在 go 中做事情的推荐方法。

最佳答案

一般的“init-once”解决方案

在一般/通常情况下,仅在实际需要时才初始化一次的最简单解决方案是使用 sync.Once及其 Once.Do()方法。

您实际上不需要从传递给 Once.Do() 的函数返回任何值,因为您可以将值存储到例如该函数中的全局变量。

看这个简单的例子:

var (
total int
calcTotalOnce sync.Once
)

func GetTotal() int {
// Init / calc total once:
calcTotalOnce.Do(func() {
fmt.Println("Fetching total...")
// Do some heavy work, make HTTP calls, whatever you want:
total++ // This will set total to 1 (once and for all)
})

// Here you can safely use total:
return total
}

func main() {
fmt.Println(GetTotal())
fmt.Println(GetTotal())
}

上面的输出(在 Go Playground 上尝试):

Fetching total...
1
1

一些注意事项:

  • 您可以使用互斥锁或 sync.Once 实现相同的效果,但后者实际上比使用互斥锁更快。
  • 如果之前调用过GetTotal(),后续调用GetTotal()除了返回之前计算的值什么都不做,这就是一次.Do() 做/确保。 sync.Once “跟踪”它的 Do() 方法之前是否被调用过,如果是,则传递的函数值将不再被调用。
  • sync.Once 提供了此解决方案的所有需求,可以安全地同时使用多个 goroutine,前提是您不直接修改或访问 total 变量来自其他任何地方。

解决您的“异常”案例

一般情况假设total 只能通过GetTotal() 函数访问。

在你的情况下这不成立:你想通过 GetTotal() 函数访问它并且你想在 GetPage() 之后设置它 调用(如果尚未设置)。

我们也可以用 sync.Once 来解决这个问题。我们需要上面的 GetTotal() 函数;并且当执行 GetPage() 调用时,它可能会使用相同的 calcTotalOnce 尝试从接收到的页面设置其值。

它可能看起来像这样:

var (
total int
calcTotalOnce sync.Once
)

func GetTotal() int {
calcTotalOnce.Do(func() {
// total is not yet initialized: get page and store total number
page := getPageImpl()
total = page.Total
})

// Here you can safely use total:
return total
}

type Page struct {
Total int
}

func GetPage() *Page {
page := getPageImpl()

calcTotalOnce.Do(func() {
// total is not yet initialized, store the value we have:
total = page.Total
})

return page
}

func getPageImpl() *Page {
// Do HTTP call or whatever
page := &Page{}
// Set page.Total from the response body
return page
}

这是如何运作的?我们在变量 calcTotalOnce 中创建并使用单个 sync.Once。这确保了它的 Do() 方法可能只调用传递给它的函数 一次,无论这个 Do() 方法在哪里/如何打电话。

如果有人先调用GetTotal()函数,那么里面的函数字面量就会运行,调用getPageImpl()获取页面并初始化 Page.Total 字段中的 >total 变量。

如果首先调用 GetPage() 函数,那么还会调用 calcTotalOnce.Do(),它只是设置 Page.Total total 变量的值。

无论哪条路线先走,都会改变 calcTotalOnce 的内部状态,它会记住 total 计算已经运行,并进一步调用 calcTotalOnce.Do() 永远不会调用传递给它的函数值。

或者只是使用“eager”初始化

另请注意,如果在您的程序的生命周期中很可能必须获取此总数,则可能不值得上述复杂性,因为您可以在变量创建时轻松地对其进行一次初始化。

var Total = getPageImpl().Total

或者如果初始化有点复杂(例如需要错误处理),请使用包 init() 函数:

var Total int

func init() {
page := getPageImpl()
// Other logic, e.g. error handling
Total = page.Total
}

关于go - 确保只检索一次值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50182009/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com