How can I properly demonstrate concurrency AND parallelism in Go/Golang? - multithreading

For a presentation, I made a program trying to illustrate how you can make a program that is concurrent and runs in parallel using Go. The output appears to show that it is at least running concurrently, but I'm not sure how to tell if it's running in parallel. I've read through a lot of resources on how to use goroutines and sync them together in a WaitGroup, but there seems to be a lot of confusion on whether these run on separate threads. As a novice programmer that's especially new to Go, I would greatly appreciate some clarification!
package main
import (
"fmt"
"sync"
"time"
)
//take order
func takeOrder1(wg *sync.WaitGroup) {
s_sleep(1000)
fmt.Println("\nTaking order...", t_time())
go takeOrder2(wg)
}
func takeOrder2(wg *sync.WaitGroup) {
s_sleep(1500)
fmt.Println("\nOrder tooken!", t_time())
wg.Done()
}
//make fires
func makeFries1(wg *sync.WaitGroup) {
s_sleep(1500)
fmt.Println("\nFrying fries...", t_time())
go makeFries2(wg)
}
func makeFries2(wg *sync.WaitGroup) {
s_sleep(3000)
fmt.Println("\nFries Fried!", t_time())
wg.Done()
}
//burn burger
func makeBurger1(wg *sync.WaitGroup) {
s_sleep(2000)
fmt.Println("\nFlipping burger...", t_time())
go makeBurger2(wg)
}
func makeBurger2(wg *sync.WaitGroup) {
s_sleep(5000)
fmt.Println("\nCooked a burger!", t_time())
wg.Done()
}
//cook drink
func pourDrink1(wg *sync.WaitGroup) {
s_sleep(1000)
fmt.Println("\nPutting ice in cup...", t_time())
go pourDrink2(wg)
}
func pourDrink2(wg *sync.WaitGroup) {
s_sleep(3000)
fmt.Println("\nPouring soda in cup...", t_time())
go pourDrink3(wg)
}
func pourDrink3(wg *sync.WaitGroup) {
s_sleep(2500)
fmt.Println("\nDrink poured!", t_time())
wg.Done()
}
//wipe table
func cleanTable1(wg *sync.WaitGroup) {
s_sleep(1000)
fmt.Println("\n'Cleaning' table....", t_time())
go cleanTable2(wg)
}
func cleanTable2(wg *sync.WaitGroup) {
s_sleep(1500)
fmt.Println("\nTable 'clean'!", t_time())
wg.Done()
}
//delay
func s_sleep(x int) { time.Sleep(time.Duration(x) * time.Millisecond) }
//just to print time
func t_time() string {
return time.Now().Format("15:04:05")
}
//create array of tasks to complete
var McDolansTasks = []func(*sync.WaitGroup){
takeOrder1, makeFries1, makeBurger1, pourDrink1, cleanTable1}
//main function
func main() {
var waitGroup sync.WaitGroup
// Set number of effective goroutines we want to wait upon
waitGroup.Add(len(McDolansTasks))
for _, task := range McDolansTasks {
// Pass reference to WaitGroup instance
// Each of the tasks should call on WaitGroup.Done()
go task(&waitGroup)
}
// Wait until all goroutines have completed execution.
waitGroup.Wait()
println("\nClock out for the day!")
}
(https://play.golang.org/p/4OhaMn1zMT9)

The short answer is that you can't show concurrency and parallelism, because maybe they aren't (concurrent and parallel). For instance, if you have just one executor—one "P", in an internal Go runtime model—you will only be running one goroutine at a time.
... there seems to be a lot of confusion on whether these run on separate threads ...
Go is not defined in terms of "threads" in the first place, so the question can't be answered. That's not very satisfactory, but unless you add qualifiers such as "on my system today", that is all you get! So perhaps you want to add the qualifier on my system today.
The spec allows goroutines to run in parallel, and defines Go's concurrency model, but does not require any particular implementation. This leaves a Go implementor free to use whatever seems suitable. I mentioned "an internal Go runtime model" above, but didn't say which one. I meant the one on your system today.
There's currently one main implementation of Go, albeit with many versions of that implementation, as needed for different CPUs and releases of Go. That one implementation has a runtime (with some things you can invoke in the runtime package that control it to some extent), and that runtime does have an internal model. In that internal model, the system—the OS and/or other implementation that lies below the Go implementation—does have threads. These system threads are called "M" internally: the OS is assumed to start the runtime with one thread, m0, after which the runtime will create new additional OS threads when that seems appropriate.
Povilas Versockas has a (now slightly out of date) write-up on scheduling in the Go runtime here. The answer to When will Go scheduler create a new M and P? describes a few other features.
Note that the terms process and thread are already rather vague, though there's some general agreement on the distinctions. See What is the difference between a process and a thread? A goroutine is very much like a thread, but in general, most modern thread systems assign a "thread ID" to each thread. Goroutines specifically do not have IDs, which means there is no way to talk meaningfully to, or about, a specific goroutine from any other goroutine. This helps force the Go programmer to use channels for communication. (It can't completely force this, but it sure helps. 😀)
To truly demonstrate parallel operation, you can reach beyond the Go specification into the areas of undefined behavior. Whenever the behavior is undefined by the Go spec, it may be defined by something else, and that something-else might then allow you to demonstrate parallelism. Unfortunately, because you've gone beyond the Go spec, you're now no longer writing a (portable) Go program in the first place: your demo simply demonstrates something about the particular implementation.
You can also use debuggers, such as delve. Debuggers generally have to interact with the runtime and OS in various poorly-defined ways just to be able to show you what is going on in your program, and by so doing, they tend to show all kinds of not-defined-by-the-specification but-actually-works-like-this behaviors. You don't get any guarantees about those, unless they're provided by (e.g.) your OS. But you definitely see a lot of stuff!

