sync.Cond测试广播 - 为什么要检查循环?

时间:2015-11-21 09:02:48

标签: go synchronization goroutine

我试图使用sync.Cond - 等待和广播。我无法理解它的某些部分:

等待电话的评论说:

   41    // Because c.L is not locked when Wait first resumes, the caller
   42    // typically cannot assume that the condition is true when
   43    // Wait returns.  Instead, the caller should Wait in a loop:
   44    //
   45    //    c.L.Lock()
   46    //    for !condition() {
   47    //        c.Wait()
   48    //    }
   49    //    ... make use of condition ...
   50    //    c.L.Unlock()

这是必要的原因是什么?

所以这意味着以下程序可能不正确(尽管它有效):

package main

import (
    "bufio"
    "fmt"
    "os"
    "sync"
)

type processor struct {
    n       int
    c       *sync.Cond
    started chan int
}

func newProcessor(n int) *processor {

    p := new(processor)
    p.n = n

    p.c = sync.NewCond(&sync.Mutex{})

    p.started = make(chan int, n)

    return p
}

func (p *processor) start() {

    for i := 0; i < p.n; i++ {
        go p.process(i)
    }

    for i := 0; i < p.n; i++ {
        <-p.started
    }
    p.c.L.Lock()
    p.c.Broadcast()
    p.c.L.Unlock()

}

func (p *processor) process(f int) {

    fmt.Printf("fork : %d\n", f)

    p.c.L.Lock()
    p.started <- f
    p.c.Wait()
    p.c.L.Unlock()
    fmt.Printf("process: %d - out of wait\n", f)
}

func main() {

    p := newProcessor(5)
    p.start()

    reader := bufio.NewReader(os.Stdin)
    _,_ =reader.ReadString('\n')

}

1 个答案:

答案 0 :(得分:0)

条件变量不会保持信号状态,它们只会唤醒在.Wait()中阻塞的其他go例程。所以这会出现一个竞争条件,除非你有一个谓词来检查你是否需要等待,或者你想要等待的事情是否已经发生。

在您的特定情况下,您已经使用.BroadCast()频道在调用p.started的go例程和调用start()的广告例程之间添加了同步,其方式尽我所能告诉我不应该提出我在这篇文章中进一步描述的竞争条件。虽然我不会赌它,但就我个人而言,我会像文档描述的那样以惯用的方式来做。

考虑您的p.c.L.Lock() p.c.Broadcast() 函数正在以下行执行广播:

process()

在那个特定的时间点,考虑你的其他一个例程已经在你的fmt.Printf("fork : %d\n", f) 函数中找到了这一点

start()

接下来常规做的就是锁定互斥锁(至少在p.c.L.Lock() p.started <- f p.c.Wait() 释放该互斥锁的常规例程之前它不会拥有)并等待条件变量。< / p>

type processor struct {
    n       int
    c       *sync.Cond
    started chan int
    done    bool //added
}

...

func (p *processor) start() {

    for i := 0; i < p.n; i++ {
        go p.process(i)
    }

    for i := 0; i < p.n; i++ {
        <-p.started
    }
    p.c.L.Lock()
    p.done = true //added
    p.c.Broadcast()
    p.c.L.Unlock()

}

func (p *processor) process(f int) {

    fmt.Printf("fork : %d\n", f)

    p.c.L.Lock()
    p.started <- f
    for !p.done { //added
        p.c.Wait()
    }
    p.c.L.Unlock()
    fmt.Printf("process: %d - out of wait\n", f)
}

但是等待永远不会回归,因为在这一点上没有人会发出信号/广播信号 - 信号已经发生了。

所以你需要另一个条件,你可以测试自己,这样当你已经知道条件已经发生时你就不需要调用Wait(),例如。

// parent.js
var child_process = require('child_process');

var numchild  = require('os').cpus().length;
var done      = 0;

for (var i = 0; i < numchild; i++){
  var child = child_process.fork('./child');
  child.send((i + 1) * 1000);
  child.on('message', function(message) {
    console.log('[parent] received message from child:', message);
    done++;
    if (done === numchild) {
      console.log('[parent] received all results');
    }
  });
}


// child.js
process.on('message', function(message) {
  console.log('[child] received message from server:', message);

  setTimeout(function() {
    process.send({
      child   : process.pid,
      result  : message + 1
    });
    process.disconnect();
  }, 10000); 

});