Go内存模型:如何通过正确的同步为字段赋值

evrscar2  于 2023-09-28  发布在  Go
关注(0)|答案(1)|浏览(109)

下面是我在Go中实现Promise的核心部分。

  1. // A promise represents the future result of a call to a function.
  2. type promise struct {
  3. // state is the current state of this promise.
  4. state int32
  5. // done is closed when execution completes to unblock concurrent waiters.
  6. done chan struct{}
  7. // the function that will be used to populate the outcome.
  8. function Function
  9. // outcome is set when execution completes.
  10. outcome Outcome
  11. }
  12. // get returns the value associated with a promise.
  13. //
  14. // All calls to promise.get on a given promise return the same result
  15. // but the function is called (to completion) at most once.
  16. //
  17. // - If the underlying function has not been invoked, it will be.
  18. // - If ctx is cancelled, get returns (nil, context.Canceled).
  19. func (p *promise) get(ctx context.Context) Outcome {
  20. if ctx.Err() != nil {
  21. return Outcome{
  22. Value: nil,
  23. Err: ctx.Err(),
  24. }
  25. }
  26. if p.changeState(IsCreated, IsExecuted) {
  27. return p.run(ctx)
  28. }
  29. return p.wait(ctx)
  30. }
  31. // run starts p.function and returns the result.
  32. func (p *promise) run(ctx context.Context) Outcome {
  33. go func() {
  34. v, err := doExecute(ctx, p.function)
  35. p.outcome = Outcome{
  36. Value: v,
  37. Err: err,
  38. }
  39. p.function = nil // aid GC
  40. close(p.done)
  41. }()
  42. return p.wait(ctx)
  43. }
  44. // wait waits for the value to be computed, or ctx to be cancelled.
  45. func (p *promise) wait(ctx context.Context) Outcome {
  46. select {
  47. case <-p.done:
  48. return p.outcome
  49. case <-ctx.Done():
  50. return Outcome{
  51. Value: nil,
  52. Err: ctx.Err(),
  53. }
  54. }
  55. }
  56. func (p *promise) changeState(from, to State) bool {
  57. return atomic.CompareAndSwapInt32(&p.state, int32(from), int32(to))
  58. }

一位同事今天给了我一个Go Memory Model文章的链接。在文章中,作者包括以下示例,并提到g可以打印2,然后打印0

  1. var a, b int
  2. func f() {
  3. a = 1
  4. b = 2
  5. }
  6. func g() {
  7. print(b)
  8. print(a)
  9. }
  10. func main() {
  11. go f()
  12. g()
  13. }

带有竞态的程序是不正确的,并且可能表现出非顺序一致的执行。特别地,注意,读取r可以观察由与r并发执行的任何写入w写入的值。即使发生这种情况,也并不意味着在r之后发生的读取将观察到在w之前发生的写入。
到目前为止,我一直认为在关闭done通道之前设置一个变量可以保证其他例程能够看到这个变量的最新值。
然而,上面的例子让我质疑我对Go如何工作的理解,以及使用done通道是否会产生任何差异。其他例程是否可能检测到done通道已关闭,并继续读取尚未更新的字段?
如果你能向我解释我的信念是否仍然正确,我将非常感激。如果它是错误的,请告诉我正确的方法来同步读&写一个字段。

s71maibg

s71maibg1#

p.outcome的赋值是“在close(done)之前排序”的,因此任何检测到done被关闭的goroutine都会看到p.outcome的最新值,因为如果done被关闭,p.outcome发生在它之前。
p.changeState可能有一个比赛,但你没有包括在你的帖子。
也就是说,通道和goroutine提供了与promise相同的功能,并且以更干净的方式实现:

  1. resultCh:=make(chan resultType)
  2. go func() {
  3. resultCh<-someFunc(ctx)
  4. }()
  5. select {
  6. case <-ctx.Done():
  7. // Canceled
  8. case result:=<-resultCh:
  9. // result is ready
  10. }

相关问题