Skip to content

Commit a7ed78b

Browse files
committed
[ADD] waitgroups, rate limiting, atomic counters
1 parent 03d5fb4 commit a7ed78b

7 files changed

+386
-1
lines changed

codes/atomic-counters.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"sync"
6+
"sync/atomic"
7+
)
8+
9+
func main() {
10+
11+
var ops atomic.Uint64
12+
13+
var wg sync.WaitGroup
14+
15+
for i := 0; i < 50; i++ {
16+
wg.Add(1)
17+
18+
go func() {
19+
for c := 0; c < 1000; c++ {
20+
21+
ops.Add(1)
22+
}
23+
24+
wg.Done()
25+
}()
26+
}
27+
28+
wg.Wait()
29+
30+
fmt.Println("ops:", ops.Load())
31+
}

codes/rate-limiting.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"time"
6+
)
7+
8+
func main() {
9+
10+
requests := make(chan int, 5)
11+
for i := 1; i <= 5; i++ {
12+
requests <- i
13+
}
14+
close(requests)
15+
16+
limiter := time.Tick(200 * time.Millisecond)
17+
18+
for req := range requests {
19+
<-limiter
20+
fmt.Println("request", req, time.Now())
21+
}
22+
23+
burstyLimiter := make(chan time.Time, 3)
24+
25+
for i := 0; i < 3; i++ {
26+
burstyLimiter <- time.Now()
27+
}
28+
29+
go func() {
30+
for t := range time.Tick(200 * time.Millisecond) {
31+
burstyLimiter <- t
32+
}
33+
}()
34+
35+
burstyRequests := make(chan int, 5)
36+
for i := 1; i <= 5; i++ {
37+
burstyRequests <- i
38+
}
39+
close(burstyRequests)
40+
for req := range burstyRequests {
41+
<-burstyLimiter
42+
fmt.Println("request", req, time.Now())
43+
}
44+
}

codes/waitgroups.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"sync"
6+
"time"
7+
)
8+
9+
func worker(id int) {
10+
fmt.Printf("Worker %d starting\n", id)
11+
12+
time.Sleep(time.Second)
13+
fmt.Printf("Worker %d done\n", id)
14+
}
15+
16+
func main() {
17+
18+
var wg sync.WaitGroup
19+
20+
for i := 1; i <= 5; i++ {
21+
wg.Add(1)
22+
23+
i := i
24+
25+
go func() {
26+
defer wg.Done()
27+
worker(i)
28+
}()
29+
}
30+
31+
wg.Wait()
32+
33+
}

documentation/38-wait-groups.md

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
This Go code demonstrates the use of the `sync.WaitGroup` to wait for a collection of goroutines to finish their execution. Let's go through it with inline comments:
2+
3+
```go
4+
package main
5+
6+
import (
7+
"fmt"
8+
"sync"
9+
"time"
10+
)
11+
12+
// worker function represents a worker that performs some work
13+
func worker(id int) {
14+
fmt.Printf("Worker %d starting\n", id)
15+
16+
// Simulate work by sleeping for one second
17+
time.Sleep(time.Second)
18+
19+
fmt.Printf("Worker %d done\n", id)
20+
}
21+
22+
func main() {
23+
// Creating a WaitGroup to wait for all goroutines to finish
24+
var wg sync.WaitGroup
25+
26+
for i := 1; i <= 5; i++ {
27+
// Incrementing the WaitGroup counter for each goroutine
28+
wg.Add(1)
29+
30+
// Creating a local variable 'i' to capture the current value
31+
i := i
32+
33+
// Launching a goroutine for each worker
34+
go func() {
35+
// Decrementing the WaitGroup counter when the goroutine completes
36+
defer wg.Done()
37+
worker(i)
38+
}()
39+
}
40+
41+
// Waiting for all goroutines to finish
42+
wg.Wait()
43+
}
44+
```
45+
### Output
46+
```
47+
Worker 2 starting
48+
Worker 3 starting
49+
Worker 5 starting
50+
Worker 4 starting
51+
Worker 1 starting
52+
Worker 1 done
53+
Worker 4 done
54+
Worker 5 done
55+
Worker 2 done
56+
Worker 3 done
57+
```
58+
59+
Explanation:
60+
61+
1. `package main`: Indicates that this Go file belongs to the main executable package.
62+
63+
2. `import (...)`: Imports necessary packages, including "fmt" for formatting and printing, "sync" for synchronization, and "time" for handling time-related operations.
64+
65+
3. `func worker(id int) { ... }`: Defines a worker function that simulates work by sleeping for one second and prints messages.
66+
67+
4. `var wg sync.WaitGroup`: Creates a `sync.WaitGroup` variable named 'wg' to wait for a collection of goroutines to finish.
68+
69+
5. `wg.Add(1)`: Increments the WaitGroup counter for each goroutine to indicate that a new goroutine is starting.
70+
71+
6. `i := i`: Creates a local variable 'i' to capture the current value of the loop variable, preventing its value from changing in the closure.
72+
73+
7. `go func() { ... }()`: Launches a goroutine for each worker. The `defer wg.Done()` statement is used to decrement the WaitGroup counter when the goroutine completes.
74+
75+
8. `wg.Wait()`: Waits for all goroutines to finish by blocking until the WaitGroup counter becomes zero.
76+
77+
This code demonstrates how to use a `sync.WaitGroup` to coordinate the execution of multiple goroutines. Each worker is launched as a goroutine, and the WaitGroup is used to wait for all workers to complete their work before proceeding further in the main function. The use of a local variable inside the loop ensures that each goroutine captures the correct value of 'i'.

