-1

In the below code:

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func getPage(url string) (int, error) {
    resp, err := http.Get(url)
    if err != nil {
        return 0, err
    }

    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return 0, err
    }

    return len(body), nil
}

func getter(urlChan chan string, size chan int) {
    url := <-urlChan
    length, err := getPage(url)
    if err == nil {
        size <- length
        urlChan <- url
    }
}

func main() {
    urls := []string{"http://www.google.com/", "http://www.yahoo.com",
        "http://www.bing.com", "http://bbc.co.uk"}

    sizeChan := make(chan int)
    urlChan := make(chan string)

    for _, url := range urls {
        urlChan <- url
        go getter(urlChan, sizeChan)
    }

    for i := 0; i < len(urls); i++ {
        fmt.Printf("%s has length %d\n", <-urlChan, <-sizeChan)
    }
}

am using urlChan as bi-directional, to know the url(urlChan) for the given size(sizeChan) calculated.

For getter() go-routine, after making first string argument url to urlChan, below output shows a hang situation:

$ go install github.com/shamhub/cs61a
$ 
$ 
$ bin/cs61a 

1) How to perform bi-directional communication on a channel?

2) How to profile the status of go-routines for a given process?

overexchange
  • 15,768
  • 30
  • 152
  • 347
  • 1
    Send SIGQUIT to the process to find out where the process is hung. Sending the `url` through a channel is unnecessarily complex. Simplify the code by passing the `url` as an argument, not as a value received through the channel: `func getter(url string, size chan int) { ... }`. – Charlie Tumahai May 08 '20 at 17:12
  • 1
    that is not correct, this will lead to unpredictable results as pushing to url and size channels might be interleaved across the multiple routines you are spawning. I cant find a case where you would use the same channel for both write then read. your return channel should be of a type that embeds all necessary information you want to send back (ie: `type xx struct { url string; len int; }`). –  May 08 '20 at 17:14
  • @CeriseLimón yes I already did that, but how do I correlate size to url in main goroutine? – overexchange May 08 '20 at 19:05
  • Use `struct { url string; size int }` as channel element type. If you need to know the index in `urls`, then pass the index to `getter` and use `struct { index int; size int }` as the channel element type. – Charlie Tumahai May 08 '20 at 19:08
  • @CeriseLimón Do I need to create channel with element type `struct { index int; size int }` in `main()` and pass on to `getter()`? – overexchange May 08 '20 at 19:44
  • The type used to create a channel must be the same as the type when using the channel (except for the send-only and receive-only attributes). – Charlie Tumahai May 08 '20 at 19:48
  • @CeriseLimón One approach could be `size <- fmt.Sprintf("%s has length %d", url, length)` in `getter()` with channel as `string` type. These are minimum changes – overexchange May 08 '20 at 22:56

2 Answers2

3

As others already noted, you can solve this problem a lot more simply:

type sized struct {
    url    string
    length int
    err    error
}

func sizer(url string, result chan<- sized, wg *sync.WaitGroup) {
    defer wg.Done()
    length, err := getPage(url)
    result <- sized{url, length, err}
}

func main() {
    urls := []string{"http://www.google.com/", "http://www.yahoo.com",
        "http://www.bing.com", "http://bbc.co.uk"}

    ch := make(chan sized)
    var wg sync.WaitGroup
    for _, url := range urls {
        wg.Add(1)
        go sizer(url, ch, &wg)
    }
    go func() {
        wg.Wait()
        close(ch)
    }()

    for result := range ch {
        if result.err != nil {
            fmt.Printf("%s: %s\n", result.url, result.err)
        } else {
            fmt.Printf("%s: length = %d\n", result.url, result.length)
        }
    }
}

Complete example on playground, though it can't dial out, so not really useful there.

How to perform bi-directional communication on a channel?

You can't.

Well, that's not entirely true. Let's say instead: you shouldn't.

Suppose we have a channel:

var ch chan T

for some type T.

The channel itself is inherently ... well, I think non-directional is the right word:

  • anyone can put any value of type T into the channel with ch <- val
  • anyone can take any value of type T out of the channel with var <- ch

