하나의 채널에서 청취하는 여러 고 루틴


82

동일한 채널에서 동시에 수신하려는 여러 고 루틴이 있습니다. 채널에서 수신을 시작하는 마지막 고 루틴이 값을 얻는 것 같습니다. 이것은 언어 사양의 어딘가에 있습니까 아니면 정의되지 않은 동작입니까?

c := make(chan string)
for i := 0; i < 5; i++ {
    go func(i int) {
        <-c
        c <- fmt.Sprintf("goroutine %d", i)
    }(i)
}
c <- "hi"
fmt.Println(<-c)

산출:

goroutine 4

놀이터의 예

편집하다:

생각보다 복잡하다는 걸 방금 깨달았습니다. 메시지는 모든 고 루틴 주위에 전달됩니다.

c := make(chan string)
for i := 0; i < 5; i++ {
    go func(i int) {
        msg := <-c
        c <- fmt.Sprintf("%s, hi from %d", msg, i)
    }(i)
}
c <- "original"
fmt.Println(<-c)

산출:

original, hi from 0, hi from 1, hi from 2, hi from 3, hi from 4

놀이터의 예


6
나는 단지 출력 (내 거대한 구호에) 마지막 조각을 시도하고 original, hi from 4...
장 키안에게

1
@ChangQian time.Sleep(time.Millisecond)은 채널 전송과 수신 사이 에 a 를 추가 하면 이전 동작을 다시 가져옵니다.
Ilia Choly

답변:


75

예, 복잡합니다.하지만 상황을 훨씬 더 간단하게 만들 수있는 몇 가지 경험 규칙이 있습니다.

  • 글로벌 범위의 채널에 액세스하는 대신 go-routine에 전달 하는 채널에 대해 형식 인수를 사용하는 것을 선호합니다 . 이 방법으로 더 많은 컴파일러 검사를 얻을 수 있으며 모듈화도 향상됩니다.
  • 특정 go-routine ( 'main'포함)의 동일한 채널에서 읽기와 쓰기를 모두 피하십시오 . 그렇지 않으면 교착 상태가 훨씬 더 위험합니다.

다음은이 두 가지 지침을 적용하는 프로그램의 대체 버전입니다. 이 사례는 채널에서 많은 작가와 한 명의 독자를 보여줍니다.

c := make(chan string)

for i := 1; i <= 5; i++ {
    go func(i int, co chan<- string) {
        for j := 1; j <= 5; j++ {
            co <- fmt.Sprintf("hi from %d.%d", i, j)
        }
    }(i, c)
}

for i := 1; i <= 25; i++ {
    fmt.Println(<-c)
}

http://play.golang.org/p/quQn7xePLw

단일 채널에 기록하는 5 개의 go-routine을 생성하며 각각 5 번 기록합니다. 기본 go-routine은 25 개의 메시지를 모두 읽습니다. 표시되는 순서는 종종 순차적이지 않습니다 (즉, 동시성이 분명함).

이 예는 Go 채널의 기능을 보여줍니다. 여러 작성자가 하나의 채널을 공유 할 수 있습니다. Go는 메시지를 자동으로 인터리브합니다.

두 번째 예에서 볼 수 있듯이 한 채널에있는 한 명의 작성자와 여러 명의 독자에 대해서도 동일하게 적용됩니다.

c := make(chan int)
var w sync.WaitGroup
w.Add(5)

for i := 1; i <= 5; i++ {
    go func(i int, ci <-chan int) {
        j := 1
        for v := range ci {
            time.Sleep(time.Millisecond)
            fmt.Printf("%d.%d got %d\n", i, j, v)
            j += 1
        }
        w.Done()
    }(i, c)
}

for i := 1; i <= 25; i++ {
    c <- i
}
close(c)
w.Wait()

두 번째 예 에는 메인 고 루틴에 부과 된 대기가 포함되어 있습니다. 그렇지 않으면 즉시 종료되고 다른 5 개의 고 루틴이 조기에 종료됩니다 ( 이 수정에 대한 olov 덕분에 ) .

두 예 모두 버퍼링이 필요하지 않았습니다. 일반적으로 버퍼링을 성능 향상기로 만 보는 것이 좋은 원칙입니다. 프로그램이 교착하지 않는 경우 없이 버퍼, 그것은 교착하지 않습니다 중 버퍼 (하지만 그 반대는 아니다 항상 true). 따라서 또 다른 경험 규칙으로 버퍼링없이 시작한 다음 필요에 따라 나중에 추가하십시오 .


모든 고 루틴이 끝날 때까지 기다릴 필요가 없습니까?
mlbright

그것은 당신이 의미하는 바에 달려 있습니다. play.golang.org 예제를 살펴보십시오. main다른 고 루틴이 수행하는 작업에 관계없이 끝에 도달하면 종료 되는 함수가 있습니다. 위의 첫 번째 예 main에서는 다른 고 루틴과 잠금 단계이므로 문제가 없습니다. 모든 메시지를 통해 전송되기 때문에 두 번째 예제는 문제없이 작동 c 하기 전에close 함수가 호출이 발생 하기 전에main goroutine의 종료. (당신은 호출하면 주장 할 수 close경우 불필요한이지만 좋은 방법입니다.)
릭-777

1
마지막 예제에서 15 개의 출력물을 (결정적으로)보고 싶다고 가정하면 기다려야합니다. 이를 증명하기 위해 동일한 예가 있지만 시간이 있습니다. Printf 바로 전에 수면
olov

그리고 여기에 time.Sleep과 고 루틴을 기다리기 위해 WaitGroup으로 고정 된 동일한 예가 있습니다 : play.golang.org/p/ESq9he_WzS
olov

