Skip to content

Commit c72963f

Browse files
committed
add
1 parent e548992 commit c72963f

22 files changed

+5336
-32
lines changed

content/courses/concurrent-programming-with-go.mdx

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -91,24 +91,24 @@ Before starting this course, you should have:
9191

9292
This course is divided into several modules to help you learn concurrent programming in Go step by step:
9393

94-
1. [Introduction to Concurrency](/courses/concurrent-programming-with-go-modules/01-introduction-to-concurrency) - Understand concurrency vs parallelism and Go's approach.
95-
2. [Channels Basics](/courses/concurrent-programming-with-go-modules/02-channels-basics) - Learn about Go's primary communication mechanism.
96-
3. [Bidirectional Channels](/courses/concurrent-programming-with-go-modules/03-bidirectional-channels) - See how channels can send and receive data.
97-
4. [Directional Channels](/courses/concurrent-programming-with-go-modules/04-directional-channels) - Learn about send-only and receive-only channels.
98-
5. [Buffered Channels](/courses/concurrent-programming-with-go-modules/05-buffered-channels) - Work with channels that can store multiple values.
99-
6. [Creating Goroutines](/courses/concurrent-programming-with-go-modules/06-creating-goroutines) - Launch lightweight threads in Go.
100-
7. [Working with WaitGroups](/courses/concurrent-programming-with-go-modules/07-working-with-waitgroups) - Coordinate multiple goroutines.
101-
8. [Testing with WaitGroups](/courses/concurrent-programming-with-go-modules/08-testing-with-waitgroups) - Write tests for concurrent code.
102-
9. [Race Conditions](/courses/concurrent-programming-with-go-modules/09-race-conditions) - Identify and understand race conditions.
103-
10. [Using Mutex](/courses/concurrent-programming-with-go-modules/10-using-mutex) - Protect shared resources from concurrent access.
104-
11. [The Producer-Consumer Problem](/courses/concurrent-programming-with-go-modules/11-producer-consumer-problem) - Solve this classic concurrency challenge.
105-
12. [Range over Channels](/courses/concurrent-programming-with-go-modules/12-range-over-channels) - Iterate over channel values elegantly.
106-
13. [Unbuffered Channels](/courses/concurrent-programming-with-go-modules/13-unbuffered-channels) - Understand synchronization with unbuffered channels.
107-
14. [Buffered vs Unbuffered Channels](/courses/concurrent-programming-with-go-modules/14-buffered-vs-unbuffered) - Compare the two channel types.
108-
15. [Channel Direction](/courses/concurrent-programming-with-go-modules/15-channel-direction) - Enforce data flow direction.
109-
16. [Channel Ownership](/courses/concurrent-programming-with-go-modules/16-channel-ownership) - Establish clear ownership patterns.
110-
17. [Pipeline Pattern](/courses/concurrent-programming-with-go-modules/17-pipeline-pattern) - Process data through a series of stages.
111-
18. [Fan-Out Fan-In Pattern](/courses/concurrent-programming-with-go-modules/18-fan-out-fan-in) - Distribute and collect work among goroutines.
112-
19. [Cancellation with Context Package](/courses/concurrent-programming-with-go-modules/19-context-package) - Manage goroutine lifecycles and cancellation.
94+
1. [Introduction to Concurrency](/courses/concurrent-programming-with-go/introduction-to-concurrency) - Understand concurrency vs parallelism and Go's approach.
95+
2. [Channels Basics](/courses/concurrent-programming-with-go/channels-basics) - Learn about Go's primary communication mechanism.
96+
3. [Bidirectional Channels](/courses/concurrent-programming-with-go/bidirectional-channels) - See how channels can send and receive data.
97+
4. [Directional Channels](/courses/concurrent-programming-with-go/directional-channels) - Learn about send-only and receive-only channels.
98+
5. [Buffered Channels](/courses/concurrent-programming-with-go/buffered-channels) - Work with channels that can store multiple values.
99+
6. [Creating Goroutines](/courses/concurrent-programming-with-go/creating-goroutines) - Launch lightweight threads in Go.
100+
7. [Working with WaitGroups](/courses/concurrent-programming-with-go/working-with-waitgroups) - Coordinate multiple goroutines.
101+
8. [Testing with WaitGroups](/courses/concurrent-programming-with-go/testing-with-waitgroups) - Write tests for concurrent code.
102+
9. [Race Conditions](/courses/concurrent-programming-with-go/race-conditions) - Identify and understand race conditions.
103+
10. [Using Mutex](/courses/concurrent-programming-with-go/using-mutex) - Protect shared resources from concurrent access.
104+
11. [The Producer-Consumer Problem](/courses/concurrent-programming-with-go/producer-consumer-problem) - Solve this classic concurrency challenge.
105+
12. [Range over Channels](/courses/concurrent-programming-with-go/range-over-channels) - Iterate over channel values elegantly.
106+
13. [Unbuffered Channels](/courses/concurrent-programming-with-go/unbuffered-channels) - Understand synchronization with unbuffered channels.
107+
14. [Buffered vs Unbuffered Channels](/courses/concurrent-programming-with-go/buffered-vs-unbuffered) - Compare the two channel types.
108+
15. [Channel Direction](/courses/concurrent-programming-with-go/channel-direction) - Enforce data flow direction.
109+
16. [Channel Ownership](/courses/concurrent-programming-with-go/channel-ownership) - Establish clear ownership patterns.
110+
17. [Pipeline Pattern](/courses/concurrent-programming-with-go/pipeline-pattern) - Process data through a series of stages.
111+
18. [Fan-Out Fan-In Pattern](/courses/concurrent-programming-with-go/fan-out-fan-in) - Distribute and collect work among goroutines.
112+
19. [Cancellation with Context Package](/courses/concurrent-programming-with-go/context-package) - Manage goroutine lifecycles and cancellation.
113113

