Chapter: Goroutines and Channels
Chapter Overview
We will learn this chapter differently: very little theory, very small Go programs, and lots of MCQs.
Assume you know absolutely nothing about goroutines, channels, or even the symbols used in the code. We will first learn what each tiny piece means, and then use questions to make the idea stick.
By the end, you should be able to look at code such as go work(), ch <- 10, x := <-ch, and select and understand what is happening.
1. First: What Problem Are We Trying to Solve?
Imagine Two Jobs
Forget programming for a moment.
Imagine you have two jobs:
- Job A: download a file.
- Job B: calculate something.
If you do A first and completely wait for it, and only then do B, you are making B wait unnecessarily.
In programming, we sometimes want different pieces of work to make progress independently.
Go gives us a simple way to start such independent work. It is called a goroutine.
For this chapter, you can initially think:
goroutine = "let this work run separately"
Quick MCQ 1
Question
Suppose a program has two independent jobs. What is one reason we might want them to run independently?
A. So one job can make progress while the other is waiting
B. So the program contains more code
C. So variables disappear
D. So Go stops executing functions
Answer: A
Explanation: Independent work does not always need to wait for other work to finish.
Quick MCQ 2
Question
In this chapter, the easiest beginner mental model for a goroutine is:
A. A database
B. A separate piece of work that Go can run concurrently
C. A variable
D. A programming language
Answer: B
Explanation: A goroutine lets a function or piece of work execute concurrently with other work.
2. What Exactly Is a Goroutine?
Suppose we have this function:
func sayHello() {
fmt.Println("Hello")
}
Do not worry about every word yet. The important part is that sayHello() is a function call.
If we write:
sayHello()
we are saying:
"Run this function now."
But if we write:
go sayHello()
we are saying:
"Start this function as a goroutine."
The important difference
| Code | Simple meaning |
|---|---|
sayHello() |
Run the function normally. |
go sayHello() |
Start the function as a goroutine. |
Understand the code piece by piece
go sayHello()
go= Go keyword telling the program to start a goroutine.sayHello= name of the function.()= call the function.
So you can mentally read:
go + function call = start that function as a goroutine.
MCQ 3
Question
What does go sayHello() mean?
A. Delete sayHello
B. Start sayHello as a goroutine
C. Create a variable called go
D. Stop the program
Answer: B
Explanation: The keyword go tells Go to start the function call as a goroutine.
MCQ 4
Question
Which part of go sayHello() tells Go to use a goroutine?
A. go
B. sayHello
C. ()
D. Nothing
Answer: A
Explanation: go is the special keyword. sayHello is simply the function being called.
MCQ 5
Question
What is the main difference between these two?
work()
go work()
A. Both always mean exactly the same thing
B. The second starts work as a goroutine
C. The first creates a goroutine and the second deletes it
D. Neither calls work
Answer: B
3. A Very Important Surprise: main Can Finish First
Look at this:
func main() {
go sayHello()
fmt.Println("Main finished")
}
You might think:
"Surely sayHello() will print first."
No.
Starting a goroutine does not mean the current code waits for it.
The program can reach the end of main() before the goroutine gets a chance to print.
Think of it like this
You tell an employee:
"Please do this task."
Then you immediately close the entire office.
The employee was asked to work, but the office did not remain open long enough.
Similarly, when main() finishes, the Go program finishes.
MCQ 6
Question
What happens when the main function returns?
A. The program continues forever because goroutines exist
B. The program ends
C. All goroutines automatically become independent programs
D. Every channel closes automatically
Answer: B
MCQ 7
Question
Is this code guaranteed to print Hello?
func main() {
go fmt.Println("Hello")
}
A. Yes, always
B. No, because main may finish before the goroutine runs
C. Yes, because go waits
D. No, because fmt.Println cannot run in a goroutine
Answer: B
Important lesson
Starting a goroutine and waiting for a goroutine are two different things.
4. Before Channels: How Can Two Goroutines Share Information?
Suppose one goroutine produces a number:
42
and another goroutine needs that number.
We need some way to pass the number from one piece of work to another.
Go gives us channels.
For a beginner, think of a channel as:
A tube through which goroutines can pass values to each other.
For example:
Worker A ----> channel ----> Worker B
Worker A can put a value into the channel.
Worker B can take that value out.
MCQ 8
Question
What is the easiest mental model for a Go channel?
A. A communication tube for values between goroutines
B. A function that prints text
C. A database table
D. A CPU core
Answer: A
MCQ 9
Question
Why might one goroutine need a channel?
A. To communicate a value to another goroutine
B. To change the Go language
C. To create a computer
D. To rename a function
Answer: A
5. Creating Your First Channel
Here is our first channel:
ch := make(chan int)
We will break every part apart.
| Part | Meaning |
|---|---|
ch |
The name we chose for the channel. |
:= |
Create a new variable and give it a value. |
make |
Creates certain Go data structures, including channels. |
chan |
Says we are creating a channel. |
int |
This channel will carry integer numbers. |
So:
ch := make(chan int)
means:
"Create a channel named ch that carries integer numbers."
Why specify int?
Because channels are typed.
This:
make(chan int)
is for integers.
This:
make(chan string)
is for strings.
MCQ 10
Question
What type of values can ch carry?
ch := make(chan int)
A. Only strings
B. Only integers
C. Only functions
D. Any value without restrictions
Answer: B
MCQ 11
Question
Which creates a channel for strings?
A. make(string)
B. make(chan string)
C. chan(string)
D. new(string)
Answer: B
6. Putting a Value INTO a Channel
Suppose we have:
ch := make(chan int)
How do we put 10 into it?
ch <- 10
This strange-looking symbol is extremely important.
Read it as:
"Send 10 into ch."
Notice the direction:
ch <- 10
The value 10 is going into the channel.
MCQ 12
Question
What does this mean?
ch <- 50
A. Receive 50 from ch
B. Send 50 into ch
C. Delete 50
D. Close ch
Answer: B
MCQ 13
Question
In ch <- 25, which is the value being sent?
A. ch
B. 25
C. <-
D. Nothing
Answer: B
7. Taking a Value OUT of a Channel
Now suppose another goroutine wants to receive the value.
We write:
x := <-ch
Read it as:
"Take a value from ch and put it into x."
| Code | Meaning |
|---|---|
ch <- 10 |
Send 10 into ch. |
x := <-ch |
Receive a value from ch into x. |
The complete picture
Sender
|
| ch <- 42
↓
[ CHANNEL ]
|
| x := <-ch
↓
Receiver
MCQ 14
Question
What does x := <-ch do?
A. Sends x into ch
B. Receives a value from ch and stores it in x
C. Deletes ch
D. Creates a goroutine
Answer: B
MCQ 15
Question
Which one sends a value?
A. x := <-ch
B. ch <- 10
C. close(ch)
D. make(chan int)
Answer: B
MCQ 16
Question
Which one receives a value?
A. ch <- 10
B. x := <-ch
C. close(ch)
D. make(chan int)
Answer: B
8. Your First Complete Goroutine + Channel Program
Now combine everything.
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
ch <- 42
}()
value := <-ch
fmt.Println(value)
}
Do not memorize it. Read it like a story.
ch := make(chan int)creates a channel for integers.go func() { ... }()starts a goroutine.- Inside the goroutine,
ch <- 42sends 42 into the channel. value := <-chreceives that 42.fmt.Println(value)prints it.
Output:
42
What does func() { ... }() mean?
This is an anonymous function: a function without a name.
You can think of:
func() {
ch <- 42
}
as:
"Here is a small piece of work."
And:
go func() {
ch <- 42
}()
means:
"Run this small piece of work as a goroutine."
MCQ 17
Question
What value does the goroutine send?
go func() {
ch <- 42
}()
A. 0
B. 1
C. 42
D. ch
Answer: C
MCQ 18
Question
What does value := <-ch receive in the program above?
A. 42
B. The channel itself
C. Nothing
D. The function
Answer: A
MCQ 19
Question
What is the final output?
ch := make(chan int)
go func() {
ch <- 100
}()
x := <-ch
fmt.Println(x)
A. 0
B. 1
C. 100
D. It must print a random number
Answer: C
Explanation: The goroutine sends 100, and the main goroutine receives that value.
9. The Most Important Idea: Blocking
Now we need to understand one behavior that explains many channel questions.
With an ordinary function call, you generally know the function executes and returns.
Channels can wait.
For example:
x := <-ch
If there is currently no value available to receive, the goroutine waits.
Similarly, with an unbuffered channel:
ch <- 10
the sender waits until another goroutine can receive the value.
Think about a real door
Imagine you want to hand a package to another person.
- If the person is there, you hand it over.
- If nobody is there, you wait.
An unbuffered channel behaves somewhat like this direct handoff.
MCQ 20
Question
Suppose ch is an empty channel and no goroutine will ever send anything into it. What happens here?
x := <-ch
A. x becomes 10
B. The receive waits forever
C. The channel automatically creates a value
D. The program prints the channel
Answer: B
MCQ 21
Question
For an unbuffered channel, what happens if a goroutine sends a value but there is no receiver ready?
A. The value is stored forever
B. The send waits
C. The value becomes zero
D. The channel closes
Answer: B
10. Unbuffered Channel: Understand It With a Picture
This:
ch := make(chan int)
creates an unbuffered channel.
Think:
Sender ---- hand the value directly ----> Receiver
There is no waiting room inside the channel.
If the sender arrives first:
Sender
|
| "I have 10"
↓
WAITING
|
| Receiver arrives
↓
Transfer happens
If the receiver arrives first:
Receiver
|
| "I need a value"
↓
WAITING
|
| Sender arrives
↓
Transfer happens
MCQ 22
Question
An unbuffered channel is best imagined as:
A. A huge storage box
B. A direct handoff between sender and receiver
C. An unlimited queue
D. A database
Answer: B
11. Buffered Channels: A Small Waiting Room
Now look at:
ch := make(chan int, 3)
The 3 means:
The channel can hold up to three values in its buffer.
Think of it as a small waiting room.
Sender
↓
[ 10 ][ 20 ][ 30 ]
CHANNEL
↓
Receiver
The sender can put values into the available spaces without requiring a receiver immediately.
Example
ch := make(chan int, 2)
ch <- 10
ch <- 20
fmt.Println(<-ch)
fmt.Println(<-ch)
Output:
10
20
Why?
The channel has two spaces.
First:
[10][ ]
Second:
[10][20]
Then receiving removes 10:
[20][ ]
Then receiving removes 20.
MCQ 23
Question
What does the 2 mean here?
make(chan int, 2)
A. The channel can hold two buffered values
B. The channel can hold only the number 2
C. Two goroutines are automatically created
D. Two channels are created
Answer: A
MCQ 24
Question
What happens here?
ch := make(chan int, 2)
ch <- 10
ch <- 20
A. Both values can enter the buffer
B. The first send always deadlocks
C. The channel closes
D. The second value replaces the first
Answer: A
MCQ 25
Question
What happens when a buffered channel is full and another value is sent?
A. The old value is automatically deleted
B. The sender waits until space becomes available
C. The channel doubles its size automatically
D. The value becomes zero
Answer: B
12. Unbuffered vs Buffered: The Question You Must Master
| Question | Unbuffered | Buffered |
|---|---|---|
| Example | make(chan int) |
make(chan int, 3) |
| Has storage space? | No | Yes, limited |
| Can sender proceed without receiver? | No | Yes, if buffer has space |
| What happens when buffer is full? | Not applicable | Sender waits |
MCQ 26
Question
Which channel can hold values temporarily?
A. Only an unbuffered channel
B. A buffered channel
C. A nil channel
D. A closed channel only
Answer: B
MCQ 27
Question
Which statement is correct?
A. Every channel is automatically buffered
B. make(chan int) has capacity 1
C. make(chan int, 5) has buffer capacity 5
D. Buffered channels have unlimited capacity
Answer: C
13. Channel Values Behave Like a Queue
Suppose:
ch := make(chan int, 3)
ch <- 10
ch <- 20
ch <- 30
Now receive three times:
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
Output:
10
20
30
The values come out in the same order in which they were sent.
Think of a line of people:
10 → 20 → 30
first person out = 10
second person out = 20
third person out = 30
MCQ 28
Question
What is the output?
ch := make(chan int, 3)
ch <- 5
ch <- 8
ch <- 12
fmt.Println(<-ch)
A. 12
B. 8
C. 5
D. Random number
Answer: C
MCQ 29
Question
If the values are sent in this order:
1
2
3
4
what does the first receive normally get?
A. 4
B. 3
C. 1
D. Random value
Answer: C
14. Closing a Channel: "I Am Finished Sending"
Imagine a worker is sending numbers:
10
20
30
How can it tell the receiver:
"I am finished. I will not send anything else."
It can close the channel:
close(ch)
Important:
Closing a channel does not mean deleting it.
It means:
"No more values will be sent."
Example
ch := make(chan int, 2)
ch <- 10
ch <- 20
close(ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
The values 10 and 20 are still available.
MCQ 30
Question
What does close(ch) primarily tell receivers?
A. Delete ch
B. No more values will be sent
C. Empty all values
D. Start a goroutine
Answer: B
MCQ 31
Question
Suppose:
ch := make(chan int, 2)
ch <- 10
close(ch)
Can 10 still be received?
A. Yes
B. No
C. Only if the channel is reopened
D. Only after another value is sent
Answer: A
MCQ 32
Question
What happens if you send to a channel after closing it?
A. The value is ignored safely
B. The channel reopens
C. The program panics
D. The value waits forever
Answer: C
15. How Can We Know Whether a Channel Is Closed?
Go gives us this useful form:
value, ok := <-ch
There are two pieces of information:
value= the received value.ok= whether a value was successfully received from an open channel.
If the channel is closed and there are no values left:
ok == false
Example
ch := make(chan int, 1)
ch <- 99
close(ch)
value, ok := <-ch
fmt.Println(value)
fmt.Println(ok)
Output:
99
true
Why is ok true?
Because 99 was successfully received.
After that:
value, ok := <-ch
produces:
0 false
because the channel is closed and empty.
MCQ 33
Question
What does ok == false tell us in the usual channel receive pattern?
A. A value was successfully received
B. The channel is closed and has no more values to receive
C. The channel is buffered
D. A goroutine was created
Answer: B
MCQ 34
Question
After a channel is closed and all buffered values have been consumed, what does receiving an int produce?
A. An arbitrary integer
B. The integer zero, with ok == false
C. A panic every time
D. The last sent value forever
Answer: B
16. range: "Keep Taking Values"
Suppose a worker produces several numbers.
Instead of repeatedly writing:
x := <-ch
x := <-ch
x := <-ch
we can write:
for value := range ch {
fmt.Println(value)
}
This means:
"Keep receiving values from ch and run this code for each value."
When the channel is closed and all remaining values have been consumed, the loop ends.
Example
ch := make(chan int, 3)
ch <- 10
ch <- 20
ch <- 30
close(ch)
for value := range ch {
fmt.Println(value)
}
Output:
10
20
30
MCQ 35
Question
What does this generally mean?
for value := range ch {
fmt.Println(value)
}
A. Keep receiving values from ch
B. Send values into ch
C. Close ch repeatedly
D. Create a new channel each time
Answer: A
MCQ 36
Question
What normally allows a channel range loop to finish after all values are processed?
A. The channel being closed
B. The channel being renamed
C. The program creating another variable
D. A random number
Answer: A
17. Who Should Usually Close the Channel?
This is a very useful rule.
Imagine:
Producer → Channel → Consumer
The producer knows when it has finished sending.
Therefore, the producer is normally the natural place to close the channel.
func producer(ch chan<- int) {
ch <- 10
ch <- 20
close(ch)
}
The consumer can then do:
for value := range ch {
fmt.Println(value)
}
Why shouldn't the consumer casually close it?
The producer may still try to send something.
Sending to a closed channel causes a panic.
MCQ 37
Question
Which goroutine usually knows best when an output channel should be closed?
A. The producer that knows it has finished sending
B. A random consumer
C. The operating system
D. The CPU
Answer: A
18. Send-Only and Receive-Only Channels
Suppose we have a producer.
The producer only needs to send.
We can tell Go that explicitly:
func producer(ch chan<- int) {
ch <- 10
}
The symbol:
chan<- int
means:
send-only channel of integers.
For receiving only:
func consumer(ch <-chan int) {
value := <-ch
fmt.Println(value)
}
<-chan int means:
receive-only channel of integers.
Do not confuse these
| Syntax | Meaning |
|---|---|
chan int |
Can send and receive. |
chan<- int |
Send only. |
<-chan int |
Receive only. |
MCQ 38
Question
What does chan<- int mean?
A. Receive-only integer channel
B. Send-only integer channel
C. Two integer channels
D. Closed integer channel
Answer: B
MCQ 39
Question
What does <-chan string mean?
A. Send-only string channel
B. Receive-only string channel
C. A string variable
D. A closed channel
Answer: B
19. select: Waiting for Different Possibilities
Imagine you are waiting for one of two things:
- Message A arrives.
- Message B arrives.
You do not want to check only A and ignore B.
Go gives us select.
select {
case x := <-ch1:
fmt.Println(x)
case y := <-ch2:
fmt.Println(y)
}
For now, understand it as:
"Wait until one of these channel operations can happen."
Simple picture
┌── ch1 ready? → do case 1
select ──────┤
└── ch2 ready? → do case 2
Why is this useful?
It is useful when a program is waiting for different possible events.
For example:
- A server response.
- A timeout.
- A cancellation signal.
MCQ 40
Question
What is the simplest purpose of select?
A. Wait for one of multiple channel operations
B. Create a channel
C. Close every channel
D. Create a function
Answer: A
MCQ 41
Question
Suppose both channel cases in a select are ready. Which one is guaranteed to run first?
A. The first case in the source code
B. The second case in the source code
C. Go selects among the ready cases; you should not depend on source order
D. Both always execute
Answer: C
20. select + default
Consider:
select {
case value := <-ch:
fmt.Println(value)
default:
fmt.Println("Nothing available")
}
If the receive is ready, the receive case can run.
If the receive is not ready, default runs immediately.
So the beginner mental model is:
"Try the channel operation. If it cannot happen right now, do something else."
MCQ 42
Question
If no channel case is ready and a default case exists, what happens?
A. The program waits forever
B. The default case runs
C. The channel closes
D. A goroutine is automatically created
Answer: B
21. Timeouts: "Don't Wait Forever"
Imagine your program asks another server for information.
You send:
"Please give me the result."
But the other server never responds.
You do not want your program to wait forever.
Go can combine a channel with select and a timer.
select {
case result := <-resultCh:
fmt.Println(result)
case <-time.After(2 * time.Second):
fmt.Println("Timed out")
}
Read it as:
- If the result arrives first → use it.
- If two seconds pass first → handle the timeout.
Understand 2 * time.Second
2= two.time.Second= one second duration.2 * time.Second= two seconds.
MCQ 43
Question
Why might a timeout be used when communicating with an external service?
A. To allow the program to wait forever
B. To prevent waiting indefinitely
C. To close the computer
D. To create more CPUs
Answer: B
MCQ 44
Question
In this code, what happens if resultCh does not produce a value within two seconds?
select {
case result := <-resultCh:
fmt.Println(result)
case <-time.After(2 * time.Second):
fmt.Println("Timed out")
}
A. The timeout case can run
B. The result becomes 2
C. The program automatically retries forever
D. The channel becomes buffered
Answer: A
22. nil Channel: A Channel That Was Never Created
Look at:
var ch chan int
We declared a channel variable, but we did not create an actual channel.
Its value is nil.
To create one, we need:
ch := make(chan int)
This difference is important.
| Code | Meaning |
|---|---|
var ch chan int |
ch is nil. |
ch := make(chan int) |
ch refers to an actual channel. |
Communication with a nil channel blocks forever.
MCQ 45
Question
What is the problem here?
var ch chan int
ch <- 10
A. ch is nil, so the send blocks forever
B. ch automatically stores 10
C. ch is buffered with capacity 1
D. The send closes the channel
Answer: A
MCQ 46
Question
Which line actually creates the channel?
A. var ch chan int
B. ch := make(chan int)
C. ch = nil
D. chan int
Answer: B
23. Deadlock: When Everyone Is Waiting
This is one of the most important concepts for understanding channel MCQs.
Look at:
func main() {
ch := make(chan int)
ch <- 10
fmt.Println(<-ch)
}
A beginner might think:
"First put 10 into the channel. Then take 10 out."
But remember: this is an unbuffered channel.
The send needs a receiver.
But the receiver is written after the send.
Therefore:
ch <- 10
↓
"I need a receiver!"
↓
No receiver is running
↓
WAIT
The program never reaches:
<-ch
This situation is a deadlock.
MCQ 47
Question
Why can this code deadlock?
ch := make(chan int)
ch <- 10
fmt.Println(<-ch)
A. Because 10 is an invalid integer
B. Because an unbuffered send needs a receiver, but no receiver is ready
C. Because channels cannot contain integers
D. Because fmt cannot print integers
Answer: B
MCQ 48
Question
Which change can allow this basic communication to work?
A. Start the receiver in another goroutine before sending
B. Delete the channel
C. Close the channel before sending
D. Replace 10 with a string
Answer: A
24. The Same Code, But Correctly Coordinated
Now:
func main() {
ch := make(chan int)
go func() {
ch <- 10
}()
fmt.Println(<-ch)
}
Now the sender is running in a separate goroutine.
The main goroutine is receiving.
So communication can happen.
Think:
Goroutine 1 Main
| |
| ---- send 10 ---------->|
| |
| receive
MCQ 49
Question
Why does this version work conceptually?
go func() {
ch <- 10
}()
fmt.Println(<-ch)
A. The channel became buffered automatically
B. One goroutine can send while the main goroutine receives
C. The channel contains infinite values
D. close was called
Answer: B
25. WaitGroup: "Tell Me When Everyone Is Finished"
Sometimes we do not need to send a result between goroutines.
We simply want to say:
"Start these workers, and don't continue until all of them are finished."
Go provides sync.WaitGroup for this kind of coordination.
Example:
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
fmt.Println("Worker 1")
}()
go func() {
defer wg.Done()
fmt.Println("Worker 2")
}()
wg.Wait()
fmt.Println("Finished")
Understand the three important commands
| Code | Simple meaning |
|---|---|
wg.Add(2) |
We are waiting for two pieces of work. |
wg.Done() |
This worker has finished. |
wg.Wait() |
Wait until all registered work finishes. |
Channel vs WaitGroup
Remember this distinction:
Channel → "Give me information."
WaitGroup → "Tell me when everyone is done."
MCQ 50
Question
What does wg.Wait() primarily do?
A. Send a value through a channel
B. Wait for registered goroutines to finish
C. Create a goroutine
D. Close a channel
Answer: B
26. Worker Pool: Many Jobs, Limited Workers
Imagine 10,000 jobs.
You could potentially create many goroutines, but sometimes you want to control how many pieces of work happen concurrently.
A worker pool is a common pattern.
Imagine:
1000 jobs
↓
[ Job Queue ]
↓
Worker 1
Worker 2
Worker 3
Worker 4
Worker 5
The workers repeatedly take jobs from the queue.
Tiny worker example
func worker(jobs <-chan int, results chan<- int) {
for job := range jobs {
results <- job * 2
}
}
Understand the code
jobs= channel from which workers receive jobs.<-chan int= jobs can only be received here.job= one job received from the channel.job * 2= pretend processing.results <- ...= send the result to another channel.
MCQ 51
Question
What is a worker pool mainly useful for?
A. Controlling how many workers process jobs concurrently
B. Removing all concurrency
C. Converting integers to strings
D. Closing every channel
Answer: A
27. Fan-Out and Fan-In Without the Scary Names
These names sound complicated, but the ideas are simple.
Fan-out
One stream of jobs is distributed to multiple workers.
→ Worker 1
Jobs → Queue → Worker 2
→ Worker 3
That is fan-out.
Fan-in
Multiple sources send results into one place.
Worker 1 ──\
Worker 2 ───→ Results
Worker 3 ──/
That is fan-in.
MCQ 52
Question
One jobs channel feeds three workers. What is this pattern called?
A. Fan-in
B. Fan-out
C. Deadlock
D. Buffer overflow
Answer: B
MCQ 53
Question
Three workers send their results into one results channel. What pattern does this describe?
A. Fan-in
B. Fan-out
C. Nil channel
D. Goroutine leak
Answer: A
28. Pipeline: Work in Stages
Imagine a factory.
Raw material
↓
Cutting
↓
Painting
↓
Packaging
Each stage does one job.
Programming can use a similar idea:
Input
↓
Stage 1
↓
Channel
↓
Stage 2
↓
Channel
↓
Stage 3
Each stage can be connected using channels.
Small example
func square(in <-chan int, out chan<- int) {
for n := range in {
out <- n * n
}
close(out)
}
Read it as:
"Take numbers from the input channel, square them, put the answers into the output channel, and close the output when finished."
MCQ 54
Question
What is the basic idea of a pipeline?
A. Different stages process data and pass it to the next stage
B. Every function must be written in one line
C. All channels must be closed immediately
D. No goroutines can be used
Answer: A
29. Goroutine Leak: "The Worker That Never Goes Home"
Imagine you hire a worker and tell them:
"Wait here until someone gives you a job."
But nobody ever gives them a job.
They stay there forever.
A goroutine can have the same problem.
go func() {
value := <-ch
fmt.Println(value)
}()
If nobody ever sends to ch, this goroutine can remain blocked.
This is commonly called a goroutine leak.
Important question whenever you write go
"How will this goroutine eventually finish?"
MCQ 55
Question
What can happen if a goroutine waits forever for a channel value that will never arrive?
A. Goroutine leak
B. Automatic garbage collection of the goroutine immediately
C. The channel becomes buffered
D. The goroutine becomes a function
Answer: A
30. Context: Telling Work to Stop
Suppose a web request starts some work.
Then the user closes the browser.
It may no longer make sense for every piece of work related to that request to continue.
Go commonly uses context.Context for cancellation and deadlines.
You will often see:
select {
case <-ctx.Done():
return
case value := <-work:
fmt.Println(value)
}
For now, the important idea is:
ctx.Done() can tell a goroutine that the operation should stop.
MCQ 56
Question
What is one major use of context.Context in concurrent Go programs?
A. Cancellation and deadlines
B. Creating integer variables
C. Printing text
D. Replacing all functions
Answer: A
Chapter-Wide Quick Reference
The 10 Things You Absolutely Must Understand
go work()startsworkas a goroutine.- A goroutine is a piece of work that can run concurrently.
make(chan int)creates an unbuffered channel carrying integers.make(chan int, 3)creates a buffered channel with capacity 3.ch <- 10means "send 10 into ch."x := <-chmeans "receive a value from ch into x."close(ch)means "no more values will be sent."for x := range chrepeatedly receives values until the channel is closed and drained.selectwaits for one of several channel operations.- Whenever you start a goroutine, understand how it will communicate and how it will eventually stop.
Syntax Table
| Code | Read it as |
|---|---|
go f() |
Run f as a goroutine. |
make(chan int) |
Create an unbuffered integer channel. |
make(chan int, 3) |
Create an integer channel with capacity 3. |
ch <- 5 |
Send 5 into ch. |
x := <-ch |
Receive from ch into x. |
close(ch) |
No more sending. |
x, ok := <-ch |
Receive x and check whether the receive succeeded. |
for x := range ch |
Keep receiving from ch. |
chan<- int |
Send-only integer channel. |
<-chan int |
Receive-only integer channel. |
select |
Wait for one of several channel operations. |
Channel Behavior
| Situation | What happens? |
|---|---|
| Receive from empty unbuffered channel | Waits. |
| Send to unbuffered channel with no receiver | Waits. |
| Send to buffered channel with free space | Value enters buffer. |
| Send to full buffered channel | Waits. |
| Receive from closed channel with remaining values | Remaining values can still be received. |
| Receive from closed and empty channel | Returns zero value and ok == false. |
| Send to closed channel | Panic. |
| Close already closed channel | Panic. |
| Send to nil channel | Blocks forever. |
| Receive from nil channel | Blocks forever. |
Common Confusions
| Concept A | Concept B | Difference |
|---|---|---|
ch <- 10 |
<-ch |
First sends; second receives. |
make(chan int) |
make(chan int, 3) |
First is unbuffered; second has capacity 3. |
go work() |
work() |
First starts a goroutine; second calls the function normally. |
| Close channel | Delete channel | Closing means no more sends; it does not mean immediate deletion. |
| Closed channel | Nil channel | A closed channel can be received from; a nil channel blocks on communication. |
| Channel | WaitGroup | Channel communicates values; WaitGroup waits for goroutines to finish. |
| Fan-out | Fan-in | Fan-out distributes work; fan-in combines streams. |
| Deadlock | Goroutine leak | Deadlock means required operations cannot make progress; a leak means an unwanted goroutine remains alive or blocked. |
Final Mastery Test
Challenge 1 — Predict the Output
func main() {
ch := make(chan int, 2)
ch <- 10
ch <- 20
fmt.Println(<-ch)
fmt.Println(<-ch)
}
Answer:
10
20
Why? The channel has capacity 2, so both values fit. They are received in the same order they were sent.
Challenge 2 — Find the Problem
func main() {
ch := make(chan int)
ch <- 50
fmt.Println(<-ch)
}
Answer: Deadlock.
Why? The channel is unbuffered. The send needs another goroutine to receive the value, but the receive occurs later in the same blocked goroutine.
Challenge 3 — Fix the Problem
func main() {
ch := make(chan int)
go func() {
ch <- 50
}()
fmt.Println(<-ch)
}
Answer: This can successfully print 50.
Why? The sender and receiver can now run as separate goroutines of execution.
Challenge 4 — Buffered or Unbuffered?
Question: Which of these allows the following two sends to proceed without an immediate receiver?
ch := make(chan int, 2)
ch <- 10
ch <- 20
A. Yes
B. No
Answer: A
Why? The buffer has two available spaces.
Challenge 5 — Closing and Receiving
ch := make(chan int, 1)
ch <- 99
close(ch)
x, ok := <-ch
fmt.Println(x, ok)
Answer:
99 true
Why? Closing does not throw away the buffered 99. It can still be received successfully.
Challenge 6 — After the Channel Is Empty
Now execute another receive:
x, ok := <-ch
fmt.Println(x, ok)
Answer:
0 false
Why? The channel is closed and contains no remaining values. The zero value for int is 0, and ok is false.
Challenge 7 — range
ch := make(chan int, 3)
ch <- 2
ch <- 4
ch <- 6
close(ch)
for x := range ch {
fmt.Println(x)
}
Answer:
2
4
6
Why? The loop receives each buffered value and finishes after the closed channel has been drained.
Challenge 8 — Direction
What does this function parameter mean?
func send(ch chan<- int) {
ch <- 10
}
Answer: ch is a send-only integer channel inside the function.
Why? The arrow points toward the channel type: chan<- int.
Challenge 9 — select
select {
case x := <-ch:
fmt.Println(x)
default:
fmt.Println("No value")
}
Question: If no value is currently available and no sender is ready, what happens?
Answer: No value is printed.
Why? The default case is selected when no other case is ready.
Challenge 10 — The Big Picture
Question: Which sequence correctly describes the following program?
ch := make(chan int)
go func() {
ch <- 123
}()
x := <-ch
fmt.Println(x)
A. Create channel → start goroutine → send 123 → receive 123 → print 123
B. Create channel → close channel → print 0
C. Create channel → receive first → send later → always deadlock
D. Create buffered channel → send two values → print both
Answer: A
Explanation: This is the fundamental goroutine-and-channel pattern: one piece of work produces a value, a channel carries it, and another piece of work receives it.
One-Minute Chapter Revision
Start here:
go work()
means:
"Run work as a goroutine."
Then:
ch := make(chan int)
means:
"Create an integer communication channel."
Send:
ch <- 10
means:
"Put 10 into the channel."
Receive:
x := <-ch
means:
"Take a value out of the channel and put it into x."
Buffered:
make(chan int, 3)
means:
"Create an integer channel with room for three waiting values."
Close:
close(ch)
means:
"I am finished sending."
Range:
for x := range ch
means:
"Keep taking values until the channel is closed and finished."
Multiple choices:
select {
case x := <-ch1:
case y := <-ch2:
}
means:
"Wait for whichever communication is ready."
And the most important mental model:
GOROUTINE
|
| sends value
↓
CHANNEL
|
| receives value
↓
ANOTHER GOROUTINE
If you understand that picture, you understand the heart of Goroutines and Channels.
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.