PHP
Versions of PHP below 5.3 didn't have a garbage collector, this caused seemingly simple code (loops, recursion) to consume huge amounts of memory:
<?php
class Foo
{
public $var = '3.14159265359';
}
$baseMemory = memory_get_usage();
for ( $i = 0; $i <= 100000; $i++ )
{
$a = new Foo;
$a->self = $a;
if ( $i % 500 === 0 )
{
echo sprintf( '%8d: ', $i ), memory_get_usage() - $baseMemory, "\n";
}
}
?>
Below is the memory usage for 5.2 and 5.3 side by side:

This makes writing code of any complexity that runs indefinitely as near as makes no difference impossible in versions of PHP below 5.3.
For versions of PHP equal to or greater than 5.3, there is absolutely nothing about Zend that prohibits the execution of long running scripts, even indefinite execution.
There are still things to think about; If you infinitely append to some array, you will need an infinite amount of memory to store that array.
One just needs to be careful, even innocuous changes can have an effect on consumption, so establish a pattern of frequent regression testing during development, so that you can see where problems are introduced immediately.
Code and image taken from PHP manual.
pthreads v3
I'm going to answer in the context of pthreads v3, PHP7+.
pthreads might consume more memory than you are expecting. PHP is a shared nothing environment, pthreads must not break that architecture and so unlike a normal multi-threaded application, threads cannot share the same address space.
pthreads makes it appear as if they do, the way it does that is not important for the answer.
With normal Thread
programming, consumption should not be so difficult to control, but with Worker
and Pool
programming, it may not be so obvious.
Take the following code:
<?php
class Job extends Threaded {
public function run() {
printf("Job in Thread %lu (%d) bytes\n",
Thread::getCurrentThreadId(),
memory_get_usage(false));
}
}
$pool = new Pool(16);
$monitor = new Threaded();
do {
for ($i=0; $i<100; $i++)
$pool->submit(new Job());
while ($pool->collect())
continue;
printf("Main context (%d) bytes\n",
memory_get_usage(false));
$monitor->synchronized(function() use($monitor) {
$monitor->wait(1000000);
});
} while (true);
?>
The calls using $monitor
should be convention, the nicest way to force a Thread
, even the main context, to sleep.
If it were not for the calls to ::collect
, consumption would soon become out of control.
The ::collect
method is actually part of the Worker
class, the Pool
is calling it by proxy for all the Worker
objects it is using.
The prototype can be taken as:
public function collect(callable $collector = Worker::collector);
When a Job
is popped from the queue for execution inside the Worker
context that is executing it, it is inserted into a garbage list.
When ::collect
is called, the garbage list for the Worker
is traversed, passing each Job
to $collector
passed (or not) as argument.
If the Worker
is doing the kind of work that would make it dangerous to collect garbage at that time, the call to ::collect
will return early, and you should try again if the return value from ::collect
is > 0.
The $collector
should return true if the Job
can be removed from the garbage list (therefore freeing associated resources if no other references exist), the default Worker::collector
returns true
, assuming that an object in the garbage list is ready for destruction at the earliest opportunity.
All of this allows long running, rather complex pthreads based applications to run indefinitely.
The same kind of considerations must be made as for normal PHP, with an additional (little) bit of leg work.