documentation/39-rate-limiting.md

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
This Go code demonstrates the use of rate limiting with time. Tick and channels to control the frequency of requests.
2+
3+
Let's go through it with inline comments:
4+
5+
```go
6+
package main
7+
8+
import (
9+
"fmt"
10+
"time"
11+
)
12+
13+
func main() {
14+
// Creating a buffered channel 'requests' for sending requests
15+
requests := make(chan int, 5)
16+
17+
// Sending 5 requests to the 'requests' channel
18+
for i := 1; i <= 5; i++ {
19+
requests <- i
20+
}
21+
close(requests)
22+
23+
// Creating a rate limiter with a tick of 200 milliseconds
24+
limiter := time.Tick(200 * time.Millisecond)
25+
26+
// Processing requests with the rate limiter
27+
for req := range requests {
28+
<-limiter
29+
fmt.Println("request", req, time.Now())
30+
}
31+
32+
// Creating a buffered channel 'burstyLimiter' with a capacity of 3
33+
burstyLimiter := make(chan time.Time, 3)
34+
35+
// Initializing 'burstyLimiter' with three time values
36+
for i := 0; i < 3; i++ {
37+
burstyLimiter <- time.Now()
38+
}
39+
40+
// Launching a goroutine to refill 'burstyLimiter' every 200 milliseconds
41+
go func() {
42+
for t := range time.Tick(200 * time.Millisecond) {
43+
burstyLimiter <- t
44+
}
45+
}()
46+
47+
// Creating a buffered channel 'burstyRequests' for sending bursty requests
48+
burstyRequests := make(chan int, 5)
49+
50+
// Sending 5 bursty requests to 'burstyRequests' channel
51+
for i := 1; i <= 5; i++ {
52+
burstyRequests <- i
53+
}
54+
close(burstyRequests)
55+
56+
// Processing bursty requests with the bursty rate limiter
57+
for req := range burstyRequests {
58+
<-burstyLimiter
59+
fmt.Println("request", req, time.Now())
60+
}
61+
}
62+
```
63+
### Output
64+
```
65+
request 1 2024-01-23 19:12:37.8840836 +0530 IST m=+0.210007101
66+
request 2 2024-01-23 19:12:38.0886902 +0530 IST m=+0.414613701
67+
request 3 2024-01-23 19:12:38.2896166 +0530 IST m=+0.615540101
68+
request 4 2024-01-23 19:12:38.4904667 +0530 IST m=+0.816390201
69+
request 5 2024-01-23 19:12:38.6761374 +0530 IST m=+1.002060901
70+
request 1 2024-01-23 19:12:38.6763013 +0530 IST m=+1.002224801
71+
request 2 2024-01-23 19:12:38.6768092 +0530 IST m=+1.002732701
72+
request 3 2024-01-23 19:12:38.6768092 +0530 IST m=+1.002732701
73+
request 4 2024-01-23 19:12:38.8792461 +0530 IST m=+1.205169601
74+
request 5 2024-01-23 19:12:39.0807175 +0530 IST m=+1.406641001
75+
```
76+
Explanation:
77+
78+
1. `package main`: Indicates that this Go file belongs to the main executable package.
79+
80+
2. `import (...)`: Imports necessary packages, including "fmt" for formatting and printing, and "time" for handling time-related operations.
81+
82+
3. `requests := make(chan int, 5)`: Creates a buffered channel 'requests' for sending requests with a capacity of 5.
83+
84+
4. Sending 5 requests to the 'requests' channel, and then closing the channel to indicate that no more requests will be sent.
85+
86+
5. `limiter := time.Tick(200 * time.Millisecond)`: Creates a rate limiter using `time.Tick` with a tick interval of 200 milliseconds.
87+
88+
6. Processing requests with the rate limiter. Each request waits for the limiter to allow it before being processed.
89+
90+
7. Creating a buffered channel 'burstyLimiter' with a capacity of 3.
91+
92+
8. Initializing 'burstyLimiter' with three time values to allow for an initial burst of requests.
93+
94+
9. Launching a goroutine to refill 'burstyLimiter' every 200 milliseconds, creating a bursty rate limiter.
95+
96+
10. `burstyRequests := make(chan int, 5)`: Creates a buffered channel 'burstyRequests' for sending bursty requests with a capacity of 5.
97+
98+
11. Sending 5 bursty requests to 'burstyRequests' channel and then closing the channel.
99+
100+
12. Processing bursty requests with the bursty rate limiter. The initial burst is allowed due to the buffered channel 'burstyLimiter'.
101+
102+
In summary, this code demonstrates two scenarios of rate limiting using channels and `time.Tick`. The first scenario demonstrates regular rate limiting, while the second scenario introduces a bursty rate limiter allowing an initial burst of requests before settling into a regular rate.
103+
104+
### Further Explanation
105+
Rate limiting is a technique used to control the rate at which events, such as requests or operations, are allowed to occur. It helps prevent resource exhaustion, manage traffic, and maintain system stability. In the provided Go code, two examples of rate limiting are demonstrated: regular rate limiting and bursty rate limiting.
106+
107+
1. **Regular Rate Limiting:**
108+
- The first part of the code uses a simple rate limiter created with `time.Tick(200 * time.Millisecond)`. This creates a channel that ticks every 200 milliseconds. Each request is processed only when it receives a tick from this channel.
109+
- This ensures that requests are processed at a regular interval, limiting the rate at which they can occur. If a request arrives before the next tick, it will wait for the next tick to proceed.
110+
- This is useful to ensure a steady flow of requests and prevent a sudden surge that could overload the system.
111+
112+
2. **Bursty Rate Limiting:**
113+
- The second part of the code introduces a bursty rate limiter, allowing an initial burst of requests to be processed more rapidly.
114+
- The `burstyLimiter` channel is buffered with a capacity of 3, allowing three requests to be processed without waiting for the tick.
115+
- The channel is initially filled with three time values, allowing an immediate burst of requests.
116+
- A goroutine is launched to continuously refill the channel with time values every 200 milliseconds, creating a regular tick for subsequent requests.
117+
- This allows for a burst of requests initially, followed by a regular rate of requests, combining the benefits of burstiness and regular rate limiting.
118+
119+
In both scenarios, rate limiting is achieved by using channels and `time.Tick`. The rate limiter allows a controlled number of events to occur within a specified time interval, preventing a flood of requests and ensuring a more controlled and predictable behavior for the system. It's a common technique used in systems to prevent abuse, manage resources, and maintain overall stability.

