Table of contents
Yesterday, I watched a video from Steve Francia and he showed a Prime Sieve implementation written in Go.
Algorithm
The prime sieve is an algorithm to generate prime numbers and, if I understand correctly, is something like this.
You have a Generator that creates sequential integers starting from 2 (the first prime).
Generator |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
As it generates the numbers, you check if it is a prime and creates a Filter filter out the multiples of that prime
Generator | Filter 2 |
2 -> | 2 |
3 | 3 |
4 | - |
5 | 5 |
6 | - |
7 | 7 |
8 | - |
9 | 9 |
10 | - |
11 | 11 |
12 | - |
13 | 13 |
The next one is 3, which passes the Filter 2 which is a prime, then you create another filter and pass the number ahead:
Generator | Filter 2 | Filter 3 |
2 | [2] | |
3 -> | 3 -> | [3] |
4 | ||
5 | ||
6 | ||
7 | ||
8 | ||
9 | ||
10 | ||
11 | ||
12 | ||
13 |
And the same goes on and on:
Generator | Filter 2 | Filter 3 |
2 | [2] | |
3 | 3 | [3] |
4 -> | - | |
5 | ||
6 | ||
7 | ||
8 | ||
9 | ||
10 | ||
11 | ||
12 | ||
13 |
Generator | Filter 2 | Filter 3 | Filter 5 |
2 | [2] | ||
3 | 3 | [3] | |
4 | - | ||
5 -> | 5 -> | 5 -> | [5] |
6 | |||
7 | |||
8 | |||
9 | |||
10 | |||
11 | |||
12 | |||
13 |
Generator | Filter 2 | Filter 3 | Filter 5 |
2 | [2] | ||
3 | 3 | [3] | |
4 | - | ||
5 | 5 | 5 | [5] |
6 -> | - | ||
7 | |||
8 | |||
9 | |||
10 | |||
11 | |||
12 | |||
13 |
Generator | Filter 2 | Filter 3 | Filter 5 | Filter 7 |
2 | [2] | |||
3 | 3 | [3] | ||
4 | - | |||
5 | 5 | 5 | [5] | |
6 | - | |||
7 -> | 7 -> | 7 -> | 7 -> | [7] |
8 | ||||
9 | ||||
10 | ||||
11 | ||||
12 | ||||
13 |
Generator | Filter 2 | Filter 3 | Filter 5 | Filter 7 |
2 | [2] | |||
3 | 3 | [3] | ||
4 | - | |||
5 | 5 | 5 | [5] | |
6 | - | |||
7 | 7 | 7 | 7 | [7] |
8 -> | - | |||
9 | ||||
10 | ||||
11 | ||||
12 | ||||
13 |
Generator | Filter 2 | Filter 3 | Filter 5 | Filter 7 |
2 | [2] | |||
3 | 3 | [3] | ||
4 | - | |||
5 | 5 | 5 | [5] | |
6 | - | |||
7 | 7 | 7 | 7 | [7] |
8 | - | |||
9 -> | 9 -> | - | ||
10 | ||||
11 | ||||
12 | ||||
13 |
Generator | Filter 2 | Filter 3 | Filter 5 | Filter 7 |
2 | [2] | |||
3 | 3 | [3] | ||
4 | - | |||
5 | 5 | 5 | [5] | |
6 | - | |||
7 | 7 | 7 | 7 | [7] |
8 | - | |||
9 | 9 | - | ||
10 -> | - | |||
11 | ||||
12 | ||||
13 |
Generator | Filter 2 | Filter 3 | Filter 5 | Filter 7 | Filter 11 |
2 | [2] | ||||
3 | 3 | [3] | |||
4 | - | ||||
5 | 5 | 5 | [5] | ||
6 | - | ||||
7 | 7 | 7 | 7 | [7] | |
8 | - | ||||
9 | 9 | - | |||
10 | - | ||||
11 -> | 11 -> | 11 -> | 11 -> | 11 -> | [11] |
12 | |||||
12 | |||||
13 | |||||
13 |
Generator | Filter 2 | Filter 3 | Filter 5 | Filter 7 | Filter 11 | Filter 11 |
2 | [2] | |||||
3 | 3 | [3] | ||||
4 | - | |||||
5 | 5 | 5 | [5] | |||
6 | - | |||||
7 | 7 | 7 | 7 | [7] | ||
8 | - | |||||
9 | 9 | - | ||||
10 | - | |||||
11 | 11 | 11 | 11 | 11 | [11] | |
12 -> | - | |||||
13 -> | 13 -> | 13 -> | 13 -> | 13 -> | 13 -> | [13] |
Implementation
That algorithm can be implemented using goroutines and channels in Go.
Each filter is a goroutine that receives a number through a channel.
If the number is not a multiple and the next filter is not created, then I have a prime. So I printed it and created another filter.
Here is the code presented in the video:
package main
func Generate(ch chan int) {
for i := 2; ; i++ {
ch <- i
}
}
func Filter(src chan<- int, dst <-chan int, prime int) {
for i := range src {
if i%prime != 0 {
dst <- i
}
}
}
func main() {
src := make(chan int)
go Generate(src)
for i := 0; i < 100; i++ {
prime := <-src
println(prime)
dst := make(chan int)
go Filter(src, dst, prime)
src = dst
}
}
To understand how the code works in Go, we need to understand:
Goroutines
Unbuffered channels
Call by value/pointers functions
The main concept of the algorithm is explained in the video. But as an early golang developer I didn’t understand what was going on on the main for loop.
So I am going to try to exaplain it here.
Main for loop
The
Generate goroutine
is running but it stops when it puts a number on an unbuffered channel (ch) [lines 19, 5]The for makes the first loop (number 2) received by the
Generate
(now the channel clears andGenerate
puts another number) [lines 21, 5]Print it [line 22]
Creates communication with
src
anddst
channels and runs another goroutine forFilter
2 [line 23, 24]Change pointer from
src
todst
[line 25]
Here is the interesting part. If you change src
to dst
it loses reference to the previous src
, BUT when you called the Filter
function, it already stored a context with the previous src
.
So the -> between Generate
and Filter 2
(in the previous section) is stored and can be used. Will look something like this:
Generator | - ——— | - 2 | 3 ->
| 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
When it runs 3 and 4 will look like this:
- Print [2]
Generator | - |
2 | [2] |
3 -> | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 |
- Creates channel and filter
Generator | Filter 2 |
2 | [2] |
3 -> | -> |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 |
- src = dst
Generator | Filter 2 |
2 | [2] |
3 -> | 3 -> |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 |
Wrapping up
This is a pretty cool algorithm using Concurrency from Golang. It took me time to understand but it helped me to learn a lot about the details of the language.
I hope it can help you.
The code can be found here https://github.com/lucaskatayama/go-prime-sieve