This clearly isn't unidirectional, but I think calling it bidirectional is misleading: it has a "put things in" side and a "take things out" side, both lumped into one channel instance, but the channel isn't associated with any particular user. You can copy the channel value to a directional instance (see What's the point of one-way channels in Go?), in either direction, but both are really just references to an underlying non-directional channel object.1 You can then pass the original channel, or a unidirectional-ized copy of it, to any user, or treat any of these as "global" (wide-scope) variables that anyone can use.

Now let's add two entities, which we'll call X and Y. If channel ch is buffered, either or both of X and Y can put items in, and then either or both of A and B can take items out. Items will come out in the order they went in, serializing access; if the channel is full, the attempt to put an item in will block; but items do not transfer specifically from X to Y. In particular:

// Y: paused or stopped

// do this in X when ch is initially empty:
ch <- T{}
v := <-ch

Here, X puts a zero-valued T into the channel, then takes it back out. This isn't directional. X got its own data back.

If the channel isn't empty, but also isn't full, either X or Y can add something to it (as a queue), then take something off the front of the queue. But that's just using the channel as a queue. (See Is it possible to use Go's buffered channel as a thread-safe queue?)

If this queue had certain features that it definitely lacks, you could use a single queue for a sort of bidirectional communication (see Is two way comm possible using a single message queue in C). But it doesn't have them. If you really need bidirectional communication, the answer is obvious: use two queues. Designate one queue—one channel—as the X-sends-to-Y channel, and the other as the Y-sends-to-X channel.


1The underlying channel is accessible / recoverable via unsafe, but don't do that unless you really know exactly what you're doing. Someone who passed you a unidirectional instance probably doesn't intend for you to use the other "side" of the channel.

torek
  • 448,244
  • 59
  • 642
  • 775
  • OK... So, if we do not use `wg` then `range ch` will block in `main()` – overexchange May 08 '20 at 22:07
  • OK... your answer did not address the problem...what went wrong with `urlChan <- url` in `getsetter()`? – overexchange May 08 '20 at 22:29
  • Does goroutine(`G`) block OS thread(`M`) on io block of `http.Get()`? or Does goroutine(`G`) gets removed from context(`P`) on io block by making OS thread(`M`) available for another go-routine? – overexchange May 08 '20 at 22:38
  • I see no occurrences of `getsetter` on this page other than your own comments, so that's entirely unanswerable. As for your `urlChan <- url`: on the first trip through your loop, you are sending on an unbuffered channel with no function reading that channel. The queue's capacity is zero, so yes, the send blocks at that point. – torek May 08 '20 at 22:49
  • No function reading that channel? `<-urlChan` is reading in `main()` go-routine – overexchange May 08 '20 at 22:54
  • How `result chan<- sized` argument declaration of `getter()` different from `result chan sized`? – overexchange May 08 '20 at 23:03
  • No, `main` is *not* reading that channel. `main` is in your first `for` loop. It has not yet reached your second `for` loop, and it never will because it blocks while attempting to send the URL on `urlChan`. As for the declaration `result chan<- sized`, that merely limits this copy of the channel value to send-only (write-to-queue) operations. See the links I included in my answer. Read *all* of the answer, not just parts of it. – torek May 08 '20 at 23:19
1

you got to read and practice the go tour, i believe every details used in below code will be explained there, but, here is one solution

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "sync"
)

func getPage(url string) (int, error) {
    resp, err := http.Get(url)
    if err != nil {
        return 0, err
    }

    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return 0, err
    }

    return len(body), nil
}

type result struct {
    url string
    len int
    err error
}

func main() {
    urls := []string{"http://www.google.com/", "http://www.yahoo.com",
        "http://www.bing.com", "http://bbc.co.uk"}

    resChan := make(chan result)
    var wg sync.WaitGroup
    for _, url := range urls {
        wg.Add(1)
        go func(url string) {
            defer wg.Done()
            l, err := getPage(url)
            resChan <- result{url: url, len: l, err: err}
        }(url)
    }

    go func() {
        wg.Wait()
        close(resChan)
    }()

    for r := range resChan {
        if r.err != nil {
            log.Printf("failed to fetch %q: %v\n", r.url, r.err)
            continue
        }
        fmt.Printf("%s has length %d\n", r.url, r.len)
    }
}

It is not ideal because it will not limit the number of concurrent outgoing requests.

About, 2) How to profile the status of go-routines for a given process?

See pprof tooling. There is a doc https://golang.org/doc/diagnostics.html

and https://stackoverflow.com/a/19145992/4466350