Related

Best way to wake 0-N sleeping goroutines at once

I'm writing a program where I start N (N is a command-line argument) worker threads, and at any time 0 to N-1 of them can be waiting on another to update a variable. What's the best way for the threads to wait for this event, and the best way for one of the threads to notify all the others at once of the event occurring? This event will be sent multiple times by each thread.
sync.Cond isn't appropriate because the threads don't need to lock a resource upon waking from sleep. sync.WaitGroup won't work because I don't know how many times to call wg.Done().
Solution #1: I could use a sync.Mutex and have the thread that will eventually notify the others acquire the lock and then unlock it to notify the others, but it seems really inefficient for the others to all fight over a lock when they all just need to pop out of sleep, read a variable to see if that particular worker is now the master, and then either go back to sleep or start working.
Solution #2: Create a wrapper for sync.WaitGroup that allows keeping track of the number of waiting threads so that I can call wg.Add(-numWaitingThreads) to wake them. This sounds like a headache to figure out how to code it without all sorts of race conditions.
Solution #3: Until someone comes up with a better idea, I'll be using a list of N channels and have the notifier non-blocking-send to all of the channels except its own. Is this really the best way?
More details: I give each worker some unique credits and have a central variable for "which credit is the next to be written to the output file". When a worker finishes its work for whichever credit ID it was working on, it needs to do the following:
for centralNextCreditID != creditID {
wait_for_centralNextCreditID_to_change()
}
saveWorkToFile()
centralNextCreditID++
wake_other_threads_waiting_for_centralNextCreditID_to_change()
To me it does seem like this is an appropriate use case for sync.Cond. You can use a *RWMutex.RLocker() for Cond.L so all goroutines can acquire the read lock simultaneously once the Cond.Broadcast() is sent.
Additionally, it may be worth making sure you hold a write lock when changing this "who's master" variable to avoid race conditions, which would make sync.Cond an even better fit.
sync.WaitGroup won't work because I don't know how many times to call wg.Done().
wg can be used in this case. Make a wg with count 1 and pass this to the N goroutines. Make them wg.Wait(), except the one that updates the variable.
The goroutine updating the variable calls wg.Done() after successful update thus resulting in N goroutines to come out of wait and start executing further.
The title says that you want to wake 0-N sleeping goroutines, but the body of the question indicates that you only need to wake the goroutine for the next id (if there is a goroutine waiting).
Here's how to implement the problem described in the body of the question:
// waiter sequences work according to an incrementing id.
type waiter struct {
mu sync.Mutex
id int
waiting map[int]chan struct{}
}
func NewWaiter(firstID int) *waiter {
return &waiter{id: firstID, waiting: make(map[int]chan struct{})}
}
// wait waits for id's turn in the sequence.
func (w *waiter) wait(id int) {
w.mu.Lock()
if w.id == id {
// This id is next. Nothing to do.
w.mu.Unlock()
return
}
// Wait for our turn.
c := make(chan struct{})
w.waiting[id] = c
w.mu.Unlock()
<-c
}
// done signals that the work for the previous id is done.
func (w *waiter) done() {
w.mu.Lock()
w.id++
c, ok := w.waiting[w.id]
if ok {
delete(w.waiting, w.id)
}
w.mu.Unlock()
if ok {
// close cause c to receive a zero value
close(c)
}
}
Here's how to use it:
for _, creditID := range creditIDs {
doWorkFor(creditID)
waiter.wait(creditID)
saveWorkToFile()
waiter.done()
}
WaitGroup is the best option. The reason is that is keeps its signalled state and you are safe from deadlock if the main thread signals too early.
If you use Cond there is a risk that the main thread calls cond.Broadcast BEFORE the worker thread calls cond.Wait(). Since Cond doesn't remember that it was signalled, the worker thread will wait for the event to happen.
Here is an example: https://go.dev/play/p/YLfvEGO2A18
The main thread broadcasts too early, the worker threads run into a deadlock.
Same case with con.WaitGroup: https://go.dev/play/p/R6_-ULo2eJ2
The main thread releases the wait group too early, but there is no deadlock.

