Как можно определить порядок получения заказа по каналам? - PullRequest
0 голосов
/ 17 января 2020

Рассмотрим следующий пример из обхода go.

Как можно определить порядок приема по каналам? почему х всегда получают первый вывод из группы? Звучит разумно, но я не нашел никакой документации по этому поводу. Я попытался добавить немного сна, и все же x получил вход от первого выполненного распределения маршрутов.

    c := make(chan int)
    go sumSleep(s[:len(s)/2], c)
    go sum(s[len(s)/2:], c)
    x, y := <-c, <-c // receive from c

    fmt.Println(x, y, x+y)

Сон перед отправкой на канал.

Ответы [ 3 ]

6 голосов
/ 17 января 2020

Сообщения всегда принимаются в порядке их отправки. Это определено c.

Однако, порядок выполнения любой данной операции через параллельные промежутки времени равен , а не terministi c. Таким образом, если у вас есть две программы, одновременно отправляющие по каналу, вы не можете знать, кто отправит первым, а кто отправит вторым. То же самое, если у вас есть две программы, принимающие по одному каналу.

1 голос
/ 17 января 2020

Я попытался добавить немного сна, и все же x получил ввод от первой выполненной программы

В дополнение к тому, что написал @Adrian, в вашем коде x всегда будет результат с первого recieve на c из-за языковых правил для назначения кортежей

Назначение происходит в два этапа. Во-первых, операнды выражений индекса и косвенных указателей (включая неявные косвенные указатели в селекторах) слева и выражения справа оцениваются в обычном порядке. Во-вторых, задания выполняются в порядке слева направо.

0 голосов
/ 17 января 2020

Чтобы добавить немного к ответ Адриана : мы не знаем, в каком порядке могли бы выполняться эти две программы. Если ваш сон наступает раньше, чем отправка по каналу, и он спит «достаточно долго», 1 , что гарантирует, что программа other сможет выполнить свою отправку. Если обе программы выполняются «одновременно» и не ждут (как в исходном примере Тура), мы не можем быть уверены, какая из них действительно достигнет своей строки c <- sum первой.

Запуск примера Тура на Go Игровая площадка (либо напрямую, либо через веб-сайт Тура), я на самом деле получаю:

-5 17 12

в окне вывода, которое (потому что мы знаем, что -9 находится во 2-й половине slice) говорит нам, что секунда goroutine «добралась» (до канала-send) первой. В некотором смысле это просто удача, но при использовании Go Playground все задания выполняются в довольно детерминированной среде c, с одним ЦП и с совместным планированием, так что результаты более предсказуемы. Другими словами, если вторая горутина попала туда первой за один проход, то, вероятно, будет на следующей. Если игровая площадка использует несколько процессоров и / или менее детерминированную среду c, результаты могут меняться от одного прогона к следующему, но это не гарантируется.

В любом случае, при условии, что ваш код делает то, что вы говорите (и я верю, что так и есть), это:

go sumSleep(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)

заставляет первого отправителя ждать, а второй отправитель запускается первым. Но это то, что мы уже наблюдали, на самом деле произошло, когда мы позволили двум рутинным гонкам. Чтобы увидеть изменение , нам нужно сделать секунду задержку отправителя.

Я сделал модифицированную версию примера в Go Playground здесь , который печатает больше аннотаций. С задержкой, вставленной во вторую половинную сумму, мы видим первую половинную сумму как x:

2nd half: sleeping for 1s
1st half: sleeping for 0s
1st half: sending 17
2nd half: sending -5
17 -5 12

, как мы можем ожидать, поскольку одна секунда "достаточно длинна".


1 Как долго является"достаточно длинным"? Ну, это зависит от того, насколько быстры наши компьютеры? Сколько еще вещей они делают? Если компьютер достаточно быстрый, может потребоваться задержка в несколько миллисекунд или даже несколько наносекунд. Если наш компьютер действительно старый или очень занят другими задачами с более высоким приоритетом, то нескольких миллисекунд может не хватить времени. Если проблема достаточно большая, одной секунды может не хватить. Часто неразумно выбирать какое-то конкретное количество time , если вы можете лучше контролировать это с помощью какой-то операции synchronization , и обычно вы можете это сделать. Например, использование переменной sync.WaitGroup позволяет подождать, пока n goroutines (для некоторого значения времени выполнения n) вызовет функцию Done, прежде чем ваша собственная процедура будет выполнена.


Код игровой площадки, скопированный в StackOverflow для удобства

package main

import (
    "fmt"
    "time"
)

func sum(s []int, c chan int, printme string, delay time.Duration) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    fmt.Printf("%s: sleeping for %v\n", printme, delay)
    time.Sleep(delay)
    fmt.Printf("%s: sending %d\n", printme, sum)
    c <- sum
}

func main() {
    s := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(s[:len(s)/2], c, "1st half", 0*time.Second)
    go sum(s[len(s)/2:], c, "2nd half", 1*time.Second)
    x, y := <-c, <-c

    fmt.Println(x, y, x+y)
}
...