优雅地终止基于计时器的编写器并为读者进程执行常规

时间:2016-07-19 07:22:12

标签: go goroutine

我有以下代码,其中包含:

  • 基于自动收报机的作家
  • 进行常规阅读
  • 用于作者和读者沟通的数据通道
  • stop_writer和stop_reader通道,用于优雅地停止作家和读者
  • 信号处理程序,以便处理" Ctrl-C"用户输入

代码:

package main

import (
    "log"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func user_interupt_register() (writer_stop, reader_stop chan struct{}) {
    writer_stop = make(chan struct{}, 1)
    reader_stop = make(chan struct{}, 1)

    signal_channel := make(chan os.Signal)
    signal.Notify(signal_channel, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        for sig := range signal_channel {
            log.Println("Signal received: ", sig)
            reader_stop <- struct{}{}
            log.Println("Signal sent")
            break
        }

        close(signal_channel)
        log.Println("Closing User Interupt Register go routing")
    }()

    return writer_stop, reader_stop
}

func writer(item_id *uint32, data_channel chan uint32) {
    log.Println("[WRITER] Item ID: ", *item_id)
    data_channel <- *item_id
    *item_id++
}

func reader(reader_stop, writer_stop chan struct{}, data_channel chan uint32) {
    var flag = true
    for flag {
        select {
        case <-reader_stop:
            writer_stop <- struct{}{}
            flag = false
        case data, ok := <-data_channel:
            if ok {
                log.Println("[READER] Item ID: ", data)
            }
        default:
            time.Sleep(1 * time.Second)
            log.Println("[READER] No ID to print")
            continue
        }
    }

    log.Println("About to close(reader_stop)")
    close(reader_stop)
    log.Println("close(reader_stop) finished")
}

func main() {
    log.Println("Starting Application")

    data_channel := make(chan uint32)
    defer close(data_channel)

    writer_stop, reader_stop := user_interupt_register()

    log.Println("Start Timer")
    ticker := time.NewTicker(2 * time.Second)

    go reader(reader_stop, writer_stop, data_channel)

    var item_id uint32
    item_id = 1

    var flag = true
    for flag {
        select {
        case <-ticker.C:
            writer(&item_id, data_channel)
        case <-writer_stop:
            log.Println("About to stop timer")
            ticker.Stop()
            log.Println("Timer stopped")
            flag = false
        }
    }

    log.Println("About to close(writer_stop)")
    close(writer_stop)
    log.Println("close(writer_stop) finished")
    log.Println("Exiting Application")
}

当我偶尔停止时,我会得到以下输出:

2016/07/19 16:02:46 Starting Application
2016/07/19 16:02:46 Start Timer
2016/07/19 16:02:47 [READER] No ID to print
2016/07/19 16:02:48 [WRITER] Item ID:  1
2016/07/19 16:02:48 [READER] No ID to print
2016/07/19 16:02:48 [READER] Item ID:  1
2016/07/19 16:02:49 [READER] No ID to print
2016/07/19 16:02:50 [WRITER] Item ID:  2
2016/07/19 16:02:50 [READER] No ID to print
2016/07/19 16:02:50 [READER] Item ID:  2
2016/07/19 16:02:51 [READER] No ID to print
^C2016/07/19 16:02:52 Signal received:  interrupt
2016/07/19 16:02:52 Signal sent
2016/07/19 16:02:52 Closing User Interupt Register go routing
2016/07/19 16:02:52 [WRITER] Item ID:  3
2016/07/19 16:02:52 [READER] No ID to print
2016/07/19 16:02:52 About to close(reader_stop)
2016/07/19 16:02:52 close(reader_stop) finished
^Cpanic: send on closed channel

goroutine 5 [running]:
panic(0x4c1d00, 0xc8200980b0)
    /usr/local/go/src/runtime/panic.go:481 +0x3e6
os/signal.process(0x7ffa8f64d078, 0xc8200980a0)
    /usr/local/go/src/os/signal/signal.go:176 +0x17a
os/signal.loop()
    /usr/local/go/src/os/signal/signal_unix.go:22 +0x75
created by os/signal.init.1
    /usr/local/go/src/os/signal/signal_unix.go:28 +0x37

但是,大多数时候它会在以下输出时正常停止。

2016/07/19 16:03:13 Starting Application
2016/07/19 16:03:13 Start Timer
2016/07/19 16:03:14 [READER] No ID to print
2016/07/19 16:03:15 [WRITER] Item ID:  1
2016/07/19 16:03:15 [READER] No ID to print
2016/07/19 16:03:15 [READER] Item ID:  1
2016/07/19 16:03:16 [READER] No ID to print
^C2016/07/19 16:03:16 Signal received:  interrupt
2016/07/19 16:03:16 Signal sent
2016/07/19 16:03:16 Closing User Interupt Register go routing
2016/07/19 16:03:17 [WRITER] Item ID:  2
2016/07/19 16:03:17 [READER] No ID to print
2016/07/19 16:03:17 [READER] Item ID:  2
2016/07/19 16:03:17 About to close(reader_stop)
2016/07/19 16:03:17 close(reader_stop) finished
2016/07/19 16:03:17 About to stop timer
2016/07/19 16:03:17 Timer stopped
2016/07/19 16:03:17 About to close(writer_stop)
2016/07/19 16:03:17 close(writer_stop) finished
2016/07/19 16:03:17 Exiting Application

那有什么不对?我怎样才能确定它会停止正常停止所有常规和关闭通道?

2 个答案:

答案 0 :(得分:1)

您的代码中发生的事情是,一旦发送信号,一切都会有效,直到reader函数结束。也就是说,信号被发送到writer_stop(语句writer_stop <- struct{}{}被执行。

问题在于您的select为主。您正在writer_stop的{​​{1}}上阅读近似值。

case

由于 select { case <-ticker.C: writer(&item_id, data_channel) case <-writer_stop: log.Println("About to stop timer") ticker.Stop() log.Println("Timer stopped") flag = false } 的性质,如果准备选择多个selectcases会随机选择一个。{/ p>

现在,在您的select之后,在调用select之后,由于错误的同步而导致错误的同步,writer_stop <- struct{}{}被选中并且case <-ticker.C:被调用。

此时writer已返回,这是reader data_channel writer阻止data_channel <- *item_id的唯一方法,导致程序挂起。

发生错误panic: send on closed channel是因为您再次点击^C并且信号频道已关闭。

您可以通过多种方式解决此问题。一个简单的解决方案是向data_channel添加缓冲区:

data_channel := make(chan uint32, 1)

这是实现此目的的一种方法:

func user_interupt_register() (writer_stop, reader_stop chan struct{}) {
    writer_stop = make(chan struct{}, 1)
    // same as before

    go func() {
        sig := <-signal_channel
        writer_stop <- struct{}{} // change to writer_stop
        // same as before
    }()

    return writer_stop, reader_stop
}

func reader(data_channel chan uint32) {
    for item := range data_channel {
        log.Println("[READER] Item ID: ", item)
    }
}

func main() {
    log.Println("Starting Application")

    data_channel := make(chan uint32)
    // defer close(data_channel)

    writer_stop, _ := user_interupt_register()

    log.Println("Start Timer")
    ticker := time.NewTicker(2 * time.Second)
    readTicker := time.NewTicker(1 * time.Second)

    go reader(data_channel)

    go func() { // to print intermittent message
        for _ = range readTicker.C {
            data_channel <- 0 // some insignificant value
        }
    }()

    var item_id uint32 = 1

    for _ = range ticker.C {
        select {
        case <-writer_stop:
            data_channel <- item_id
            close(data_channel)
            ticker.Stop()
            readTicker.Stop()
            return
        default:
            data_channel <- item_id
            item_id++
        }
    }

    close(writer_stop)
}

请注意,这需要通过测试。

答案 1 :(得分:1)

IMO你应该改变你的读者/作者结构。你不需要只为作者停止读者。

当前结构的问题是可能存在将要写入且从未读取的项目。这可能就是为什么你不想让data_channel成为缓冲通道的原因。但是仍有一个项目可能无法读取已写入该频道的内容。

您只需要作家的停止频道。如果该通道发出信号并关闭数据通道,则写入器停止。读者没有选择,只有<ul> @foreach($foods as $food) <li>{{$food->name}}</li> @endforeach </ul></pre>。如果data_channel已关闭,则阅读器将清空其中的现有项目并自行停止。

替换它:

for item := range data_channel

使用:

func reader(reader_stop, writer_stop chan struct{}, data_channel chan uint32) {
    var flag = true
    for flag {
        select {
        case <-reader_stop:
            writer_stop <- struct{}{}
            flag = false
        case data, ok := <-data_channel:
            if ok {
                log.Println("[READER] Item ID: ", data)
            }
        default:
            time.Sleep(1 * time.Second)
            log.Println("[READER] No ID to print")
            continue
        }
    }

    log.Println("About to close(reader_stop)")
    close(reader_stop)
    log.Println("close(reader_stop) finished")
}

卸下:

func reader(writer_stop chan struct{}, data_channel chan uint32) {
    for item := range data_channel {
      log.Println("[READER] Item ID: ", data)
    }
}

并在此处添加:

defer close(data_channel)

然后你需要一些等待读者完成的东西。查看case <-writer_stop: close(data_channel) log.Println("About to stop timer") ticker.Stop() log.Println("Timer stopped") flag = false 。更好的方法是将作者放入常规程序并将读者放入主程序中。这样,如果读者完成,你的主程序就会停止(如果它没有从作者那里接收到任何更多的值 - 而作者关闭了频道,则会停止)。

这里我的工作建议尽量少变:

sync.WaitGroup

您不会收到package main import ( "log" "os" "os/signal" "syscall" "time" ) func user_interupt_register() (writer_stop chan struct{}) { writer_stop = make(chan struct{}, 1) signal_channel := make(chan os.Signal) signal.Notify(signal_channel, syscall.SIGINT, syscall.SIGTERM) go func() { for sig := range signal_channel { log.Println("Signal received: ", sig) writer_stop <- struct{}{} log.Println("Signal sent") break } close(signal_channel) log.Println("Closing User Interupt Register go routing") }() return } func writer(item_id *uint32, data_channel chan uint32, writer_stop chan struct{}) { log.Println("Start Timer") ticker := time.NewTicker(2 * time.Second) var flag = true for flag { select { case <-ticker.C: log.Println("[WRITER] Item ID: ", *item_id) data_channel <- *item_id *item_id++ case <-writer_stop: close(data_channel) log.Println("About to stop timer") ticker.Stop() log.Println("Timer stopped") flag = false } } } func main() { log.Println("Starting Application") data_channel := make(chan uint32) writer_stop := user_interupt_register() var item_id uint32 item_id = 1 go writer(&item_id, data_channel, writer_stop) for data := range data_channel { log.Println("[READER] Item ID: ", data) } log.Println("About to close(writer_stop)") close(writer_stop) log.Println("close(writer_stop) finished") log.Println("Exiting Application") } 消息,否则会产生相同的结果。

注意:其他代码需要相应调整...就像删除writer_stop频道一样。