Do goroutines keep running even if main function is terminated?

I am running a server where main fires off several go routines. like this:
main() {
go someFn(){
// will run for infinite time()
}
go otherFn()
}
I have two doubts:
what if the main function is exited? will these threads will still run or will terminate with the main function?
if no, then what is the best method to make the main function run forever/ or run till I need it? currently, I am using select{} command for making it run forever! is there any better and more efficient method available than select{}
I would recommend to read the language specification in its entirety — Go is one of a very small number of laguages whose language spec can really be read over two lunches (or a single one — if this is your, I dunno, third of fourth programming language).
To cite it:
Program execution begins by initializing the main package and then invoking the function main. When that function invocation returns, the program exits. It does not wait for other (non-main) goroutines to complete.
I would add that logically making main wait all non-main goroutines would be a bad thing for two reasons:
That would easily create situations where you could wait for a program to shut down for some indeterminate amount of time even if you do not want that (and you not always want that).
Logically, that would require inventing a "special" way to end main — something like abort(3) from C, and having such a way is a sure path for it to be abused.
Any sensible usage of goroutines where you actually need to wait on them anyway requires explicit synchronisation — just per the language specification and its memory model.
That is, when you want to wait on outstanding goroutines, you must do that explicitly.
The program exits when the function main() returns.
If one of someFn or otherFn runs forever, then call that function directly at the end of main. The main function never exits with this change.
If neither function runs forever, the use a sync.WaitGroup to wait on the goroutines and then exit.
The empty select statement select {} efficiently blocks a goroutine forever.
[Do] go routines keeps on running even if main function is terminated?
No.
This is asked once a week.
In my opinion, when it is difficult to remember a concept the best thing is to code such concept yourself.
Here is a simple program which demonstrates that go routines are terminated when main function exits:
package main
import (
"log"
"os"
"time"
)
func main() {
f, err := os.Create("./test.txt")
if err != nil {
log.Fatal("can not write to a file", err)
}
go func() {
for {
f.WriteString("new line created at: " + time.Now().String() + "\n")
time.Sleep(1 * time.Second)
}
}()
time.Sleep(5 * time.Second)
// writing to the file by goroutine will be finished in 5 seconds
// when main exits
}

Golang: can WaitGroup leak with go-routines

I am planning to implement a go-routine and have a sync.WaitGroup to synchronize end of a created go-routine. I create a thread essentially using go <function>. So it is something like:
main() {
var wg sync.WaitGroup
for <some condition> {
go myThread(wg)
wg.Add(1)
}
wg.wait()
}
myThread(wg sync.WaitGroup) {
defer wg.Done()
}
I have earlier worked with pthread_create which does fail to create a thread under some circumstances. With that context, is it possibly for the above go myThread(wg) to fail to start, and/or run wg.Done() if the rest of the routine behaves correctly? If so, what would be reported and how would the error be caught? My concern is a possible leak in wg due to wg.Add(1) following the thread creation. (Of course it may be possible to use wg.Add(1) within the function, but that leads to other races between the increment and the main program waiting).
I have read through numerous documentation of go-routines and there is no mention of a failure in scheduling or thread creation anywhere. What would be the case if I create billions of threads and exhaust bookkeeping space? Would the go-routine still work and threads still be created?
I don't know of any possible way for this to fail, and if it is possible, it would result in a panic (and therefor application crash). I have never seen it happen, and I'm aware of examples of applications running millions of goroutines. The only limiting factor is available memory to allocate the goroutine stack.
go foo() is not like pthread_create. Goroutines are lightweight green threads handled by the Go runtime, and scheduled to run on OS threads. Starting a goroutine does not start a new OS thread.
The problem with your code is not in starting a goroutine (which cannot "fail" per se) or that like but in the use of sync.WaitGroup. Your code has two major bugs:
You must do wg.Add(1) before launching the goroutine as otherwise the Done() could be executed before the Add(1).
You must not copy a sync.WaitGroup. Your code makes a copy while calling myThread().
Both issues are explained in the official documentation to sync.WaitGroup and the given example in https://golang.org/pkg/sync/#WaitGroup

