Monday, 27 July 2026

GO OS process vs thread

## 1. Channels vs Goroutines vs Processes


These are 3 different things:


```javascript

┌─── OS Process (itemgen-service binary) ─────────────────────┐

│  ┌─ OS Thread 1 ────────────────────────────────────────┐   │

│  │  ┌─ Goroutine: HTTP handler ──┐                      │   │

│  │  │  ┌─ Goroutine: HTTP handler ┤                      │   │

│  │  │  │  ┌─ Goroutine: Worker ───┤                      │   │

│  │  │  │  │                         │                      │   │

│  │  │  │  │  ←── channel (in-memory) ──┘                  │   │

│  │  │  │  │      (just a queue, NOT a goroutine)          │   │

│  │  └──┴──┴────────────────────────┘                      │   │

│  └───────────────────────────────────────────────────────┘   │

│  ┌─ OS Thread 2 ────────────────────────────────────────┐   │

│  │  ┌─ Goroutine: scheduler ──┐                          │   │

│  │  └──────────────────────────┘                          │   │

│  └───────────────────────────────────────────────────────┘   │

└──────────────────────────────────────────────────────────────┘

```


- __Channel__: Just a thread-safe in-memory queue. No code runs "inside" it. It's a data structure, like a slice with a lock.

- __Goroutine__: A lightweight unit of execution (~2KB stack). NOT an OS process. Go runs thousands of goroutines on a few OS threads. Your app already uses goroutines (the scheduler, the item generation `sync.WaitGroup` workers, etc.)

- __Process__: The OS-level process — your running `itemgen-service` binary. There's only ONE process. Everything (channels, goroutines) lives inside it.



```javascript

$ ps aux | grep itemgen-service

jxiang  12345  ... itemgen-service    ← ONE process, always


$ htop (press H to toggle thread view)

12345 itemgen-service                 ← main thread

12346 itemgen-service                 ← OS thread (Go runtime)

12347 itemgen-service                 ← OS thread (Go runtime)

12348 itemgen-service                 ← OS thread (Go runtime)

```


They all share PID 12345 as the parent. The sub-IDs (12346, 12347...) are __threads__, not separate processes. They share the same memory space.


## Go's GMP model


```javascript

Goroutines (G)          OS Threads (M)         CPU Cores (P)

┌──────────────┐        ┌──────────────┐      ┌──────────┐

│ HTTP handler │──┐     │              │      │          │

│ HTTP handler │──┤     │  Thread 1    │◀────▶│  Core 1  │

│ Scheduler    │──┼──▶  │              │      │          │

│ DB sync      │──┤     ├──────────────┤      ├──────────┤

│ Worker       │──┤     │  Thread 2    │◀────▶│  Core 2  │

│ item gen #1  │──┘     │              │      │          │

│ item gen #2  │        └──────────────┘      └──────────┘

└──────────────┘

  ~1000 goroutines         ~4 threads (= CPU count)

```


- __Goroutines__: Your code spawns these with `go func()`. Thousands can exist. They're cheap (~2KB each).

- __OS Threads__: Go runtime creates these automatically. Default = number of CPU cores. These are what `htop` shows.

- __Processes__: Only ONE. Your binary. Always.


## How to verify


```bash

# Count OS processes (always 1):

ps -C itemgen-service -o pid= | wc -l

# Output: 1


# Count OS threads (several):

ps -C ite,gen-service -L -o tid= | wc -l

# Output: 4-8 (depending on CPU cores)


# Count goroutines (many):

# Run inside your Go code:

runtime.NumGoroutine()

# Output: 50-100+ (handlers, scheduler, worker, etc.)

```


## Why you might see "new process spawning"


Some monitoring tools (like Docker stats, certain dashboards) display __threads as if they were processes__. On Linux, `/proc/<pid>/task/` lists each thread with its own TID (thread ID), which can look like process IDs. But they're threads — they share memory, file descriptors, and the same PID.


Your app already spawns goroutines today (scheduler, item generation workers, DB sync) — you're just not noticing because the worker pattern is the same. Adding the notify worker is no different.


Shall I implement the SMTP notification plan now?



# Linux — count threads vs processes:

ps -eLf | grep itemgen-service | head

# Columns: UID PID PPID LWP C NLWP ...

# PID = process ID (same for all)

# LWP = thread ID (different for each)

# NLWP = number of threads


# Or simpler:

cat /proc/$(pgrep itemgen-service)/status | grep Threads

# Output: Threads: 7


# On Windows (your machine):

tasklist /fi "imagename eq itemgen-service.exe" /fo csv

# Shows 1 process


# PowerShell — count threads:

(Get-Process itemgen-service).Threads.Count

# Output: 7 (threads, not processes)


No comments:

Post a Comment