1

On my machine there are 4 logical processors. so there are four contexts P1, P2, P3 & P4 working with OS threads M1, M2, M3 & M4

$ lscpu
Architecture:        x86_64
CPU op-mode(s):      32-bit, 64-bit
Byte Order:          Little Endian
CPU(s):              4
On-line CPU(s) list: 0-3
Thread(s) per core:  2
Core(s) per socket:  2
Socket(s):           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 worker(urlChan chan string, sizeChan chan<- string, i int) {
    for {
        url := <-urlChan
        length, err := getPage(url)
        if err == nil {
            sizeChan <- fmt.Sprintf("%s has length %d (%d)", url, length, i)
        } else {
            sizeChan <- fmt.Sprintf("%s has error %s (%d)", url, err, i)
        }
    }
}

func main() {

    urls := []string{"http://www.google.com/", "http://www.yahoo.com",
        "http://www.bing.com", "http://bbc.co.uk", "http://www.ndtv.com", "https://www.cnn.com/"}

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

    for i := 0; i < len(urls); i++ {
        go worker(urlChan, sizeChan, i)
    }

    for _, url := range urls {
        urlChan <- url
    }

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

}

there are six go-routines that perform http.Get()


1)

Does OS thread(M1) get blocked with go-routine(G1) on io(http.Get())? on context P1

or

Does Go scheduler pre-empt go-routine(G1) from OS thread(M1) upon http.Get()? and assign G2 to M1... if yes, on pre-emption of G1, how G1 is managed by Goruntime to resume G1 upon completion of IO(http.Get)?

2)

What is the api to retrieve context number(P) used for each go-routine(G)? for debugging purpose..

3) we maintain critical section using counted semaphore for above reader writer problem using C pthreads library. Why are we not getting into the usage of critical sections using go-routines and channels?

overexchange
  • 15,768
  • 30
  • 152
  • 347
  • 1
    Run the program with `GODEBUG=schedtrace=1000` to see the goroutine schedule trace – rustyx May 09 '20 at 00:15
  • Note that while the concurrently executing user threads are limited to GOMAXPROCS, the runtime will spawn as many threads as it needs to service blocking syscalls, which of course includes any actual blocking IO calls. – JimB May 09 '20 at 00:17
  • See also, https://stackoverflow.com/questions/24599645/how-do-goroutines-work-or-goroutines-and-os-threads-relation, https://stackoverflow.com/questions/53983656/why-is-threads-usage-getting-increased-with-network-io-in-golang/53988593, https://stackoverflow.com/questions/59722707/how-do-goroutines-and-the-os-threads-running-them-behave-when-they-get-blocked/59722796, https://stackoverflow.com/questions/36489498/when-a-goroutine-blocks-on-i-o-how-does-the-scheduler-identify-that-it-has-stopp/36489725, etc – JimB May 09 '20 at 00:19

1 Answers1

1

No, it doesn't block. My rough (and unsourced, I picked it up through osmosis) understanding is that whenever a goroutine wants to perform a "blocking" I/O that has an equivalent non-blocking version,

  1. Performs a non-blocking version instead.
  2. Records its own ID in a table somewhere keyed by the handle it is "blocking" on.
  3. Transfers responsibility for the completion to a dedicated thread which sits in a select loop (or poll or whatever equivalent is available) waiting for such operations to unblock, and
  4. Suspends itself, freeing up its OS thread (M) to run another goroutine.

When the I/O operation unblocks, the select-loop looks in the table to figure out which goroutine was interested in the result, and schedules it to be run. In this way, goroutines waiting for I/O do not occupy an OS thread.

In case of I/O that can't be done non-blockingly, or any other blocking syscall, the goroutine executes the syscall through a runtime function that marks its thread as blocked, and the runtime will create a new OS thread for goroutines to be scheduled on. This maintains the ability to have GOMAXPROCS running (not blocked) goroutines. This doesn't cause very much thread bloat for most programs, since the most common syscalls for dealing with files, sockets, etc. have been made async-friendly. (Thanks to @JimB for reminding me of this, and the authors of the helpful linked answers.)

hobbs
  • 223,387
  • 19
  • 210
  • 288
  • 1
    There are still some blocking IO calls, but they are treated the same as any other blocking syscall; the runtime spawns new threads as needed to keep same number of user threads running. – JimB May 09 '20 at 00:15
  • not at all. There may be more references in the questions I linked above too, I didn't look too thoroughly. – JimB May 09 '20 at 00:55
  • 1
    we maintain critical section with semaphore for above reader writer problem using C pthreads library. Why are we not getting into critical sections using go-routines? – overexchange May 09 '20 at 10:43
  • @overexchange: besides being irrelevant to the question at hand, if you wrote the same code in Go, it would require the same use of synchronization primitives. You're writing different code however, so comparing implementation details is not useful. – JimB May 09 '20 at 13:30
  • @JimB When you say, "writing same code in Go", Do you mean without using channels? – overexchange May 09 '20 at 13:33
  • @overexchange: I mean with the same overall structure and code flow, which also implies not using channels (unless of course, if you implemented channels in your C code) – JimB May 09 '20 at 13:34
  • @JimB Does that mean, internally, `urlChan` & `sizeChan`(queue datastructure is critical section) is accessed with synchronization primitives, which is an implementation detail. GoLang channel is hiding this implementation detail and allowing us to take a [CSP](https://medium.com/@niteshagarwal_/communicating-sequential-processes-golang-a3d6d5d4b25e) thought process. Is that correct? – overexchange May 09 '20 at 13:37
  • @overexchange: of course channels are accessed through synchronization primitives, or else they wouldn't be safe for concurrent use, which would negate their primary use case. In Go, channels are explicitly used for synchronization. – JimB May 09 '20 at 13:45
  • 1
    For your point: "*In case of I/O that can't be done non-blockingly, or any other blocking syscall, the goroutine executes the syscall through a runtime function that marks its thread as blocked*", does OS scheduler itself context switch OS thread(M), when OS thread goes in such waiting state? – overexchange May 14 '20 at 00:53