I’m wondering is there any way to create a singe-threaded console application in Go?
Surely, I completely understand this is not the Golang way to do things (and we can create such application in C/Rust/Zig/&c), but I’m just exploring the abilities of Go language and its modern compilers.
I have tried different approaches involving runtime.GOMAXPROCS()
, but seems it doesn’t affect the overall number of threads with the values lower than current CPU count (or I’m just doing it wrong).
Basically, I wanted to play with creating some old-style console application for Linux without any concurrency and goroutines, just an infinite for
loop doing some things periodically (maybe reading some files in I/O-blocking way and display some info from them), where I occasionally call the garbage collector manually if I need to.
As I understand, I can turn off the garbage collector and run it manually with debug.SetGCPercent
, debug.SetMemoryLimit
, and runtime.GC()
. But how can I create single-threadness? Or is it impossible intrinsically?
Here’s my basic draft:
package main
import (
"runtime"
"runtime/debug"
"strings"
"time"
)
func init() {
// Locking OS thread
runtime.LockOSThread()
// GOMAXPROCS=1, Any effect?
runtime.GOMAXPROCS(1)
// Turn off garbage collection
debug.SetGCPercent(-1)
//debug.SetMemoryLimit(math.MaxInt64)
}
func main() {
var index uint64
for {
index++
// Emulate some memory allocation
strings.Repeat("x", 10*1024)
// Get memory statistics
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
// Output some info
println(index, memStats.Alloc/1024.0, memStats.Sys/1024.0, memStats.NumGC)
if index%20 == 0 {
// Run GC periodically
runtime.GC()
}
time.Sleep(time.Duration(500) * time.Millisecond)
}
}
Build for Linux:
GOARCH=amd64 GOOS=linux go build -ldflags "-s -w" -trimpath .
Even though, I don’t use goroutines and have set GOMAXPROCS
to 1, I have multiple threads in the OS (Debian/Ubuntu in this case). Here’s the htop
output:
I realize the runtime reserves at least one thread for the application, one for the garbage collector, and a couple more just in case. The question is, is this behavior mandatory, or can it be disabled somehow? (Let’s say I want to shoot myself in the foot and handle all memory and I/O-blocking issues by myself.)
I’m wondering is there any way to create a singe-threaded console application in Go?
Surely, I completely understand this is not the Golang way to do things (and we can create such application in C/Rust/Zig/&c), but I’m just exploring the abilities of Go language and its modern compilers.
I have tried different approaches involving runtime.GOMAXPROCS()
, but seems it doesn’t affect the overall number of threads with the values lower than current CPU count (or I’m just doing it wrong).
Basically, I wanted to play with creating some old-style console application for Linux without any concurrency and goroutines, just an infinite for
loop doing some things periodically (maybe reading some files in I/O-blocking way and display some info from them), where I occasionally call the garbage collector manually if I need to.
As I understand, I can turn off the garbage collector and run it manually with debug.SetGCPercent
, debug.SetMemoryLimit
, and runtime.GC()
. But how can I create single-threadness? Or is it impossible intrinsically?
Here’s my basic draft:
package main
import (
"runtime"
"runtime/debug"
"strings"
"time"
)
func init() {
// Locking OS thread
runtime.LockOSThread()
// GOMAXPROCS=1, Any effect?
runtime.GOMAXPROCS(1)
// Turn off garbage collection
debug.SetGCPercent(-1)
//debug.SetMemoryLimit(math.MaxInt64)
}
func main() {
var index uint64
for {
index++
// Emulate some memory allocation
strings.Repeat("x", 10*1024)
// Get memory statistics
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
// Output some info
println(index, memStats.Alloc/1024.0, memStats.Sys/1024.0, memStats.NumGC)
if index%20 == 0 {
// Run GC periodically
runtime.GC()
}
time.Sleep(time.Duration(500) * time.Millisecond)
}
}
Build for Linux:
GOARCH=amd64 GOOS=linux go build -ldflags "-s -w" -trimpath .
Even though, I don’t use goroutines and have set GOMAXPROCS
to 1, I have multiple threads in the OS (Debian/Ubuntu in this case). Here’s the htop
output:
I realize the runtime reserves at least one thread for the application, one for the garbage collector, and a couple more just in case. The question is, is this behavior mandatory, or can it be disabled somehow? (Let’s say I want to shoot myself in the foot and handle all memory and I/O-blocking issues by myself.)
So you want to get rid of all things good in Go and make it behave like an oldschool C program?
package main
import (
"fmt"
"os"
"runtime"
"runtime/debug"
)
func main() {
runtime.GOMAXPROCS(1) // limit to one logical processor
runtime.LockOSThread() // pin execution to a single OS thread
debug.SetGCPercent(-1) // disable automatic GC
fmt.Println("good old day feelings start here")
// ... use syscall-based I/O for more nostalgia
}
HOWEVER, Go always spawns additional threads even when GOMAXPROCS(1)
is set. This is because the runtime manages scheduling, system calls, signaling, and garbage collection (also: kernel threads for blocking syscalls or eg using Cgo). Not using something doesn't make it disappear.
=> this is probably as far as "faking it" can take you ... but why !?!