What is the solution for asynchronous PHP processes?

Asked

Viewed 2,839 times

11

In PHP many times, in the middle of an operation, I need to send an email, make a call webservice or register a log, and these can sometimes take a while to process, but without having to wait for them to finalize a request.

I’ve been doing some research around, and I’ve seen that some languages like C#, Nodejs and even Python3.6 have been implementing asynchronous task functionalities in the language.

I looked for something in PHP, but I did not find anything satisfactory. I saw many gambiarras how to use a Message Queue, fsockopen, command line calls and the like to perform an asynchronous task.

I needed something that was possible to execute within the scope of a certain code, without having to wait for something that was built in hand.

  • There is something better to implement asynchronous PHP tasks?

  • Is there an extension or some library that does this work (or at least simulates it in a more coherent way than the ones I usually see around)?

Note: I have been reading some things about Pthreads, but I do not know if it is the solution. If you can explain about this extension, I would be grateful.

  • Please do not ask to change language. I am already thinking about the possibility, but it is not yet feasible. kkk

  • 5

    C#, Java, C++, Harbour, etc.

  • 2

    The only "asynchronous" thing in PHP that comes to mind is the HTTP client implementation of the Guzzle people. They provide an HTTP client similar to Restangular (Javascript) that supports asynchronous calls and Promises. If I don’t miss the memory they use Message Queue or something like that. Maybe it’s worth taking a look.

  • @nmindz no, Guzzle uses Curl. And he’s asynchronous between requests. Anyway, the script needs to wait for the end of the process, which may fail with the set_time_limit, in my opinion.

  • Either way, asynchronous requests can be made via fsockopen, but it doesn’t solve my problem. It doesn’t serve to send email or log logs. Sad is to think of using it to do tricks, but it’s what we have :\

  • @Wallacemaxters got it. I was looking this and I thought maybe I could help. Well, good luck!

  • @I use it in a system. It’s great, because Curl does a magic where you don’t have to wait for 50 requests, it makes a kind of simultaneous request that returns at different times, but contact the execution time of the script (that is, while this queue doesn’t end, the script keeps running).

  • https://stackoverflow.com/questions/13846192/php-threading-call-to-a-php-function-asynchronously

  • 3

    For those who did not understand the problem of PHP is that it is a language of script, every time you try to do something complex you will have difficulties. It is a case of using hammer for a screw. https://en.wikipedia.org/wiki/Law_of_the_instrument.

  • Guys, I think I’m confusing things. I asked this question here. Maybe I’ve talked nonsense here :\

Show 5 more comments

3 answers

8


One thing is a function run asynchronously, another thing is to make a call to something external without expecting a reply (or catch the answer later), I will not go into detail because to be honest I do not know the background Guzzle.

PHP only really supports Threads with Pthread, still it is possible to simulate something. Personally I see no need to run a function asynchronously in a PHP script processing a web page, maybe I don’t quite understand the need for this, I think maybe in an application CLI (Command-line interface/command line interface) that uses PHP would be more interesting, since it could perform multiple tasks and run endlessly (or until all "events" end).

Call a PHP script without waiting for it to respond

Speaking regardless of the need, an example which would be something similar to "asynchronous" (in fact it’s a totally separate process) would be to call a PHP script through another script in CLI mode, would look something like:

/**
 * Iniciar script em outro processo
 *
 * @param string $script  Define a localização do script
 * @param string $php_exe Define a localização do interpretador (opcional) 
 * @param string $php_ini Define a localização do php.ini (opcional)
 * @return void
 */
function processPhpScript($script, $php_exe = 'php', $php_ini = null) {
    $script = realpath($script);

    $php_ini = $php_ini ? $php_ini : php_ini_loaded_file();

    if (stripos(PHP_OS, 'WIN') !== false) {
        /* Windows OS */
        $exec = 'start /B cmd /S /C ' . escapeshellarg($php_exe . ' -c ' . $php_ini . ' ' . $script) . ' > NUL';
    } else {
        /* nix OS */
        $exec = escapeshellarg($php_exe) . ' -c ' . escapeshellarg($php_ini) . ' ' . escapeshellarg($script . ' >/dev/null 2>&1');
    }

    $handle = popen($exec, 'r');

    if ($handle) {
        pclose($handle);
    }
}

processPhpScript('pasta/script1.php');
processPhpScript('pasta/script2.php');

