eftec / multione
A multi process library using PHP
Requires
- ext-json: *
README
MultiOne is a library to execute concurrent tasks programmed in PHP using JavaScript.
This library is focused to run local task easily and without much extra boilerplate.
The goal is to be able to run CPU intensive tasks using multiples cores by distributing every task in different workers (what does the job). It also adds a new functionality, it allows to simulate a Command Line Interface (CLI) using the web.
How to use it?
Install this library using Composer
composer require eftec/multione
And call it as follows:
use Eftec\MultiOne\MultiOne; include __DIR__ . '/../vendor/autoload.php'; $multiOne = MultiOne::Factory( 1000, // every miliseconds basename(__FILE__), // the url to call 4 // the number of workers )->setMethod( static function($numWorkers):array { // the initial call // todo code $result=[]; for($instance=0; $instance<$numWorkers; $instance++) { $result[$instance]='initial'; } return $result; }, static function($idWorker, $body):array { // the worker call // some job here if($done){ return ['type'=>'end','result'=>'done','ui'=>'done']; } return ['result'=>'working', 'ui'=>"Worker: $idWorker"]; }, static function($bodies) { // the worker-end call echo "all workers ended"; } )->setHtmls( '<div id="PREFIX_contentcontent">%s</div>', // how the content is draw '<div id="PREFIX_worker_%s"></div><div id="PREFIX_worker_error_%s"></div>', // how every worker is draw '<div id="PREFIX_worker_end"></div>', // how the worker-end is draw. "loading ⏳" // the loading ui. If empty, then it don't use loading ui )->runAuto();
The browser will show the next screen
In the folder examples you can find examples of code.
Logic
There are 3 functions (defined by setMethod) that are called in different stages of the process
setMethod( static function($numWorkers):array { // the initial call (returns an array with all the payload for each worker) }, static function($idWorker, $body):array { // the worker call }, static function($bodies) { // the worker-end call, it returns a message. }
- The initial function is called at the beginner of the operation.
- It must create the initial payload, it also display the UI and it could show some visual content.
- After the initial function is called, then JavaScript (in the HTML) does the rest of operation and coordination.
- Every "n" milliseconds, every worker is called (unless it is in use). Each worker receives the load of its worker.
- Once the worker is done, it must return the next structure:
- type the type of result, example: "end" or "run".
- If a worker returns end, the the worker will stop its operation.
- If none is set then it uses "run".
- result the result (load) of the worker. If none is set, then it uses the ui (if any) or empty
- ui the visual result of the worker. If none is set, then it uses result
- type the type of result, example: "end" or "run".
- Once the worker is done, it must return the next structure:
- When all workers are finished, then worker_end is executed.
- It receives the load of all workers and it results a visual text.
How to synchronize and share information between every worker?
You can use the payload to synchronize the operations of every worker.
However, if it is not enough, then you can use the next alternatives:
- You can use an external library such as eftec/cacheone, to share information between workers
- You can use APCU, Redis, Memcache, database or file system
- The library contains 3 methods to save file locally
- MultiOne::getGetData to get some data using a local file
- MultiOne::getSetData to set some data using a local file
- MultiOne::getSetDataSafe to get some data, replace the value, and save in a safe way
Command Line Interface (CLI)
It is possible to run the library to display a command line interface on web.
In CLI mode, the code calls a single worker only when the user inputs a value in the input text.
Example
MultiOne::Factory( 1000, // every miliseconds basename(__FILE__), // the url to call )->setMethods( static function($numWorkers, $payload): array { // the initial call $_SESSION['USERNAME'] = 'admin'; return ["Start"], // the initial screen ]; }, static function($idWorker, $payload): array { // the worker call // it reads the data and reduces to 1. $username=$_SESSION['USERNAME']??null; if($username===null) { throw new RuntimeException('no user is not set'); } return MultiOne::msgRun("$username:{$payload['line']}","@$username> "); } ) ->setCli('> ','100%','600px') // we use a cli ->runAuto();
Where:
MultiOne::msgAnswer('run','text to add to the console','a prompt'); // run,full,error
The text to display could contains colors. Example:
MultiOne::msgAnswer('run','<red>red text</red><byellow>yellow background</byellow>');
Example of multi operations
You can find this example in the example folder
Lets say we need to execute 100 operations in 4 workers
First, we create the 4 workers
MultiOne::Factory( 1000, // every miliseconds basename(__FILE__), // the url to call 4 // the number of workers )
Then, we create the initial load and we split in 4 workers
static function($numWorkers):array { // the initial call $result=[]; $numOper=100; $split=ceil($numOper/$numWorkers); for($instance=0; $instance<$numWorkers; $instance++) { $result[$instance]=['done'=>$split*$instance,'task'=>[ 'init'=>($split*$instance)+$split, 'end'=>$split*$instance] ]; } return $result; }
Then, every worker must do the job
static function($idWorker, $body):array { // the worker call $body['done'] += 1; if($body['done']>=$body['task']['end']) { $body['done']='ok'; return MultiOne::msgAnswer('end',$body,'done'); } usleep(random_int(500, 1500)); MultiOne::msgAnswer('run',$body,"Worker: $idWorker"); }
// And when all jobs are done, we could close it. It could be used to merge all the results. It only needs to return a visual result.
static function($body) { // the worker-end call echo "all workers ended"; }
Optionally we could do changes in the UI.
Finally, our code could look as follows:
MultiOne::Factory( 1000, // every miliseconds basename(__FILE__), // the url to call 4 // the number of workers )->setMethods( static function($numWorkers):array { // the initial call $result=[]; $numOper=100; $split=ceil($numOper/$numWorkers); for($instance=0; $instance<$numWorkers; $instance++) { $result[$instance]=['done'=>$split*$instance,'task'=>[ 'init'=>($split*$instance)+$split, 'end'=>$split*$instance] ]; } return $result; }, static function($idWorker,$body):array { // the worker call $body['done'] += 1; if($body['done']>=$body['task']['end']) { $body['done']='ok'; return MultiOne::msgAnswer('end',$body,'done'); } usleep(random_int(500, 1500)); return MultiOne::msgAnswer('run',$body,"#$idWorker {$body['done']}"); }, static function($body) { // the worker-end call echo "all worker ended"; } )->runAuto();
Changelog
- 1.3 2025-01-04
- Added CLI
- Added sessions. By default it uses sessions.
- 1.2 2025-01-02
- Fixed a problem with PHP 8.4
- 1.1 2024-12-27
- fixed a small bug in initialize some arguments
- fixed a bug with some examples.
- 1.0 2024-12-27
- first version