Goroutines are cooperatively scheduled. Does that mean that goroutines that don't yield execution will cause goroutines to run one by one?

From: http://blog.nindalf.com/how-goroutines-work/
As the goroutines are scheduled cooperatively, a goroutine that loops continuously can starve other goroutines on the same thread.
Goroutines are cheap and do not cause the thread on which they are multiplexed to block if they are blocked on
network input
sleeping
channel operations or
blocking on primitives in the sync package.
So given the above, say that you have some code like this that does nothing but loop a random number of times and print the sum:
func sum(x int) {
sum := 0
for i := 0; i < x; i++ {
sum += i
}
fmt.Println(sum)
}
if you use goroutines like
go sum(100)
go sum(200)
go sum(300)
go sum(400)
will the goroutines run one by one if you only have one thread?
A compilation and tidying of all of creker's comments.
Preemptive means that kernel (runtime) allows threads to run for a specific amount of time and then yields execution to other threads without them doing or knowing anything. In OS kernels that's usually implemented using hardware interrupts. Process can't block entire OS. In cooperative multitasking thread have to explicitly yield execution to others. If it doesn't it could block whole process or even whole machine. That's how Go does it. It has some very specific points where goroutine can yield execution. But if goroutine just executes for {} then it will lock entire process.
However, the quote doesn't mention recent changes in the runtime. fmt.Println(sum) could cause other goroutines to be scheduled as newer runtimes will call scheduler on function calls.
If you don't have any function calls, just some math, then yes, goroutine will lock the thread until it exits or hits something that could yield execution to others. That's why for {} doesn't work in Go. Even worse, it will still lead to process hanging even if GOMAXPROCS > 1 because of how GC works, but in any case you shouldn't depend on that. It's good to understand that stuff but don't count on it. There is even a proposal to insert scheduler calls in loops like yours
The main thing that Go's runtime does is it gives its best to allow everyone to execute and don't starve anyone. How it does that is not specified in the language specification and might change in the future. If the proposal about loops will be implemented then even without function calls switching could occur. At the moment the only thing you should remember is that in some circumstances function calls could cause goroutine to yield execution.
To explain the switching in Akavall's answer, when fmt.Printf is called, the first thing it does is checks whether it needs to grow the stack and calls the scheduler. It MIGHT switch to another goroutine. Whether it will switch depends on the state of other goroutines and exact implementation of the scheduler. Like any scheduler, it probably checks whether there're starving goroutines that should be executed instead. With many iterations function call has greater chance to make a switch because others are starving longer. With few iterations goroutine finishes before starvation happens.
For what its worth it. I can produce a simple example where it is clear that the goroutines are not ran one by one:
package main
import (
"fmt"
"runtime"
)
func sum_up(name string, count_to int, print_every int, done chan bool) {
my_sum := 0
for i := 0; i < count_to; i++ {
if i % print_every == 0 {
fmt.Printf("%s working on: %d\n", name, i)
}
my_sum += 1
}
fmt.Printf("%s: %d\n", name, my_sum)
done <- true
}
func main() {
runtime.GOMAXPROCS(1)
done := make(chan bool)
const COUNT_TO = 10000000
const PRINT_EVERY = 1000000
go sum_up("Amy", COUNT_TO, PRINT_EVERY, done)
go sum_up("Brian", COUNT_TO, PRINT_EVERY, done)
<- done
<- done
}
Result:
....
Amy working on: 7000000
Brian working on: 8000000
Amy working on: 8000000
Amy working on: 9000000
Brian working on: 9000000
Brian: 10000000
Amy: 10000000
Also if I add a function that just does a forever loop, that will block the entire process.
func dumb() {
for {
}
}
This blocks at some random point:
go dumb()
go sum_up("Amy", COUNT_TO, PRINT_EVERY, done)
go sum_up("Brian", COUNT_TO, PRINT_EVERY, done)
Well, let's say runtime.GOMAXPROCS is 1. The goroutines run concurrently one at a time. Go's scheduler just gives the upper hand to one of the spawned goroutines for a certain time, then to another, etc until all are finished.
So, you never know which goroutine is running at a given time, that's why you need to synchronize your variables. From your example, it's unlikely that sum(100) will run fully, then sum(200) will run fully, etc
The most probable is that one goroutine will do some iterations, then another will do some, then another again etc.
So, the overall is that they are not sequential, even if there is only one goroutine active at a time (GOMAXPROCS=1).
So, what's the advantage of using goroutines ? Plenty. It means that you can just do an operation in a goroutine because it is not crucial and continue the main program. Imagine an HTTP webserver. Treating each request in a goroutine is convenient because you do not have to care about queueing them and run them sequentially: you let Go's scheduler do the job.
Plus, sometimes goroutines are inactive, because you called time.Sleep, or they are waiting for an event, like receiving something for a channel. Go can see this and just executes other goroutines while some are in those idle states.
I know there are a handful of advantages I didn't present, but I don't know concurrency that much to tell you about them.
EDIT:
Related to your example code, if you add each iteration at the end of a channel, run that on one processor and print the content of the channel, you'll see that there is no context switching between goroutines: Each one runs sequentially after another one is done.
However, it is not a general rule and is not specified in the language. So, you should not rely on these results for drawing general conclusions.
#Akavall Try adding sleep after creating dumb goroutine, goruntime never executes sum_up goroutines.
From that it looks like go runtime spawns next go routines immediately, it might execute sum_up goroutine until go runtime schedules dumb() goroutine to run. Once dumb() is scheduled to run then go runtime won't schedule sum_up goroutines to run, as dumb runs for{}

