Multiprocessor
Multiprocessing Manager
Multiprocessor is a powerful package enabling developers to manually spawn EVM instances for parallel tasks, allows the programmer to fully leverage the computation power of modern multiple processor design. These jobs run in isolated EU instances and are subject to the same commutativity checks as EOA-submitted transactions. The system automatically detects conflicts and merges safe updates deterministically.
How It Works
The parent execution unit (EU) spawns multiple child EUs to execute transactions in parallel. Each child runs in isolation, and its state changes are passed to the Conflict Detector. Only clean, non-conflicting changes are merged back into the parent EU, ensuring deterministic and safe parallelism.
Storage: Child EUs receive a snapshot copy of the parent’s storage state at the moment of forking. They do not write directly to global storage during execution. They accumulate tentative state changes in a local cache. After execution, these changes are passed to the Conflict Detector, only clean, non-conflicting storage changes are committed back to the parent EU.
Memory: Each spawned EU gets its own memory space. Memory is not shared, not snapshotted, and has no impact on conflict detection. You can use memory freely within each parallel job without restriction. Since they have no impact on the global state.
The conflict rules applied to EOA transitions are also applicable to these multiprocessor-spawned tasks.

Implementation
The Multiprocess
offers a queue, which temporarily holds a list of tasks before execution begins. State consistency is rigorously guaranteed all the time. The actual parallel execution of the task in the queue won't start until the function run()
is called. After execution, it checks for conflicts (e.g., write to same storage) and only merges non-conflicting (commutative) state changes back into the main state.
Multiprocess
Manages job queue and controls execution across isolated EUs.
addJob(...)
Adds a job to the queue — each job includes a gas limit, value to transfer (optional, 0 by default), target contract, and calldata.
run()
Executes all jobs in parallel.
Conflict Detection
After run()
is called, Arcology automatically performs conflict detection to ensure state consistency. Any jobs that violate commutativity (e.g., write to the same storage key) are reverted and excluded from the final state merge.
Examples
The examples highlight the main features and applications of Arcology's multiprocessing capability. They demonstrate how Arcology's technology can enhance the execution of smart contracts and provide significant benefits in terms of scalability and efficiency.
Parallel Assignment
This contract illustrates how two independent value assignments can be executed in parallel, safely merged back into the global state, and verified for correctness — all within a single transaction.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;
import "@arcologynetwork/concurrentlib/lib/multiprocess/Multiprocess.sol";
contract ParaAssigment {
uint256 _1;
uint256 _2;
function call() public {
Multiprocess mp = new Multiprocess(2); // Two parallel processors.
// Adds two parallel jobs into the queue, each with a gas limit, value,
// address and calldata-very similar to address.call{}() syntax.
mp.addJob(50000, 0, address(this), abi.encodeWithSignature("assigner(uint256)", 0));
mp.addJob(50000, 0, address(this), abi.encodeWithSignature("assigner(uint256)", 1));
/*
Run two jobs with two new EU instances in parallel.
Conflict detector will check the state they touch to preserve
commutativity after execution. Only clean state changs are merged back to the
parent EVM instance.
*/
mp.run();
assert(_1 == 10); // Check if the changes have been merged back.
assert(_2 == 12);
}
function assigner(uint256 v) public {
if (v == 0) {
_1 = v + 10; // For the first call
return;
}
_2 = v + 11; // For the first call
}
}
The contract declares two state variables, _1
and _2
, and sets up two parallel jobs using the Multiprocess
API with two execution units. Job 0 runs assigner(0)
, setting _1 = 10
, while Job 1 runs assigner(1)
, setting _2 = 11
.
These jobs are executed in parallel inside isolated EU instances using mp.run()
. After execution, Arcology’s conflict detector verifies that the two jobs modify separate, non-overlapping state, confirming no conflicts.
Since the updates are commutative and independent, their changes are safely merged back into the parent context. The final assertions verify that _1
is 10 and _2
is 11, confirming the success of deterministic parallel execution.
A Conflict Example
Similar to the example above, the contract ParaAssignmentWithConflict
contains a function call()
that utilizes the Multiprocess
contract to execute the function assigner()
in parallel. The assigner()
function takes an input v
and assigns the value v + 10
to the element at index v
in the results
array.
The major difference is when assigner()
is no longer thread-safe, a write-write conflict happens at runtime. But the system reverts one transaction to preserve determinism. Only non-conflicting results are merged into the final state.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;
import "@arcologynetwork/concurrentlib/lib/multiprocess/Multiprocess.sol";
contract ParaAssignmentWithConflict {
uint256[2] results;
function call() public {
Multiprocess mp = new Multiprocess(2);
mp.addJob(50000, 0, address(this), abi.encodeWithSignature("assigner(uint256)", 0));
mp.addJob(50000, 0, address(this), abi.encodeWithSignature("assigner(uint256)", 0));
mp.addJob(50000, 0, address(this), abi.encodeWithSignature("assigner(uint256)", 1));
mp.run();
assert(results[0] == 10);
assert(results[1] == 11);
}
function assigner(uint256 v) public {
results[v] = v + 10;
}
}
Here's a step-by-step explanation of what happens:
The call()
function is called, and it creates a new instance of the Multiprocess
contract with 2 threads (processes).
Two messages are added into the Multiprocess
container with the function signature "assigner(uint256)" and respective values 0
and 1
.
The mp.run()
function is called, which executes the two messages in parallel using the specified number of threads (2).
Arcology detects the overlapping write to the same storage key. One of the conflicting jobs is reverted. The third job (assigner(1)
) writes to results[1]
and is conflict-free, so it’s committed.
Only one write to results[0]
is retained.results[0] == 10
, results[1] == 11
so the assertions pass.
Last updated