草庐IT

go - 确保只检索一次值

coder 2023-06-30 原文

我正在开发一个 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/

有关go - 确保只检索一次值的更多相关文章

  1. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  2. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

  3. ruby-on-rails - Rake 任务仅调用一次时执行两次 - 2

    我写了一个非常简单的rake任务来尝试找到这个问题的根源。namespace:foodotaskbar::environmentdoputs'RUNNING'endend当在控制台中执行rakefoo:bar时,输出为:RUNNINGRUNNING当我执行任何rake任务时会发生这种情况。有没有人遇到过这样的事情?编辑上面的rake任务就是写在那个.rake文件中的所有内容。这是当前正在使用的Rakefile。requireFile.expand_path('../config/application',__FILE__)OurApp::Application.load_tasks这里

  4. ruby - 我怎样才能只写一次 "Text"并同时检查 path_info 是否包含 'A' ? - 2

    -if!request.path_info.include?'A'%{:id=>'A'}"Text"-else"Text"“文本”写了两次。我怎样才能只写一次并同时检查path_info是否包含“A”? 最佳答案 有两种方法可以做到这一点。使用部分,或使用content_forblock:如果“文本”较长,或者是一个重要的子树,您可以将其提取到一个部分。这会使您的代码变干一点。在给出的示例中,这似乎有点矫枉过正。在这种情况下更好的方法是使用content_forblock,如下所示:-if!request.path_info.inc

  5. ruby-on-rails - 使用 HTTP.get_response 检索 Facebook 访问 token 时出现 Rails EOF 错误 - 2

    我试图在我的网站上实现使用Facebook登录功能,但在尝试从Facebook取回访问token时遇到障碍。这是我的代码:ifparams[:error_reason]=="user_denied"thenflash[:error]="TologinwithFacebook,youmustclick'Allow'toletthesiteaccessyourinformation"redirect_to:loginelsifparams[:code]thentoken_uri=URI.parse("https://graph.facebook.com/oauth/access_token

  6. ruby-on-rails - 使用 Ruby 正确处理 Stripe 错误和异常以实现一次性收费 - 2

    我查看了Stripedocumentationonerrors,但我仍然无法正确处理/重定向这些错误。基本上无论发生什么,我都希望他们返回到edit操作(通过edit_profile_path)并向他们显示一条消息(无论成功与否)。我在edit操作上有一个表单,它可以POST到update操作。使用有效的信用卡可以正常工作(费用在Stripe仪表板中)。我正在使用Stripe.js。classExtrasController5000,#amountincents:currency=>"usd",:card=>token,:description=>current_user.email)

  7. ruby-on-rails - Textmate 'Go to symbol' 相当于 Vim - 2

    在Railcasts上,我注意到一个非常有趣的功能“转到符号”窗口。它像Command-T一样工作,但显示当前文件中可用的类和方法。如何在vim中获取它? 最佳答案 尝试:helptags有各种程序和脚本可以生成标记文件。此外,标记文件格式非常简单,因此很容易将sed(1)或类似的脚本组合在一起,无论您使用何种语言,它们都可以生成标记文件。轻松获取标记文件(除了下载生成器之外)的关键在于格式化样式而不是实际解析语法。 关于ruby-on-rails-Textmate'Gotosymbol

  8. ruby - 如何在 Ruby 中只执行一次方法?有静态变量吗? - 2

    我写了一个脚本,其中包含一些方法定义,没有类和一些公共(public)代码。其中一些方法执行一些非常耗时的shell程序。然而,这些shell程序只需要在第一次调用该方法时执行。现在在C中,我会在每个方法中声明一个静态变量,以确保这些程序只执行一次。我怎么能在Ruby中做到这一点? 最佳答案 ruby中有一个成语:x||=y。defsomething@something||=calculate_somethingendprivatedefcalculate_something#somelongprocessend但是如果您的“长时间

  9. ruby - 格式化数字以每隔三位数拆分一次 - 2

    我想在格式化数字时每隔三个字符放置一个空格。根据这个规范:it"shouldformatanamount"dospaces_on(1202003).should=="1202003"end我想出了这段代码来完成这项工作defspaces_onamountthousands=amount/1000remainder=amount%1000ifthousands==0"#{remainder}"elsezero_padded_remainder='%03.f'%remainder"#{spaces_onthousands}#{zero_padded_remainder}"endend所以我

  10. ruby-on-rails - 如何检索网站图标? - 2

    我正在使用RubyonRailsv3.0.9,我想检索我设置了链接的每个网站的favicon.ico图像。也就是说,如果在我的应用程序中我设置了http://www.facebook.com/URL,我想检索Facebook的图标并在我的网页中使用\插入它。当然,我也想为所有其他网站这样做。如何以“自动”方式从网站检索favicon.ico图标(“自动”是指在网站中搜索图标并获取它的链接-我认为不是,因为并非所有网站都有一个名为“favicon.ico”的图标。我想以“自动”方式识别它)?P.S.:我想做的是像Facebook在您的Facebook页面中添加链接\URL时所做的那样:它

随机推荐