2013-11-17 2 views
0

Я пытаюсь написать прослушиватель событий и попытаться контролировать поток состояния внутри слушателя. Я знал, что пропустил какой-то принцип использования канала, и код может выглядеть глупо. Тем не менее, я буду признателен, если кто-то может помочь мне понять, что моя ошибка и как ее улучшить.Путаница о потоке управления каналом

Этот код не может работать:

package main 

import (
    "fmt" 
    "time" 
) 

type A struct { 
    count int 
    ch chan bool 
    exit chan bool 
} 

func (this *A) Run() { 
    for { 
     select { 
     case <-this.ch: 
      this.handler() 
     case <-this.exit: 
      return 
     default: 
      time.Sleep(20 * time.Millisecond) 
     } 
    } 
} 

func (this *A) handler() { 
    println("hit me") 
    if this.count > 2 { 
     this.exit <- true 
    } 
    fmt.Println(this.count) 
    this.count += 1 
} 

func (this *A) Hit() { 
    this.ch <- true 
} 

func main() { 
    a := &A{} 
    a.ch = make(chan bool) 
    a.exit = make(chan bool) 

    go a.Hit() 
    go a.Hit() 
    go a.Hit() 
    go a.Hit() 
    a.Run() 

    fmt.Println("s") 
} 

это поднять ошибку:

hit me 
0 
hit me 
1 
hit me 
2 
hit me 
fatal error: all goroutines are asleep - deadlock! 

goroutine 1 [chan send]: 
main.(*A).handler(0x2101bf000) 
/Users/yeer/go/src/github.com/athom/practice/channel-controll.go:31 +0x60 
main.(*A).Run(0x2101bf000) 
/Users/yeer/go/src/github.com/athom/practice/channel-controll.go:19 +0x66 
main.main() 
/Users/yeer/go/src/github.com/athom/practice/channel-controll.go:50 +0xed 
exit status 2 

Однако, этот код работает:

package main 

import (
    "fmt" 
    "time" 
) 

type A struct { 
    count int 
    ch chan bool 
    exit chan bool 
} 

func (this *A) Run() { 
    for { 
     select { 
     case <-this.ch: 
      this.handler() 
     case <-this.exit: 
      return 
     default: 
      time.Sleep(20 * time.Millisecond) 
     } 
    } 
} 

func (this *A) handler() { 
    println("hit me") 
} 

func (this *A) Hit() { 
    this.ch <- true 
    if this.count > 2 { 
     this.exit <- true 
    } 
    fmt.Println(this.count) 
    this.count += 1 
} 

func main() { 
    a := &A{} 
    a.ch = make(chan bool) 
    a.exit = make(chan bool) 

    go a.Hit() 
    go a.Hit() 
    go a.Hit() 
    go a.Hit() 
    a.Run() 

    fmt.Println("s") 
} 

Почему не может вызвать другой канал в один и тот же канальный обработчик уровня?

+0

'this.exit <-true' блокирует работы goroutine в то время как он ожидает goroutine к получать от 'this.exit'. Когда вы 'this.exit <-true' в' handler() 'вызывается из' Run() ',' Run() 'goroutine, который обычно получает' this.exit', застревает, так что ничего не получится это 'истина', поэтому вы получаете эту ошибку. Другим изменением для работы над ним было бы просто 'go this.handler()' вместо 'this.handler()'; Я действительно не знаю, что вы пытаетесь сделать здесь, поэтому я не знаю, что имеет смысл. – twotwotwo

+0

Сокращенная версия: каналы отправляются обычно * между * goroutines. Никогда не делайте блокирующий канал, отправляемый из той же goroutine, которая должна выполнять соответствующий прием канала. – twotwotwo

ответ

0

Ваш код заблокирован, потому что, когда вы отправляете по выходному каналу this.exit <- true, который является тем же самым каналом, который вы получаете с этого канала, и нет никакого способа, который когда-либо будет завершен.

Возможно, наиболее разумной задачей является замена выходного канала логическим флагом. Если вы это сделаете, тогда все будет хорошо.

Play

type A struct { 
    count int 
    ch chan bool 
    exit bool 
} 

func (this *A) Run() { 
    for !this.exit { 
     select { 
     case <-this.ch: 
      this.handler() 
     default: 
      time.Sleep(20 * time.Millisecond) 
     } 
    } 
} 

func (this *A) handler() { 
    println("hit me") 
    if this.count > 2 { 
     this.exit = true 
    } 
    fmt.Println(this.count) 
    this.count += 1 
} 

func (this *A) Hit() { 
    this.ch <- true 
} 

func main() { 
    a := &A{} 
    a.ch = make(chan bool) 

    go a.Hit() 
    go a.Hit() 
    go a.Hit() 
    go a.Hit() 
    a.Run() 

    fmt.Println("Done") 
} 

Другой альтернативой будет запускать каждый обработчик в своей рутине идут с go this.handler()

Play

func (this *A) Run() { 
    for { 
     select { 
     case <-this.ch: 
      go this.handler() // add go here 
     case <-this.exit: 
      return 
     default: 
      time.Sleep(20 * time.Millisecond) 
     } 
    } 
} 

И, наконец, вы могли бы буфер выходного канала

Play

func main() { 
    a := &A{} 
    a.ch = make(chan bool) 
    a.exit = make(chan bool, 5) // add buffer here 

    go a.Hit() 
    go a.Hit() 
    go a.Hit() 
    go a.Hit() 
    a.Run() 

    fmt.Println("Done") 
} 
+0

Глобальный флаг работает здесь, поскольку существует только один goroutine, выполняющий 'Run()'/'обработчик()', но я хотел бы подчеркнуть, что вопросник должен быть осторожным с глобальными флагами, поскольку [модель памяти] (http : //golang.org/ref/mem) указывает только на то, что записи будут видны для _other_ goroutines после операции канала или некоторых других событий. (Я знаю, что вы знали это, Ник, просто выкладывая его для потомков.) Опция 'go handler()' имеет для меня наибольший смысл, если она работает в приложении афер, но любой из вариантов должен избегать deadlck. – twotwotwo

0

Вы должны использовать Time.after() для ожидания в цикле:

select { 
    case <-this.ch: 
     go this.handler() // add go here 
    case <-this.exit: 
     return 
    case <-time.After(20 * time.Millisecond) 
Смежные вопросы