If it is in windows the scripts will be executed as if they were in the CMD with the command start so you don’t have to wait:

start /B cmd /S /C "c:\php\php.exe -c c:\php\php.ini c:\documents\user\pasta\script1.php" > NUL
start /B cmd /S /C "c:\php\php.exe -c c:\php\php.ini c:\documents\user\pasta\script2.php" > NUL

If you are in a Unix-like environment you will run with to save the output to /dev/null instead of returning to output

php -c /etc/php/php.ini /home/user/pasta/script1.php >/dev/null 2>&1
php -c /etc/php/php.ini /home/user/pasta/script2.php >/dev/null 2>&1

This will make your script that called the function processPhpScript don’t have to wait for the answer.

Curl and fsockopen

I believe that when we speak of asynchronous in Guzzle in fact we are talking about external requests of which you do not need to wait for the answer because you do not want it or of multiple requests that work concurrently at the same time and are delivered as they end, Curl himself can do something like this:

<?php
// Inicia dois CURLs
$ch1 = curl_init("/");
$ch2 = curl_init("https://meta.pt.stackoverflow.com/");

//Esta parte é apenas devido ao SSL, é tudo apenas um exemplo
curl_setopt($ch1, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch2, CURLOPT_SSL_VERIFYPEER, 0);

curl_setopt($ch1, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch2, CURLOPT_RETURNTRANSFER, 1);

//Inicia o manipulador e adiciona o curls
$mh = curl_multi_init();
curl_multi_add_handle($mh, $ch1);
curl_multi_add_handle($mh, $ch2);

//Executa as requisições simultaneamente
$running = null;

do {
    curl_multi_exec($mh, $running);
} while ($running);

//Finaliza o manipulador
curl_multi_remove_handle($mh, $ch1);
curl_multi_remove_handle($mh, $ch2);
curl_multi_close($mh);

//Pega o conteúdo
$response1 = curl_multi_getcontent($ch1);
$response2 = curl_multi_getcontent($ch2);

//Exibe as respostas
echo $response1, PHP_EOL;
echo $response2, PHP_EOL;

Now if you look closely, we wanted two requests at once, yet to get the answer we had to wait for everything. I believe in Guzzle the requestAsync should work with something like the Promisse, that what he succeeds in getting he sends for a reply in a callback, I don’t know much about Curl, maybe it is possible to check which item on curl_init finished, but I’ll make a suggestion with fsockopen, is just a basic example:

<?php

function createRequest($url, &$failno, &$failstr) {
    $parsed = parse_url($url);

    $isHttps = $parsed['scheme'] == 'https';
    $host = ($isHttps ? 'ssl://' : '') . $parsed['host'];
    $port = isset($parsed['port']) ? $parsed['port'] : ($isHttps ? 443 : 80);

    $socket = fsockopen($host, $port, $errorno, $errorstr);

    echo $host, $port;

    if ($socket) {
        $out = "GET " . $parsed['path'] . " HTTP/1.1\r\n";
        $out .= "Host: " . $parsed['host'] . "\r\n";
        $out .= "Connection: close\r\n\r\n";
        fwrite($socket, $out);
        return $socket;
    }

    return false;
}

function checkStatus(&$promisses, \Closure &$done) {
    if (empty($promisses)) {
        return false;
    }

    $nocomplete = false;

    foreach ($promisses as &$promisse) {
        if (feof($promisse['socket']) === false) {
            $nocomplete = true;
            $promisse['response'] .= fgets($promisse['socket'], 1024);
        } else if ($promisse['complete'] === false) {
            $promisse['complete'] = true;
            $done($promisse['url'], $promisse['response']);
        }
    }

    return $nocomplete;
}

function promisseRequests(array $urls, \Closure $done, \Closure $fail)
{
    $promisses = array();

    foreach ($urls as $url) {
        $current = createRequest($url, $errorno, $errorstr);

        if ($current) {
            $promisses[] = array(
                'complete' => false,
                'response' => '',
                'socket' => $current,
                'url' => $url
            );
        } else {
            $fail($url, $errorno, $errorstr);
        }
    }

    $processing = true;

    while ($processing) {
        $processing = checkStatus($promisses, $done);
    }
}

// Inicia dois CURLs
$urls = array(
    'http://localhost/',
    'http://localhost/inphinit/'
);

