cthreads compiles a typed Python subset into native kernels: CPU via C++ (@Thread / @Threadable), optional GPU via Vulkan (@Gpu). You keep Python-shaped control flow (jobs, pools, sync, await) while the heavy work runs as OS-native / on-device compute in the background.
GPU support ships as cthreads-gpu (same import cthreads; do not install it alongside the CPU-only cthreads wheel). The type whitelist covers scalars, containers, and your own Threadable types.
Hand-written C++/GLSL (or a full pybind project) is powerful but leaves the Python loop. threading stays on the GIL for CPU-bound bytecode; multiprocessing gets multi-core but pays process boundaries and pickling. cthreads sits in between: annotate -> compile -> run with the same job/pool/sync feel on CPU, and the same idea on GPU through cthreads-gpu.
It is a typed subset (not full Python). It is not a drop-in NumPy replacement. Prefer it when you want compiled native kernels without leaving a Python-shaped API.
| cthreads SPH | Python + NumPy SPH |
|---|---|
| realtime | linear speedup up to 100x realtime on this scene |
![]() |
![]() |
Fig. 1. Side-by-side Smoothed Particle Hydrodynamics (SPH) fluid demo under identical scene setup (same particle set, forces, timestep, and camera).
Left: dynamics advanced with cthreads (@Thread kernels on a ThreadPool, shared native buffers). Right: the same solver expressed as Python + NumPy array kernels on the host. Overlay text at the top of each clip reports live run metrics; the second-to-last value is realtime speedup (simulated time per unit wall-clock). On this scene the NumPy path reaches up to about 100x realtime; the cthreads path is shown for visual parity of the fluid, not as a matched FPS bake-off in the GIF encode.
Interactive CPU Mandelbrot (pool + Shared + TBuffer): Open in Colab.
- Install
- FAQ
- CPU quickstart (reading map + basics)
- GPU quickstart
- Guides catalog
- API reference
Python >= 3.10, a C++17 compiler, and CMake >= 3.18 (CMake is only needed to build the native _ext module). Full toolchain notes: docs/install.md.
Published wheels (Linux / Windows x86_64) and the sdist are on PyPI:
pip install cthreads
# Vulkan GPU (@Gpu) support (full package; do not install alongside cthreads):
pip install cthreads-gpuYou still need a C++ compiler for the first thread(...) (user kernels). On Linux, wheels include a prebuilt _ext; CMake is only required if you install from the sdist or develop from source.
python -m venv .venv
# activate, then:
pip install cmake ninja # CMake/Ninja in the venv; compiler is still system/MSVC
pip install -e ".[test]" # or: pip install -e .First cthreads.thread(...) auto-runs cache-checked prepare + load_kernels. Call unload_kernels() before a force rebuild (thread(..., force=True) or prepare(force=True)).
Annotate what should become a native kernel:
@Thread- functions / methods compiled to C++@Threadable- classes compiled to C++ structs (shared state across kernels)@Gpu- functions compiled to Vulkan compute (lists of scalars; see GPU guides)
Allowed in annotations (arguments, returns, locals, Threadable fields):
int,float,bool,strlist[...]of allowed typesdict[...]of allowed types (typicallydict[str, ...])- nested combinations of the above
@Threadableclasses- any internal types imported by
cthreads
This is a whitelist, not full Python. No arbitrary objects, no untyped values in kernels.
Marks a function or method for compilation. Pass it to cthreads.thread(...) to run off the GIL.
from cthreads import Thread
@Thread
def my_example_function() -> None:
return- Typed parameters and a return type (use
-> Nonewhen there is no value). - No
*args/**kwargs. - Locals must be annotated with an allowed type (
x: int = 0). - Inside the body, only call other
@Threadfunctions/methods, plus supported libraries (math,cthreads.math,cthreads.sync,cthreads.linalg) and builtins likelen/range. No arbitrary Python. - Return values must match the declared return type.
from cthreads import Thread
@Thread
def example_function(scale: float, values: list[float]) -> float:
total: float = 0.0
i: int = 0
while i < len(values):
total += scale * values[i]
i += 1
return totalPython's open object model does not map cleanly to C++. @Threadable marks a class so the compiler can emit a fixed C++ struct and marshal it safely.
from cthreads import Threadable
@Threadable
class MyExample:
x: float
y: float- All fields are typed at class scope (dataclass-style annotations).
- Do not define / override
__init__. The decorator injects a dataclass-style constructor (ExampleClass(1, "x")orExampleClass(attr1=1); omitted fields zero / empty, matching C++T{}). - Kernel methods must use
@Threadand takeselflike normal methods. - Method argument / return annotations must be allowed types (or
-> None).
from cthreads import Threadable, Thread
@Threadable
class ExampleClass:
attr1: int
attr2: str
attr3: list[float]
@Thread
def method1(self) -> None:
self.attr1 += 1
@Thread
def method2(self, string: str) -> str:
return self.attr2 + string
obj = ExampleClass(0, "1", [2.0, 3.0])
obj.method1()
print(obj.attr1, obj.method2(" 1")) # 1 1 1Why Threadables?
- shared state for worker threads
- typed containers / domain objects
- grouping related kernel methods
import asyncio
import cthreads
from cthreads import Thread
@Thread
def example_function(lhs: float, rhs: float, count: int) -> float:
for i in range(count):
lhs += rhs
return lhs
# Sync: Job -> join -> result
job = cthreads.thread(example_function, 1.5, 2.0, 200)
job.join() # starts if needed; blocks this thread (GIL released in C++)
result = job.result()
# Async: await auto-starts and returns the result (event loop stays free)
async def main() -> None:
job = cthreads.thread(example_function, 1.5, 2.0, 200)
result = await job
print(result)
asyncio.run(main())Signature: cthreads.thread(fn, *args, force: bool = False, **kwargs) -> Job.
Vulkan compute kernels use the same "annotate then launch" idea on a separate backend:
from cthreads.gpu import Gpu, GlobalIdx, gpu
@Gpu
def saxpy(n: int, a: float, x: list[float], y: list[float]) -> None:
i: int = GlobalIdx.x
if i >= n:
return # do not return None. Simply return
y[i] = a * x[i] + y[i]
x = [1.0, 2.0, 3.0, 4.0]
y = [10.0, 20.0, 30.0, 40.0]
gpu(saxpy, len(x), 2.0, x, y).join()Full guides (concepts, best practices, examples): docs/guide/gpu/README.md. Install / drivers: docs/install.md.

