Changeset View
Changeset View
Standalone View
Standalone View
src/docs/article/using_futures.diviner
- This file was added.
@title Using Futures | |||||
@group future | |||||
Overview of how futures work in libphutil. | |||||
= Overview = | |||||
Futures (also called "Promises") are objects which represent the result of some | |||||
pending computation (like executing a command or making a request to another | |||||
server), but don't actually hold that result until the computation finishes. | |||||
They are used to simplify parallel programming, since you can pass the future | |||||
around as a representation for the real result while the real result is being | |||||
computed in the background. When the object is asked to return the actual | |||||
result, it blocks until the result is available. | |||||
libphutil provides a number of future-based APIs, as they strike a good balance | |||||
between ease of use and power for many of the domains where PHP is a reasonable | |||||
language choice. | |||||
Each type of future is used to do a different type of computation (for instance, | |||||
@{class:ExecFuture} executes system commands while @{class:HTTPFuture} executes | |||||
HTTP requests), but all of them behave in a basically similar way and can be | |||||
manipulated with the same top-level constructs. | |||||
= Basics = | |||||
You create a future by instantiating the relevant class and ask it to return the | |||||
result by calling `resolve()`: | |||||
$gzip_future = new ExecFuture('gzip %s', $some_file); | |||||
$gzip_future->start(); | |||||
// The future is now executing in the background, and you can continue | |||||
// doing computation in this process by putting code here. | |||||
list($err, $stdout, $stderr) = $gzip_future->resolve(); | |||||
When you call `resolve()`, the future blocks until the result is ready. You | |||||
can test if a future's result is ready by calling `isReady()`: | |||||
$is_ready = $gzip_future->isReady(); | |||||
Being "ready" indicates that the future's computation has completed and it will | |||||
not need to block when you call `resolve()`. | |||||
Note that when you instantiate a future, it does not immediately initiate | |||||
computation. You must call `start()`, `isReady()` or `resolve()` to | |||||
activate it. If you simply call `resolve()` it will start, block until it is | |||||
complete, and then return the result, acting in a completely synchronous way. | |||||
See @{article:Command Execution} for more detailed documentation on how to | |||||
execute system commands with libphutil. | |||||
= Managing Multiple Futures = | |||||
Commonly, you may have many similar tasks you wish to parallelize: instead of | |||||
compressing one file, you want to compress several files. You can use the | |||||
@{class:FutureIterator} class to manage multiple futures. | |||||
$futures = array(); | |||||
foreach ($files as $file) { | |||||
$futures[$file] = new ExecFuture("gzip %s", $file); | |||||
} | |||||
foreach (new FutureIterator($futures) as $file => $future) { | |||||
list($err, $stdout, $stderr) = $future->resolve(); | |||||
if (!$err) { | |||||
echo "Compressed {$file}...\n"; | |||||
} else { | |||||
echo "Failed to compress {$file}!\n"; | |||||
} | |||||
} | |||||
@{class:FutureIterator} takes a list of futures and runs them in parallel, | |||||
**returning them in the order they resolve, NOT the original list order**. This | |||||
allows your program to begin any follow-up computation as quickly as possible: | |||||
if the slowest future in the list happens to be the first one, you can finish | |||||
processing all the other futures while waiting for it. | |||||
You can also limit how many futures you want to run at once. For instance, to | |||||
process no more than 4 files simultaneously: | |||||
foreach (id(new FutureIterator($futures))->limit(4) as $file => $future) { | |||||
// ... | |||||
} | |||||
Consult the @{class:FutureIterator} documentation for detailed information on | |||||
class capabilities. |