# Compute threads

A pocketpy VM contains independent modules, globals, and Python objects.
There are 16 VM slots, indexed `0` through `15`; VM 0 is the default.
**Only one native thread may access a particular VM at a time.**

Build with `PK_ENABLE_THREADS=ON` to use `pkpy.ComputeThread`. This is the
CMake default, but not the default for manual builds.
Check `pkpy.configmacros['PK_ENABLE_THREADS']` when a build lacks the class.

## Run a job in another VM

This example needs no external Python files:

```python
from pkpy import ComputeThread

worker = ComputeThread(1)
worker.exec('def square_sum(count):\n    return sum([i * i for i in range(count)])')

worker.submit_call('square_sum', 100)
# The main thread can do other work while the job runs.
worker.wait_for_done()

error = worker.last_error()
if error is not None:
    raise RuntimeError(error)

assert worker.last_retval() == 328350
```

`exec()` initializes the worker VM synchronously. `submit_call()` evaluates
the function name in that VM and starts a background job. A main-thread
function or global is not automatically visible in the worker.

## Job API

| Member | Behavior |
| --- | --- |
| `ComputeThread(vm_index)` | Reserve a worker VM from 1 through 15; use a distinct index per live worker. |
| `exec(source)`, `eval(source)` | Run synchronously in the worker VM. |
| `submit_exec(source)` | Submit statements; the successful result is `None`. |
| `submit_eval(source)` | Submit an expression. |
| `submit_call(expression, *args, **kwargs)` | Evaluate a callable in the worker and invoke it with copied arguments. |
| `is_done` | Whether the submitted job has finished. |
| `wait_for_done()` | Wait until completion. |
| `last_error()` | Formatted worker error, or `None` after success. Read only after completion. |
| `last_retval()` | Deserialize the result of a successful completed job. |

Only one job can run per worker at a time. Wait for completion before submitting
another job, and collect the result before a new job replaces it.

In a frame loop, poll `is_done` once per frame. Read `last_error()` before
`last_retval()`; a failed job has no usable return value. Keep the worker
object alive until completion, and finish all jobs before finalizing the host.

## Data and native resources

Arguments and results cross the VM boundary through [pickle](../modules/pickle.md).
They must be serializable; mutable containers are copied, not shared.
For class instances, the corresponding class must be importable in the worker.
Each VM loads its own Python module state.

Native libraries and host resources can still share process-wide state.
Protect that state in your bindings. Do not pass `py_Ref` values between VMs
or share a VM with a native worker that is already running.

Use `pkpy.currentvm()` to identify the current VM. Custom import and output
callbacks must be configured for each VM that needs them.
