Simple, Simon and Sage with Gophers.
By Gemini 5 Oct, 2025.
Chat about Goroutines
by: burt rosenberg
at: university of miami
date: october 2025
Overview
This is the result of a conversation I had with ChatGPT, in response to the class presentation about the concurrency examples given in this
github repository.
Class discussion indicated that,
-
A little bit more time was needed to introduce the Go programming language.
All is not so obvious.
-
The nature of goroutines and channels should be built up carefully, before
applying it to the case of accumulate.
I hope this dialogue addresses these issues.
Cast of characters
Simon
The knowledgeable programmer. Confident and clear, Simon explains concepts
in an abstract manner.
Sage
The thoughtful and curious observer. Skeptical at times and not afraid to challenge Simon, Sage voices questions and frustrations that many learners feel.
Simple
The beginner student. Curious, eager to learn, and often reacts to explanations, making the dialogue accessible for readers who are new to computer science.
Introduction to Go
Simple:In class an example was giving of concurrency when
programing the Go programming language. But I don't know Go.
Sage: Yes Simple, I also do not know Go. Maybe
a simpler example go program with explanations will help.
Simon: Oh OK. Let us look at
this example Go program. Simple, what do you think this program does?
Simple:
Well I am pretty sure func introduces a function, like def does in Python. And I am pretty sure that Go uses curly braces like C to make blocks of code.
The braces after func probably enclose the function body. And maybe like
C language, the func main is the starting point to run the code.
Simon: Sage, what do you see in this program?
Sage:
Well it looks like Go does not declare types as C does. The (s string) after
func f means that Go has a string type, and declaration of variables have type
name follow the variable name. But I see both = and := that look like
assignment?
Simon:
Yes the
short variable declaration statement of the form
variable := value
both assigns
the value and defines the variable, inferring the type of the variable from the type
of the value.
Simple:
Well if there is a short variable declaration, is there a not-short one?
Simon:
Yes, there are many not-short forms, beginning with the keyword
var and using
the simple equals sign. Here is a simple example
var i, j, k int
Sage:
And so once initialized, the variable value is updated with the equals sign like in C?
Simon:
Mostly. Once declared with
:=, all updates use
=.
But the gramatical details are different.
In Go there is an
assignment statement,
ExpressionList [ add_op | mul_op ] = ExpressionList
for instance,
=,
+= or
<<=. In C, equals is an operator, it
can go anywhere in an expression.
Sage:
The for keyword looks like it is working as a while in C?
Simon:
In Go the
for statement is defined as,
for [ Condition | ForClause | RangeClause ] Block
The version in this program uses
Condition. The
ForClause version
would look like the C
initialization; condition; increment pattern, except
no parenthesis. Or the ForClause can be empty and it is a do-forever loop.
Goroutines
Simple:
What does this line do,
go f(hello[i])
Simon:
Oh that creates and starts a
goroutine.
In Go, a
goroutine is a lightweight thread
of execution managed by the Go runtime.
It is concurrent, not necessarily parallel.
Multiple goroutines can run independently,
and the Go scheduler multiplexes them onto OS threads.
Sage:
Is there a reason Go created goroutines, rather than using a library like pthreads?
Simon:
Several reasons,
- A goroutine is lightweight: thousands or even millions can exist in the same program, unlike OS threads which are heavier.
- Scheduler-managed: The Go runtime schedules goroutines onto OS threads.
- The thread stack for a goroutine grows dynamically: Goroutines start with small stacks (about 2KB) and grow/shrink as needed.
Simple:
Well what about all that concurrency stuff I learned, like mutex's and monitors
and PV-semaphores.
Simon:
Well I am not saying that all of that stuff goes out the window, but Go follows
another school of thought about concurrency.
Goroutines synchronize primarily via channels, a typed conduit for messages between goroutines.
Also, goroutines have an immediate, non-blocking start: the
go statement continues
immediately; execution of the function runs concurrently.
Sage:
So now that explains the output of
hello.go.
* % make hello
* go run hello.go
* ollhe
* % make hello
* go run hello.go
* ohlel
A bunch of goroutines are started, each with a letter to print. They are concurrent
so at one point or another they print the letter. But there is no synchronization
between the prints.
The Rendez-Vous
Simple:
Will this explain the
accumulate.go program shown in class?
Simon:
Sure it can, but let us look at another program, for another synchronization
requirement first. Let us think about the
rendez-vous.
Suppose Alice and Bob have tasks A1 and A2 for Alice, and B1 and B2 for Bob.
Alice will do A1 then A2, and Bob will do B1 and B2. As Alice and Bob are by
nature concurrent, we have not ordering of either A1 or A2 to either of B1 or B2.
Suppose in addition to wanting A1 to come before A2, we also demand A1 comes before
B2. Likewise, we demand B1 come before A2. See the diagram.
We can assure this ordering if we create a meet-up point in time between Alice
and Bob, an even R we call a Rendez-Vous event. If we can create such a synchronization
primitive then we can solve he problem of time ordering as demanded.
A1 R A2
| | |
A: ----+-----+--+--------------
|
|
B: --------+-+-----+-----
| | |
B1 R B2
Sage:
Let me see if I have this correct. For Alice, events A1 and and A2 are arranged in
time as A1 < A2. Likeswise for Bob, B1 < B2. Is it a good idea to use <
to express these orderings?
Simon:
We can try it. And we can assume that in a thread, every event has a necessary
time ordering, determined by the sequential nature of the code.
Simple:
This is beginning to get heavy. So A1 and A2 are events, like lines in a program?
Alice:
// some code
A1: x = 2
// more more code
A2: y = 3
And A1 < A2 because in the program of Alice, the execution of line A1 must occur
before the execution of line A2? Those are
events, things that happen and we
want to order them in time?
Simon:
Yes.
Sage:
And we also want A1 < B2, and B1 < A1. But it is not so clear how to
make that happen, since A and B are concurrent threads, and no such time
order relationships are naturally required between threads.
Simon:
Right. So we dream up a rendez-vous event R which is shared by Alice and Bob.
For alice,
A1 < R < A2
and for Bob,
B1 < R < B2
Simple:
Let me try the math. So A1 < R, in Alice's thread. And R < B2 in Bob's
thread. But R is the same event, so,
A1 < R < B2
And so we have A1 < B2. Likewise B1 < A2.
Simon:
That's the plan. A good plan?
Sage:
Good plan. How do we do it.
Simon:
Channels. In particular an
unbuffered channel. In Go, an
unbuffered channel
for type integers is created like this,
ch := make(chan int)
The channel has two operations. Receive and send.
The receive operator receives a value from the channel. If there is no value
in the channel, the operator blocks until this is one. Then the value of expression
with the operator is the received value.
i := 0 // an integer. to match the channel type
// some code
i = <- ch
The
send statement sends a value of the correct type into the channel, if there is room in the channel.
ch <- 1
An unbuffered channel never has room. Therefore, sender and receiver
will rendez-vous on the event "transfer of a value".
Simple:
So the event that above we called R is when an integer value is passed from
the Alice goroutine to the Bob goroutine?
Simon:
Yes.
nd in the code this R is at
The send statement in the Alice's goroutine, and the receive operator in Bob's
goroutine are the event R. The threads will align their flow at that event.
Sage:
So this explains the rendez-vous program pretty well. There are two globals
that need to be set. Each of two goroutines sets one. And there they each
print the values.
No print should happen until both routines have advanced down their code enough
that the globals have been set to the correct values. So they rendez-vous on
the event that they have set their global, and then continue after the rendez-vous
to the event the printing of the globals.
Simon:
That is a good guess that,
var a_g int = 0
var b_g int = 0
were globals. As in C they are defined in file scope. But those
words do not really apply in Go. They are called
package scope.
Sage:
Nice to know. I once won in Jeopardy with Computer Nerd for $500 with "What is variable hoisting."
Exercise: Mutex
Simon:
You should try to create a mutex out of a channel. The accumulate solution is not
really a mutex. Rather it passes a token back and forth to the main thread. It is
safe for mutual exclusion; it is live and mostly fair. But a mutex would be self-contained,
would not need an entire thread just to be keeper of the token.
Simple:
We get a hint?
Simon:
Of course. Think of a PV-semaphore initialized to one. How is that like a
buffered channel with capacity 1,
ch := make(chan int, 1)