114114
Let's explore how Go's concurrency model makes it easy to write concurrent programs that are both efficient and easy to reason about!
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
---
2+
title: 'Bidirectional Channels'
3+
slug: 'bidirectional-channels'
4+
---
5+
6+
# Bidirectional Channels
7+
8+
By default, channels in Go are bidirectional, which means they can be used to both send and receive values. This is the simplest form of a channel and is what you get when you create a channel without any direction-specific notation.
9+
10+
## Creating a Bidirectional Channel
11+
12+
Creating a bidirectional channel is straightforward:
13+
14+
```go
15+
// Create a bidirectional channel for string values
16+
messages := make(chan string)
17+
```
18+
19+
## Using a Bidirectional Channel
20+
21+
Let's look at a simple example that demonstrates sending and receiving on a bidirectional channel:
22+
23+
```go
24+
package main
25+
26+
import "fmt"
27+
28+
func main() {
29+
messages := make(chan string)
30+
31+
// Send a value into the channel in a separate goroutine
32+
go func() {
33+
messages <- "ping"
34+
}()
35+
36+
// Receive the value from the channel
37+
msg := <-messages
38+
fmt.Println(msg)
39+
}
40+
```
41+
42+
In this example:
43+
1. We create a bidirectional channel called `messages`
44+
2. We launch a goroutine that sends the string "ping" into the channel
45+
3. The main goroutine receives the value from the channel
46+
4. The received value is printed
47+
48+
## Benefits and Limitations
49+
50+
**Benefits:**
51+
- Simple to use and understand
52+
- Flexible - can be used for both sending and receiving
53+
- No need to specify direction when creating the channel
54+
55+
**Limitations:**
56+
- Anyone can read and write to the channel
57+
- This can cause problems in concurrent environments where you want to restrict operations
58+
- No type safety to prevent incorrect usage
59+
60+
## When to Use Bidirectional Channels
61+
62+
Bidirectional channels are most appropriate when:
63+
64+
1. You have a simple communication pattern
65+
2. The same goroutine might need to both send and receive
66+
3. You're prototyping and don't need strict access control
67+
68+
## Common Patterns with Bidirectional Channels
69+
70+
### Ping-Pong Pattern
71+
72+
```go
73+
package main
74+
75+
import (
76+
"fmt"
77+
"time"
78+
)
79+
80+
func ping(ch chan string) {
81+
ch <- "ping"
82+
}
83+
84+
func pong(ch chan string) {
85+
msg := <-ch
86+
fmt.Println(msg)
87+
ch <- "pong"
88+
}
89+
90+
func main() {
91+
ch := make(chan string)
92+
93+
go ping(ch)
94+
go pong(ch)
95+
96+
fmt.Println(<-ch)
97+
time.Sleep(100 * time.Millisecond) // Ensure the goroutines have time to execute
98+
}
99+
```
100+
101+
### Basic Worker Pattern
102+
103+
```go
104+
package main
105+
106+
import "fmt"
107+
108+
func worker(jobs chan int, results chan int) {
109+
for job := range jobs {
110+
results <- job * 2 // Double the job value
111+
}
112+
}
113+
114+
func main() {
115+
jobs := make(chan int, 5)
116+
results := make(chan int, 5)
117+
118+
// Start a worker
119+
go worker(jobs, results)
120+
121+
// Send jobs
122+
for i := 1; i <= 3; i++ {
123+
jobs <- i
124+
}
125+
close(jobs)
126+
127+
// Collect results
128+
for i := 1; i <= 3; i++ {
129+
fmt.Println(<-results)
130+
}
131+
}
132+
```
133+
134+
## Summary
135+
136+
- Bidirectional channels can be used for both sending and receiving
137+
- They're created with `make(chan Type)`
138+
- They're the default channel type in Go
139+
- They offer flexibility but less type safety than directional channels
140+
- They're great for simple communication patterns
141+
142+
In the next lab, we'll look at directional channels, which add type safety by restricting operations to either sending or receiving.
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
---
2+
title: 'Buffered Channels'
3+
slug: 'buffered-channels'
4+
---
5+
6+
# Buffered Channels
7+
8+
By default, channels in Go are unbuffered, meaning they will only accept sends (`chan <-`) if there is a corresponding receiver (`<-chan`) ready to receive the value. Buffered channels, on the other hand, can hold a limited number of values without a corresponding receiver.
9+
10+
## Creating a Buffered Channel
11+
12+
To create a buffered channel, you specify the buffer size as the second argument to `make`:
13+
14+
```go
15+
// Create a buffered channel with capacity of 2
16+
messages := make(chan string, 2)
17+
```
18+
19+
## How Buffered Channels Work
20+
21+
Buffered channels work like queues:
22+
23+
1. When you send a value, it's added to the end of the queue
24+
2. When you receive a value, it's taken from the front of the queue
25+
3. If the queue is full, sends will block
26+
4. If the queue is empty, receives will block
27+
28+
## Example of a Buffered Channel
29+
30+
Here's a simple example demonstrating buffered channels:
31+
32+
```go
33+
package main
34+
35+
import "fmt"
36+
37+
func main() {
38+
// Create a buffered channel with a capacity of 2
39+
messages := make(chan string, 2)
40+
41+
// Send two values into the channel without a receiver
42+
messages <- "buffered"
43+
messages <- "channel"
44+
45+
// Receive the two values
46+
fmt.Println(<-messages) // Output: buffered
47+
fmt.Println(<-messages) // Output: channel
48+
}
49+
```
50+
51+
Notice how in this example:
52+
- We can send two values into the channel without any goroutine ready to receive
53+
- The sends don't block because the buffer isn't full yet
54+
- If we tried to send a third value without first receiving any, the send would block
55+
56+
## When to Use Buffered Channels
57+
58+
Buffered channels are useful in several scenarios:
59+
60+
1. **Reducing goroutine blocking**: When the sender doesn't need to wait for the receiver to be ready
61+
2. **Bursty workloads**: When the rate of sends and receives varies over time
62+
3. **Batching operations**: When you want to process items in batches rather than one at a time
63+
4. **Producer/consumer patterns**: When producers might temporarily produce faster than consumers can consume
64+
65+
## Choosing a Buffer Size
66+
67+
Selecting an appropriate buffer size depends on your specific use case:
68+
69+
- **Too small**: May cause unnecessary blocking and reduce concurrency
70+
- **Too large**: May hide synchronization bugs and consume too much memory
71+
- **Just right**: Provides enough capacity to smooth out temporary rate differences
72+
73+
As a general rule, use the smallest buffer size that prevents unnecessary blocking.
74+
75+
## Example: Worker Pool with Buffered Channels
76+
77+
Here's a more practical example using buffered channels to implement a worker pool:
78+
79+
```go
80+
package main
81+
82+
import (
83+
"fmt"
84+
"time"
85+
)
86+
87+
func worker(id int, jobs <-chan int, results chan<- int) {
88+
for job := range jobs {
89+
fmt.Printf("Worker %d processing job %d\n", id, job)
90+
time.Sleep(time.Second) // Simulate work
91+
results <- job * 2
92+
}
93+
}
94+
95+
func main() {
96+
// Create buffered channels
97+
jobs := make(chan int, 100)
98+
results := make(chan int, 100)
99+
100+
// Start workers
101+
for w := 1; w <= 3; w++ {
102+
go worker(w, jobs, results)
103+
}
104+
105+
// Send jobs
106+
for j := 1; j <= 9; j++ {
107+
jobs <- j
108+
}
109+
close(jobs)
110+
111+
// Collect results
112+
for a := 1; a <= 9; a++ {
113+
<-results
114+
}
115+
}
116+
```
117+
118+
In this example:
119+
- The buffered `jobs` channel allows us to queue up jobs without waiting for workers
120+
- The buffered `results` channel allows workers to report results without waiting for the main goroutine
121+
122+
## Channel Blocking Behavior with Buffers
123+
124+
Understanding when buffered channels block is crucial:
125+
126+
1. **Send operation** (`ch <- value`):
127+
- Blocks when the buffer is full
128+
- Doesn't block when the buffer has space
129+
130+
2. **Receive operation** (`<-ch`):
131+
- Blocks when the buffer is empty
132+
- Doesn't block when the buffer has at least one value
133+
134+
## Deadlocks with Buffered Channels
135+
136+
Even with buffered channels, deadlocks can still occur. Common causes include:
137+
138+
- Buffer too small for the number of sends
139+
- Forgetting to close channels
140+
- Circular dependencies between goroutines
141+
142+
## Performance Considerations
143+
144+
Some performance characteristics to keep in mind:
145+
146+
- Unbuffered channels are slightly faster than buffered channels for synchronization
147+
- Larger buffer sizes don't necessarily mean better performance
148+
- The optimal buffer size depends on your specific workload patterns
149+
150+
## Summary
151+
152+
- Buffered channels have a capacity that allows them to hold multiple values
153+
- They're created with `make(chan Type, capacity)`
154+
- Sends only block when the buffer is full
155+
- Receives only block when the buffer is empty
156+
- They're useful for decoupling senders and receivers
157+
- Choose buffer sizes carefully based on your specific use case
158+
159+
In the next lab, we'll explore how to create and work with goroutines, Go's lightweight threads for concurrent execution.

0 commit comments

Comments
 (0)