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.)
Share Improve this question edited Feb 8 at 15:40 MaximAL asked Feb 2 at 14:36 MaximALMaximAL 4093 silver badges8 bronze badges 11- 1 Yes, you can set GOMAXPROCS to 1. No, that can’t prevent the runtime from starting multiple threads, the standard runtime is itself multithreaded. – Mr_Pink Commented Feb 2 at 14:42
- 2 Perhaps you should change your posted question to ask how to do what you want to do. What you want to do is not create a single threaded application but rather to "create some old-style console application without any concurrency and goroutines". It also appears you want to run the garbage collector at specific points and to otherwise have it turned off. There may be other functionality you want to turn off. So ask about what you want to do rather than the approach you think will get you to what you want to do. – Richard Chambers Commented Feb 2 at 14:50
- 2 "Or is it impossible intrinsically?" This. – Volker Commented Feb 2 at 16:10
- 2 This project may be of interest to you. TinyGo for embedded applications which claims "TinyGo is a project to bring Go to microcontrollers and small systems with a single processor core": github/tinygo-/tinygo – Richard Chambers Commented Feb 2 at 20:36
- 1 [1/2] Two things. The first one to consider is that how many OS threads a Go runtime starts under its hood in a running program powered by said runtime is an implementation detail. It is not in any way defined by the language spec, and Go can be implemented on platforms with no (contemporary) concept of "a thread". (Just for amusement, the most popular Go implementation can compile for the "wasm" target; while I'm not in any way an expert of that stuff but I think WASM does not provide a concept of a thread.) So, wher you say that a simple running Go program utilizes multiple OS threads, … – kostix Commented Feb 3 at 18:36
1 Answer
Reset to default 2So 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 !?!