Exploring Processes, Goroutines, and Memory in Go

Overview

Using Go to lightly observe process address spaces, goroutine behavior, stack, and heap.

Checking Differences Between Child Processes and Address Spaces

In Go, new processes are typically launched using the os/exec package. Internally, os/exec performs operations equivalent to fork() + exec() on Unix-like OSes to execute a new program (in this example, itself).

Display the addresses of the same variables in parent and child processes to confirm the independence of memory spaces.

Meaning of Execution Results

  • Process Isolation: Different PIDs are displayed for parent and child.

  • Address Space Independence:

    • Both the global variable globalVar and the local variable localVar show different addresses in parent and child.
    • This is because Unix-like OSes allocate independent virtual address spaces for each process. Although the numbers may appear similar, they are completely separate in physical memory.

Diagram of Memory Spaces Between Processes

graph TD A[Parent Process<br/>PID=1234] -->|os/exec| B[Child Process<br/>PID=1235] subgraph Astack[Parent Process Memory Space] A1[Stack: localVar=10] A2[Heap: globalVar=100] end subgraph Bstack[Child Process Memory Space] B1[Stack: localVar=20] B2[Heap: globalVar=100] end style Astack fill:#d1e8ff,stroke:#333,stroke-width:1px style Bstack fill:#ffd1d1,stroke:#333,stroke-width:1px

Observing Goroutines and Memory Sharing

Using Go's lightweight threads, goroutines, to observe memory. Since goroutines operate within the same process, they share the virtual address space. To confirm this, display the addresses of global and local variables across multiple goroutines.

Analysis

  1. Same PID All goroutines operate within the same process, so the PID is identical.
  2. Global Variable Sharing The address of globalVar is the same across all goroutines.
  3. Local Variable Independence Each goroutine has an independent stack, but in this example, the address of localVar is obtained and passed to fmt.Printf, causing it to escape to the heap. Even so, each goroutine is allocated a separate memory region, maintaining independence.
  4. Data Race The value of globalVar becoming 106 is coincidental; the result varies depending on execution timing. To safely perform concurrent processing, synchronization mechanisms like channels or mutexes are required. → Use go run -race to detect race conditions.

Diagram of Memory Sharing Between Goroutines

graph TD subgraph Proc[Single Process<br/>PID=2000] subgraph G0[g0 Stack] G0L[localVar=5] end subgraph G1[g1 Stack] G1L[localVar=10] end Heap[Heap Area<br/>globalVar=100] end G0 --- Heap G1 --- Heap style Proc fill:#f0f0f0,stroke:#333,stroke-width:1px style G0 fill:#d1e8ff,stroke:#333,stroke-width:1px style G1 fill:#ffd1d1,stroke:#333,stroke-width:1px style Heap fill:#d1ffd1,stroke:#333,stroke-width:1px

Safe Concurrent Processing Using Goroutines and Channels

Using channels allows data exchange without directly updating shared variables.

Heap Area and Garbage Collection

In Go, dynamic memory is often placed in the heap, but the placement can be confirmed via escape analysis.

  • The heap area is managed by GC, and explicit deallocation is unnecessary.

Differences Between Stack and Heap

Item Stack Heap
Management Automatically allocated/released with function calls Managed by GC
Independence Independent per goroutine Shared across the process
Speed Fast Relatively slower
Placement Non-escaping variables Escaping variables

Summary

  1. Process Independence Child processes have separate virtual address spaces from parent processes, and variables are not shared.
  2. Goroutine Memory Sharing Goroutines operate within the same process, sharing global variables but maintaining independent stacks.
  3. Memory Management Characteristics Go automatically allocates stack and heap using escape analysis, and manages the heap with GC.