promisseRequests($urls, function ($url, $response) {
    var_dump('Sucesso:', $url, $response);
}, function ($url, $errorno, $errorstr) {
    var_dump('Falhou:', $url, $errorno, $errorstr);
});

In general what I did was to make the requisitions work at the same time, what I find interesting is that you can get the result that first finish and already manipulate as you wish, but I believe that it is not always useful.

Where Thread would be interesting

Both examples I quoted above are not threads, one is a process by part that avoids having to wait (since the call is totally owned) and the other is about multiple HTTP requests and the one that ends first calls the Closure, but speaking of real threads (or next) the only place I see that maybe it would be interesting is with a PHP script that keeps running continuously, for example a CLI script as I mentioned or a Socket to work with Websocket (which by the way is also a CLI).

I will use as an example Websocket, a socket server handles multiple calls and it responds to the websocket only when you wish, imagining that 5 people connect to the socket through Websocket making orders it would be interesting to move orders to Threds and delivery-los only when finished, otherwise would have to process them to the extent that was requested and then the user who made a simple request ends up having to wait for the users who made longer requests to process, with Thread this could improve a little, helping to work the competition (of course if the script is well written).

As soon as possible I will post an example with Websocket

Installing Pthreads

It can be installed via PECL, using the command:

 pecl install pthreads

But if you don’t have Pecl you can try and using Windows you can download the binaries here http://windows.php.net/downloads/pecl/releases/pthreads/, but if you use Mac OSX, Linux or do not have the binary for your PHP version and do not have PECL, then you will have to compile after downloading from https://github.com/krakjoe/pthreads, of course the PHP executable has to have been compiled on your machine as well and by the same compiler (I won’t go into detail as this is not the focus of the question)

  • What would be the CLI mode or CLI application?

  • 2

    @cat opa, wobble my, so CLI is short for Command-line interface, which would be in English something like "command interface per line"

  • @Guilhermenascimento, could at least by a reference for the curious to continue reading?

  • @Jeffersonquesado off the doc of PHP.net I do not know much what to add, mostly it is personal knowledge, about the example with websocket it is complex to develop, so I finish I will update the answer.

  • @Guilhermenascimento was ill, I wanted to refer to reading the compilation using the same compiler

  • 2

    @Jeffersonquesado this is something very broad and out of context of what was asked, but it is something even missing on the site (maybe I do not remember anything), maybe I formulate something on the subject, on PECL I will add details as soon as I finish the examples with Websocket (I will need to install the PECL).

  • +1 for the first option (to trigger an external process), which is undoubtedly what "solves" the problem of the questioner in a simpler way. It is not the best method, because there may be situations where it is not scalable, competition, etc., and in this, the questioner should also pay attention to not end up falling into other "holes".

  • 1

    @Charlesrobertocanato is true, competition for example, maybe only with Pthread, however the focus really was to explain the need for this in PHP, in Web I believe that this really is not necessary, with the exception of Websocket, where it is totally necessary things like competition.

Show 3 more comments

2

Well, everything goes by the approach you will take. If you expect how async and awaitC# you won’t find in PHP anyway.

I won’t go into too much detail about what is asynchronous and lower-level implementations because I’ve never studied it in depth.

There is something better to implement asynchronous PHP tasks?

Is there an extension or some library that does this work (or at least simulates it in a more coherent way than the ones I usually see around)?

For these types of problems you reported, we can use the Reactphp. It is a collection of libraries that helps you perform tasks asynchronously. It suggests that you install some extensions to improve Event-loop performance, but nothing complicated to install.

It includes an HTTP Server implementation that resembles nodejs a bit

require 'vendor/autoload.php';

$app = function ($request, $response) {
    $response->writeHead(200, array('Content-Type' => 'text/plain'));
    $response->end("Hello World\n");
};

$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server($loop);
$http = new React\Http\Server($socket, $loop);

$http->on('request', $app);
echo "Server running at http://127.0.0.1:1337\n";

$socket->listen(1337);
$loop->run();

In PHP often, in the middle of an operation, I need to send an email, make a call webservice or register a log, and these times may take some time in processing, but no need for me to wait for them to finalize a request.

Here comes the approach and how you will work and implement your architecture can help you.

Calls from webservices will usually have to wait, unless there are multiple calls to several different services. These requests you can make way asynchronous with the guzzle.

