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.