如何在Go语言中在并发的goroutines中锁定/同步对变量的访问?

huangapple go评论78阅读模式
英文:

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&#39;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, &quot;Counting %s, %d so far.&quot;, 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.

huangapple
  • 本文由 发表于 2012年5月24日 06:41:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/10728863.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定