documentation/40-atomic-conters.md

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
This Go code demonstrates the use of the `sync/atomic` package and the `atomic.Uint64` type to perform atomic operations on a `uint64` variable. Let's go through the code with inline comments:
2+
3+
```go
4+
package main
5+
6+
import (
7+
"fmt"
8+
"sync"
9+
"sync/atomic"
10+
)
11+
12+
func main() {
13+
// Creating an atomic.Uint64 variable named 'ops'
14+
var ops atomic.Uint64
15+
16+
// Creating a WaitGroup to wait for all goroutines to finish
17+
var wg sync.WaitGroup
18+
19+
// Launching 50 goroutines
20+
for i := 0; i < 50; i++ {
21+
// Incrementing the WaitGroup counter for each goroutine
22+
wg.Add(1)
23+
24+
// Launching a goroutine
25+
go func() {
26+
// Performing 1000 atomic increments on the 'ops' variable
27+
for c := 0; c < 1000; c++ {
28+
ops.Add(1)
29+
}
30+
31+
// Decrementing the WaitGroup counter when the goroutine completes
32+
wg.Done()
33+
}()
34+
}
35+
36+
// Waiting for all goroutines to finish
37+
wg.Wait()
38+
39+
// Loading the final value of 'ops' using ops.Load()
40+
fmt.Println("ops:", ops.Load())
41+
}
42+
```
43+
44+
Explanation:
45+
46+
1. `package main`: Indicates that this Go file belongs to the main executable package.
47+
48+
2. `import (...)`: Imports necessary packages, including "fmt" for formatting and printing, "sync" for synchronization, and "sync/atomic" for atomic operations.
49+
50+
3. `var ops atomic.Uint64`: Creates an atomic `uint64` variable named 'ops' using `atomic.Uint64`.
51+
52+
4. `var wg sync.WaitGroup`: Creates a `sync.WaitGroup` variable named 'wg' to wait for all goroutines to finish.
53+
54+
5. Launching 50 goroutines, each performing 1000 atomic increments on the 'ops' variable.
55+
56+
6. `ops.Add(1)`: Atomically increments the value of 'ops' by 1.
57+
58+
7. `wg.Add(1)`: Increments the WaitGroup counter for each goroutine.
59+
60+
8. `wg.Done()`: Decrements the WaitGroup counter when a goroutine completes.
61+
62+
9. `wg.Wait()`: Waits for all goroutines to finish.
63+
64+
10. `ops.Load()`: Loads the final value of 'ops' atomically.
65+
66+
11. Printing the final value of 'ops'.
67+
68+
In summary, this code demonstrates the use of the `sync/atomic` package to perform atomic operations on a `uint64` variable (`ops`). The `atomic.Uint64` type provides atomic methods for performing operations like increments without the need for locks, ensuring safe concurrent access to the variable. The final value of 'ops' represents the total number of increments performed across all goroutines.
69+
70+
### Further Explanation
71+
72+
In concurrent programming, the term "atomic" refers to an operation that is executed as a single, indivisible unit, without the possibility of interruption or interference by other concurrent operations. In the context of the Go programming language and its `sync/atomic` package, "atomic" operations are designed to be thread-safe and avoid race conditions.
73+
74+
In simpler terms, when an operation is atomic, it is guaranteed to be completed without being interrupted by other parallel operations. This is particularly crucial in concurrent or multithreaded programs where multiple threads or goroutines may be accessing shared data simultaneously. Without atomicity, there is a risk of data corruption or unexpected behavior due to interference between concurrent operations.
75+
76+
The `sync/atomic` package in Go provides atomic types and functions for performing atomic operations on variables. For example, `atomic.AddUint64` increments a `uint64` variable in an atomic manner, ensuring that the operation is completed without interruption and without the need for explicit locks.
77+
78+
In the provided code example, `atomic.Uint64` is used to create an atomic `uint64` variable named 'ops'. The `Add(1)` operation on 'ops' is atomic, meaning that each increment is guaranteed to complete as a single, uninterrupted operation, even when performed concurrently by multiple goroutines. This helps prevent data corruption and ensures the accuracy of the final result when multiple goroutines are involved in incrementing the variable concurrently.

0 commit comments

Comments
 (0)