I’ve used C a little and mostly for scientific computing, but I’ve recently learned about some of the details of the Go programming language. It just…is everything I didn’t know I wanted from C (especially for strings). Now, don’t get me wrong, C has it’s place and I’m not running from Julia to Go for scientific computing.
Because I have to go to work, I’ve used Claude to summarize the key components of Go:
- Static typing with type inference
- Compiled to native code with fast startup time
- Built-in concurrency with goroutines and channels
- Single binary deployment with no external dependencies
- Garbage collection with low latency
- Strong standard library for systems programming
- Explicit error handling with multiple return values
and build a short “Monte-Carlo Pi calculation” to help translate
Julia
using Statistics
function calculate_pi(samples)
inside = 0
for i in 1:samples
x, y = rand(), rand()
if x^2 + y^2 <= 1
inside += 1
end
end
return 4 * inside / samples
end
function main()
workers = Threads.nthreads()
samples_per_worker = 1_000_000
# Array to store results from each thread
results = zeros(workers)
# Use Threads.@threads for loop-level parallelism
Threads.@threads for i in 1:workers
results[i] = calculate_pi(samples_per_worker)
end
pi_estimate = mean(results)
println("π ≈ $(round(pi_estimate, digits=10))")
end
main()
(Me) Then of course run this with julia script.jl
.
Go
package main
import (
"fmt"
"math/rand"
"sync"
)
func calculatePi(samples int) float64 {
r := rand.New(rand.NewSource(int64(samples))) // Create a separate RNG for each goroutine
inside := 0
for i := 0; i < samples; i++ {
x, y := r.Float64(), r.Float64()
if x*x+y*y <= 1 {
inside++
}
}
return 4 * float64(inside) / float64(samples)
}
func main() {
const (
workers = 8
samplesPerWorker = 1_000_000
)
var wg sync.WaitGroup
// := is the short variable declaration operator - it declares and initializes variables in one step
// make() allocates and initializes a slice (not an array) with the specified length and capacity
results := make([]float64, workers)
// Launch workers
for i := 0; i < workers; i++ {
wg.Add(1)
// The "go" keyword launches a goroutine - a lightweight thread managed by the Go runtime
// The function that follows executes concurrently with the rest of the program
go func(id int) {
// defer schedules a function call to run just before the function returns
// This ensures wg.Done() is called even if the function panics
defer wg.Done()
results[id] = calculatePi(samplesPerWorker)
}(i) // Passing 'i' as an argument to avoid capturing the loop variable by reference
}
// Wait for all goroutines to complete
wg.Wait()
// Calculate average
sum := 0.0
for _, result := range results {
sum += result
}
pi := sum / float64(workers)
fmt.Printf("π ≈ %.10f\n", pi)
}
Go can either run this directly with go run script.go
or build a binary with go build script.go
.