처음에는 버퍼링을 생략하는 것이 좋은 권장 사항이라고 생각하지 않습니다. 버퍼링이 없으면 실제로 동시 코드를 작성하지 않으며 교착 상태가 될 수 없을뿐만 아니라 채널의 다른 쪽에서 처리 결과가 전송 후 다음 명령어에서 이미 사용 가능하게됩니다. 의도하지 않게 (또는 초보자의 경우 의도적으로 이벤트) 그것에 의존 할 수 있습니다. 그리고 특별히 기다리지 않고 즉시 결과를 얻었다는 사실에 의존하고 버퍼를 추가하면 경쟁 조건이 생깁니다.
사용자

24

늦게 답장을 드렸지만 앞으로 긴 폴링, "글로벌"버튼, 모든 사람에게 브로드 캐스트 와 같은 다른 사람들에게 도움이되기를 바랍니다 .

Effective Go 는 문제를 설명합니다.

수신자는 수신 할 데이터가있을 때까지 항상 차단합니다.

즉, 하나의 채널을 청취하는 고 루틴이 하나 이상있을 수 없으며 모든 고 루틴이 동일한 값을받을 것으로 기대합니다.

코드 예제를 실행하십시오 .

package main

import "fmt"

func main() {
    c := make(chan int)

    for i := 1; i <= 5; i++ {
        go func(i int) {
        for v := range c {
                fmt.Printf("count %d from goroutine #%d\n", v, i)
            }
        }(i)
    }

    for i := 1; i <= 25; i++ {
        c<-i
    }

    close(c)
}

채널을 듣고있는 5 개의 고 루틴이 있어도 "카운트 1"이 두 번 이상 표시되지 않습니다. 이는 첫 번째 고 루틴이 채널을 차단할 때 다른 모든 고 루틴이 줄을서야하기 때문입니다. 채널이 차단 해제되면 카운트가 이미 수신되어 채널에서 제거되어 라인의 다음 고 루틴이 다음 카운트 값을 얻습니다.


1
감사합니다 - 지금이 예제하게 감지 github.com/goinaction/code/blob/master/chapter6/listing20/...
user31208

아 이거 도움이 되었어요. 정보가 필요한 각 Go 루틴에 대한 채널을 만든 다음 필요한 경우 모든 채널에 메시지를 보내는 것이 좋은 대안이 될까요? 그것이 제가 상상할 수있는 옵션입니다.
ThePartyTurtle

8

복잡하다.

또한 GOMAXPROCS = NumCPU+1. 예를 들면

package main

import (
    "fmt"
    "runtime"
)

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU() + 1)
    fmt.Print(runtime.GOMAXPROCS(0))
    c := make(chan string)
    for i := 0; i < 5; i++ {
        go func(i int) {
            msg := <-c
            c <- fmt.Sprintf("%s, hi from %d", msg, i)
        }(i)
    }
    c <- ", original"
    fmt.Println(<-c)
}

산출:

5, original, hi from 4

그리고 버퍼링 된 채널에서 어떤 일이 발생하는지 확인하십시오. 예를 들면

package main

import "fmt"

func main() {
    c := make(chan string, 5+1)
    for i := 0; i < 5; i++ {
        go func(i int) {
            msg := <-c
            c <- fmt.Sprintf("%s, hi from %d", msg, i)
        }(i)
    }
    c <- "original"
    fmt.Println(<-c)
}

산출:

original

이러한 경우도 설명 할 수 있어야합니다.


7

기존 솔루션을 연구하고 간단한 방송 라이브러리 https://github.com/grafov/bcast를 만들었습니다 .

    group := bcast.NewGroup() // you created the broadcast group
    go bcast.Broadcasting(0) // the group accepts messages and broadcast it to all members

    member := group.Join() // then you join member(s) from other goroutine(s)
    member.Send("test message") // or send messages of any type to the group 

    member1 := group.Join() // then you join member(s) from other goroutine(s)
    val := member1.Recv() // and for example listen for messages

2
훌륭한 lib가 있습니다! 나는 또한 github.com/asaskevich/EventBus를
user

큰 문제는 아니지만 readme에서 가입 해제하는 방법을 언급해야 할 것입니다.
사용자

메모리 누수
jhvaras

:( 당신은 세부 @jhvaras를 설명 할 수 있습니까?
알렉산더 I.Grafov

2

여러 고 루틴의 경우 하나의 채널에서 청취 할 수 있습니다. 핵심은 메시지 자체입니다. 다음과 같은 메시지를 정의 할 수 있습니다.

package main

import (
    "fmt"
    "sync"
)

type obj struct {
    msg string
    receiver int
}

func main() {
    ch := make(chan *obj) // both block or non-block are ok
    var wg sync.WaitGroup
    receiver := 25 // specify receiver count

    sender := func() {
        o := &obj {
            msg: "hello everyone!",
            receiver: receiver,
        }
        ch <- o
    }
    recv := func(idx int) {
        defer wg.Done()
        o := <-ch
        fmt.Printf("%d received at %d\n", idx, o.receiver)
        o.receiver--
        if o.receiver > 0 {
            ch <- o // forward to others
        } else {
            fmt.Printf("last receiver: %d\n", idx)
        }
    }

    go sender()
    for i:=0; i<reciever; i++ {
        wg.Add(1)
        go recv(i)
    }

    wg.Wait()
}

출력은 무작위입니다.

5 received at 25
24 received at 24
6 received at 23
7 received at 22
8 received at 21
9 received at 20
10 received at 19
11 received at 18
12 received at 17
13 received at 16
14 received at 15
15 received at 14
16 received at 13
17 received at 12
18 received at 11
19 received at 10
20 received at 9
21 received at 8
22 received at 7
23 received at 6
2 received at 5
0 received at 4
1 received at 3
3 received at 2
4 received at 1
last receiver 4
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.