Recycling Contexts

In line with our mantra, always consider employing the Worker and Stackable model of execution.

The Worker model allows a context (Thread) to be reused, so if many tasks need to be executed asynchronously to the process (or creating Thread), but those tasks themselves are suitable to be executed synchronously then using a single context rather than many is a huge win.

Worker and Stackable Brief

The patterns used to create Workers and Stackable are much the same as those used to create Threads.

                class WebWorker extends Worker {
                    public function run(){}
                }
                
                class WebRequest extends Stackable {
                    public $url;
                    public $data;
                    
                    public function __construct($url) {
                        $this->url = $url;
                    }
                    
                    public function run() {
                        $response = file_get_contents($this->url);
                        
                        if ($response) {
                            /* process response */
                            
                            $this->data = array($response);
                        }
                    }
                }
                
                $worker = new WebWorker();
                $worker->start();
                
                $work = array();
                
                /* create some random work */
                foreach (range(0, 10) as $index) {
                    /* retain reference to the work */
                    $work[$index] = new WebRequest(
                        "http://pthreads.org/?query={$index}");
                        
                    /* stack the work for execution */
                    $worker->stack($work[$index]);
                }
                
                /* shutting down waits for the execution of 
                    anything previously stacked, then joins the Worker */
                $worker->shutdown();
                
                foreach ($work as $task) {
                    var_dump ($task->data);
                }
                

In the example above, many requests are processed by the same context. The aim should be to always use Workers and Stackables wherever possible, it is much more efficient to create one context (Worker), rather than many contexts (Threads).

Asynchronous Logging

A good example of using Workers in the real world might be an implementation of asynchronous logging.

In the real world, an asynchronous logger would be much more complex than the following example, for the purposes of a tutorial we will just write logs to standard output.

				class LogEntry extends Stackable {
					public $message;
					public $args;
					 
					public function __construct($message, $args = null) {
						$this->message = $message;
						$this->args = $args;
					}
					 
					public function run() {
						/* for simplicity */
						vprintf(
							"{$this->message}\n", $this->args);
					}
				}
				 
				class Logger extends Worker {
					static $instance = null;
					static $work = [];
		
					public function __construct() {
						$this->start();
					}
					 
					public static function log($message, $args = null) {
						if (self::$instance == null) {
							self::$instance = new Logger();
						}
				
						$args = func_get_args();
						if ($args) {
							$wid = count(self::$work);
							self::$work[$wid]=new LogEntry(
								array_shift($args),
								$args
							);
							self::$instance->stack(self::$work[$wid]);
						}
					}
					 
					public function run() {}
				}
				Logger::log("Hello %s", "World");
				Logger::log("Bye :)");
                

In the example above, it is the Worker that actually performs the writing of logs - however that is performed.