use GuzzleHttp\Client;
use GuzzleHttp\Promise;

$client = new Client(['base_uri' => 'http://httpbin.org/']);

// Initiate each request but do not block
$promises = [
    'image' => $client->getAsync('/image'),
    'png'   => $client->getAsync('/image/png'),
    'jpeg'  => $client->getAsync('/image/jpeg'),
    'webp'  => $client->getAsync('/image/webp')
];

// Wait on all of the requests to complete. Throws a ConnectException
// if any of the requests fail
$results = Promise\unwrap($promises);

// Wait for the requests to complete, even if some of them fail
$results = Promise\settle($promises)->wait();

// You can access each result using the key provided to the unwrap
// function.
echo $results['image']['value']->getHeader('Content-Length')[0]
echo $results['png']['value']->getHeader('Content-Length')[0]

Heavy operations like sending email, in my view are much better enjoyed if you forward to a Queue out of of the server used to be your web server. Several frameworks (Zend, Symfony, Laravel) already comes with some implementation of Queue to solve these problems, but doing something from scratch is also possible. Follow an example using a library to work with queues managed by Beanstalkd:

<?php

// Hopefully you're using Composer autoloading.

use Pheanstalk\Pheanstalk;

$pheanstalk = new Pheanstalk('127.0.0.1');

// ----------------------------------------
// producer (queues jobs)

$pheanstalk
  ->useTube('testtube')
  ->put("job payload goes here\n");

// ----------------------------------------
// worker (performs jobs)

$job = $pheanstalk
  ->watch('testtube')
  ->ignore('default')
  ->reserve();

echo $job->getData();

$pheanstalk->delete($job);

// ----------------------------------------
// check server availability

$pheanstalk->getConnection()->isServiceListening(); // true or false

In conclusion, we have some options out there. Languages all have their advantages and disadvantages and their resources are more a reflection of how those who use language usually solve problems than what we want.

Sometimes before deciding how the implementation will be, we can reflect on which my real problems and how others have solved.

  • Good answer. I found the idea of React. I had used the Beanstalkd in Laravel, but I didn’t really like the serialization of closures :. I’ll think about this idea of a server running for these operations.

1

The library pthreads is a great option for using parallel process.

How to install:

Before you start, you must have Thread Safety in PHP enabled. Thread Safety means that binary can work in a multithreaded web server context, such as Apache 2 on Windows. Thread Safety works by creating a local copy on each thread, so that the data will not collide with another thread.

On Windows, look for the version that matches your php in windows.php.net, unzip the file and browse the file php_pthreads.dll and put in the folder ext inside the briefcase php in the directory you installed. And put the file pthreadVC2.dll in the briefcase php. If an error occurs, put this dll in as well C: Windows System32.

On linux, type the command pecl install pthreads. You can check the versions available on pecl.php.net

After everything is working, let’s go to the basic example:

/** THREAD DE ENVIO DE E-MAIL *******************/
class tEmail extends Thread
{
     private $id;

     public function __construct($id)
     {
           $this->id = $id;
     }

     public function run()
     {
         sleep(rand (1, 50));
         echo "Thread: ".$this->id.", Email enviado; ".date("d/m/Y H:i:s")."\r\n";
     }

     public function getId(){
          return $this->id;
     }
}

//Criar 50 thread para rodar
$tEmail = array();
for($i=0;$i<50;$i++){
    $tEmail[] = (new tEmail($i));
}

foreach ($tEmail as $t) {
   echo ("Thread " . $t->getId() . " iniciada! \r\n");
   $t->start(); //Manda rodar em paralelo
}

//Você pode trabalhar com as instancias delas, mesmo estando em paralelo
foreach ($tEmail as $t) {
     while ($t->isRunning()) { //Pode ser verificado se ainda está rodando
        usleep(100);
     }
     echo ("Thread " . $t->getId() . " finalizada! \r\n");
}

I hope I’ve helped!

  • The bag of this library is just the "recompilation of PHP"... I’m picking up here :\

  • Do you have an already installed PHP version that does not have the right TS enabled? (linux or windows)

  • Well, I don’t know what version you’re using, but in this tutorial you explain how to compile in linux for PHP 5.5 http://eddmann.com/posts/compiling-php-5-5-with-zts-and-pthreads-support/

Browser other questions tagged

You are not signed in. Login or sign up in order to post.