Should idle threads be left around in long running process?

I am creating a go program that is intended to run long term and listen for work. When it receives a request, it runs the work on a process queue.
I am new to golang and systems programming, so my question is this: should I spin up the process queue (with it's multiple idle worker threads) at the program launch (they will just sit there until work comes in) or should I spin them up when work arrives and shut them down when finished?
I am unclear as to the overall system impact multiple idle threads will have, but I am assuming since they are idle there will be no impact until work arrives. That being said, I want to make sure my program is a "good neighbor" and as efficient as possible.
--EDIT--
To clarify, the "process pool" is a group of worker go routines waiting for work on a channel. Should they be started/stopped when work arrives, or started when the program launches and left waiting until work comes in?
First of all you can't create a thread using standard Go library. In Go universe you should use goroutines which are so called green threads.
Usually you shouldn't spawn "reusable" goroutines. They are cheap to create so create them on demand as work job arrives and finish (return from goroutine) as soon as work is completed.
Also don't hesitate to create nested goroutines. In general spawn them like crazy if you feel you should do something in concurrent manner and don't try to reuse them as it makes no sense.
There is very little cost either way. goroutines don't require a separate OS thread and consume practically no resources while blocking on a channel receive, but also cost very little to spin up, so there's no great reason to leave them open either.
My code rarely uses worker pools. Generally my producer will spawn a goroutine for every unit of work it produces and hands it off directly along with a response channel, then spawns a "listener" that does some formatting for the work output and pipes all the responses back to the main thread. A common pattern for me looks like:
func Foo(input []interface{}) resp chan interface{} {
var wg sync.WaitGroup
resp := make(chan interface{})
listen := make(chan interface{})
theWork := makeWork(input)
// do work
for _, unitOfWork := range theWork {
wg.Add(1)
go func() {
// doWork has signature:
// func doWork(w interface{}, ch chan interface{})
doWork(unitOfWork, listen)
wg.Done()
}()
}
// format the output of listen chan and send to resp chan
// then close resp chan so main can continue
go func() {
for r := range listen {
resp <- doFormatting(r)
}
close(resp)
}()
// close listen chan after work is done
go func() {
wg.Wait()
close(listen)
}()
return resp
}
Then my main function passes it some input and listens on the response channel
func main() {
loremipsum := []string{"foo", "bar", "spam", "eggs"}
response := Foo(loremipsum)
for output := range response {
fmt.Println(output)
}
}
Pattern with tasks queue and waiting workers is common in Go. Goroutines are cheap, but order of execution is nondetermined. So if you want your system behavior to be predictable, you better would control workers rendezvous with main routine thru unbuffered channels requested in a loop or somehow else. Otherwise some of them can be spawned but remain idle which is legal.

Resources