module
Lapis::Docs::D_CONCURRENCY_AND_FIBERS::B_OS_THREADS_AND_CHANNELS
Overview
OS Threads & Actor Channels
Comprehensive guide to multi-core parallel computation, procedural generation, pathfinding,
and thread-safe communication using the Actor Pattern with Godot::Channel(T).
Executive Summary & Key Topics
| Topic | Method / Anchor | Description |
|---|---|---|
| Thread Safety Rules | .topic_00_threading_invariants |
SceneTree modification thread affinity, buffered channels, and Crystal 1.20+ rules. |
| The Actor Pattern with Channels | .topic_01_actor_pattern |
Spawning background threads and communicating via immutable message channels. |
| Crystal 1.20+ Execution Context Safety | .topic_02_crystal_120_context_safety |
Why unbuffered channels suspend and raise NilAssertionError on raw OS threads. |
Related Guides & Source References
- Channel Implementation:
src/libgodot/channel.cr - Live Specs:
spec/suites/test_concurrency_stress.cr
Defined in:
libgodot/docs/d_concurrency_and_fibers/b_os_threads_and_channels.crClass Method Summary
-
.topic_00_threading_invariants : Nil
Thread Safety Rules: SceneTree modification thread affinity, buffered channels, and Crystal 1.20+ rules.
-
.topic_01_actor_pattern : Nil
The Actor Pattern with Channels: Spawning background threads and communicating via immutable message channels.
-
.topic_02_crystal_120_context_safety : Nil
Crystal 120+ Execution Context Safety: Why unbuffered channels suspend and raise NilAssertionError on raw OS threads.
Class Method Detail
Thread Safety Rules: SceneTree modification thread affinity, buffered channels, and Crystal 1.20+ rules.
Key Topics & Information
- Rule 1: NEVER mutate the SceneTree (add_child, queue_free) from a background thread
- Rule 2: Always use buffered channels across OS threads (capacity >= 1)
- Rule 3: Crystal collections (Hash, Array) must be protected with ::Thread::Mutex
The Actor Pattern with Channels: Spawning background threads and communicating via immutable message channels.
struct PathResult
getter path : Array(Vector2)
def initialize(@path : Array(Vector2)); end
end
node NavigationManager < Node do
@channel = Godot::Channel(PathResult).new(capacity: 100)
def request_path(start_pos : Vector2, end_pos : Vector2) : Void
Thread.new do
# Compute path on background thread:
computed = compute_heavy_astar(start_pos, end_pos)
@channel.send(PathResult.new(computed))
end
end
def _process(delta : Float64) : Void
# Drain channel non-blockingly on main thread:
while result = @channel.receive?
apply_path_to_player(result.path)
end
end
end
Crystal 120+ Execution Context Safety: Why unbuffered channels suspend and raise NilAssertionError on raw OS threads.
The Hazard in Crystal 1.20+:
In Crystal 1.20+, unbuffered channels (Channel(T).new) suspend the calling fiber when no receiver is ready. On raw OS threads (Thread.new), Fiber#execution_context is nil, so suspending raises:
NilAssertionError: Fiber#execution_context cannot be nil
The Solution:
Godot::Channel(T) enforces capacity >= 1. Senders deposit messages into the ring buffer without suspending the calling thread!