英文:
How to lock/synchronize access to a variable in Go during concurrent goroutines?
问题
在他对这个问题的回答中:https://stackoverflow.com/questions/3918941/golang-for-windows-erratic-behavior
用户@distributed建议在并发的goroutine上锁定/同步访问共享变量。
我该如何做到这一点?
关于这个问题的更多信息:
我在同时运行几个goroutine上运行了这段代码(带有对views
的闭包的返回函数):
func makeHomeHandler() func(c *http.Conn, r *http.Request) {
views := 1
return func(c *http.Conn, r *http.Request) {
fmt.Fprintf(c, "Counting %s, %d so far.", r.URL.Path[1:], views)
views++
}
}
看起来IO函数需要一些时间,结果我得到了这样的输出:
Counting monkeys, 5 so far.
Counting monkeys, 5 so far.
Counting monkeys, 5 so far.
Counting monkeys, 8 so far.
Counting monkeys, 8 so far.
Counting monkeys, 8 so far.
Counting monkeys, 11 so far.
它递增得很好,但当它被打印出来时,我可以看到打印+递增的操作根本不是原子的。
如果我将其更改为:
func makeHomeHandler() func(c *http.Conn, r *http.Request) {
views := 0
return func(c *http.Conn, r *http.Request) {
views++
// 我只能希望其他的goroutine在这一点上不会增加计数器
// 即,在上一行执行之后和下一行执行之前!
views_now := views
fmt.Fprintf(c, "Counting %s, %d so far.", r.URL.Path[1:], views_now)
}
}
它似乎工作得很好,但我不完全确定它最终不会失败...
英文:
In his answer to this question: https://stackoverflow.com/questions/3918941/golang-for-windows-erratic-behavior
user @distributed recommended to lock/synchronize access to a shared variable on concurrent goroutines.
How can I do that?
More on the issue:
I get this code (the returned function with a closure on views
) running on several goroutines at the same time:
func makeHomeHandler() func(c *http.Conn, r *http.Request) {
views := 1
return func(c *http.Conn, r *http.Request) {
fmt.Fprintf(c, "Counting %s, %d so far.", r.URL.Path[1:], views)
views++
}
}
It looks like the IO function takes it's time, and as a result I get this kind of output:
Counting monkeys, 5 so far.
Counting monkeys, 5 so far.
Counting monkeys, 5 so far.
Counting monkeys, 8 so far.
Counting monkeys, 8 so far.
Counting monkeys, 8 so far.
Counting monkeys, 11 so far.
It increments fine, but when it gets printed I can see that the operation printing+incrementing is not atomic at all.
If I change it to:
func makeHomeHandler() func(c *http.Conn, r *http.Request) {
views := 0
return func(c *http.Conn, r *http.Request) {
views++
// I can only hope that other goroutine does not increment the counter
// at this point, i.e., right after the previous line and before the
// next one are executed!
views_now := views
fmt.Fprintf(c, "Counting %s, %d so far.", r.URL.Path[1:], views_now)
}
}
It seems to work fine, but I'm not completely sure if it will not fail eventually...
答案1
得分: 29
如果您只需要一个同步计数器,那么使用sync.Mutex
是标准解决方案。sync/atomic包应该只用于低级别的操作或者当您已经测量到严重的性能问题时。
type Counter struct {
mu sync.Mutex
x int64
}
func (c *Counter) Add(x int64) {
c.mu.Lock()
c.x += x
c.mu.Unlock()
}
func (c *Counter) Value() (x int64) {
c.mu.Lock()
x = c.x
c.mu.Unlock()
return
}
func makeHomeHandler() func(c http.ResponseWriter, r *http.Request) {
var views Counter
return func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Counting %s, %d so far.", r.URL.Path[1:], views.Value())
views.Add(1)
}
}
对于您的特定问题,我建议定义一个新类型,满足http.Handler接口,而不是返回一个闭包。这看起来也更简单:
type homeHandler struct {
mu sync.Mutex
views int64
}
func (h *homeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.mu.Lock()
defer h.mu.Unlock()
fmt.Fprintf(w, "Counting %s, %d so far.", r.URL.Path[1:], h.views)
h.views++
}
func init() {
http.Handle("/", new(homeHandler))
}
英文:
If a synchronized counter is all you want, then using sync.Mutex
is the canonical solution. The sync/atomic package should only be used for low level stuff or when you've measured a serious performance problem.
type Counter struct {
mu sync.Mutex
x int64
}
func (c *Counter) Add(x int64) {
c.mu.Lock()
c.x += x
c.mu.Unlock()
}
func (c *Counter) Value() (x int64) {
c.mu.Lock()
x = c.x
c.mu.Unlock()
return
}
func makeHomeHandler() func(c http.ResponseWriter, r *http.Request) {
var views Counter
return func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Counting %s, %d so far.", r.URL.Path[1:], views.Value())
views.Add(1)
}
}
For your particular problem, I'd suggest defining a new type that satisfies the http.Handler interface, rather than returning a closure. That looks simpler too:
type homeHandler struct {
mu sync.Mutex
views int64
}
func (h *homeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.mu.Lock()
defer h.mu.Unlock()
fmt.Fprintf(w, "Counting %s, %d so far.", r.URL.Path[1:], h.views)
h.views++
}
func init() {
http.Handle("/", new(homeHandler))
}
答案2
得分: 11
sync 包含一些同步原语。根据问题,你可以使用 RWMutex 或者普通的 Mutex。
如果你想要更具体的答案,请提供一些关于它的更多信息。
编辑:在阅读了链接的问题之后,你可能正在寻找 sync/atomic,不过 Mutex 也可以。
编辑2:我看到你在帖子中更新了一个示例。这里是使用 sync/atomic 的代码。
<!-- language: go -->
func makeHomeHandler() func(w http.ResponseWriter, r *http.Request) {
var views *uint64 = new(uint64)
atomic.StoreUint64(views, 0) // 我认为这不是必需的
return func(w http.ResponseWriter, r *http.Request) {
// 原子地将 views 加一并获取新值
// 或许你想在这里减一
views_now := atomic.AddUint64(views, 1)
fmt.Fprintf(w, "Counting %s, %d so far.", r.URL.Path[1:], views_now)
}
}
<strike>(注意:我没有测试上面的代码,所以可能会有拼写错误/脑抽)</strike> 我现在已经测试过了。
英文:
The sync package has some synchronization primitives. Depending on the problem you could use a RWMutex or a plain Mutex.
If you want a more specific answer please give some more information on what it's for.
Edit: After reading the linked question you're probably looking for sync/atomic, though a Mutex is fine too.
Edit2: I saw you updated your post with an example. Here's the code using sync/atomic.
<!-- language: go -->
func makeHomeHandler() func(w http.ResponseWriter, r *http.Request) {
var views *uint64 = new(uint64)
atomic.StoreUint64(views, 0) // I don't think this is strictly necessary
return func(w http.ResponseWriter, r *http.Request) {
// Atomically add one to views and get the new value
// Perhaps you want to subtract one here
views_now := atomic.AddUint64(views, 1)
fmt.Fprintf(w, "Counting %s, %d so far.", r.URL.Path[1:], views_now)
}
}
<strike>(Note: I have not tested the above so there might be typos/brainfarts)</strike> I tested it now.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论