## 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