PHP asinkron

Sepuluh tahun yang lalu, kami memiliki tumpukan LAMP klasik: Linux, Apache, MySQL, dan PHP, yang bekerja dalam mode lambat mod_php. Dunia telah berubah, dan dengan itu pentingnya kecepatan. PHP-FPM muncul, yang memungkinkan untuk secara signifikan meningkatkan kinerja solusi dalam PHP, dan tidak untuk segera menulis ulang ke sesuatu yang lebih cepat.

Secara paralel, perpustakaan ReactPHP dikembangkan menggunakan konsep Event Loop untuk memproses sinyal dari OS dan menyajikan hasil untuk operasi asinkron. Pengembangan ide ReactPHP - AMPHP. Pustaka ini menggunakan Event Loop yang sama, tetapi mendukung coroutine, tidak seperti ReactPHP. Mereka memungkinkan Anda untuk menulis kode asinkron yang terlihat seperti sinkron. Mungkin ini adalah kerangka kerja terkini untuk mengembangkan aplikasi asinkron dalam PHP.



Tetapi kecepatan diperlukan lebih dan lebih, alat sudah tidak cukup, sehingga gagasan pemrograman asinkron dalam PHP adalah salah satu cara untuk mempercepat pemrosesan permintaan dan memanfaatkan sumber daya dengan lebih baik.

Inilah yang akan dibicarakan oleh Anton Shabovta (zloyusr) Adalah pengembang di Onliner. Pengalaman lebih dari 10 tahun: Saya mulai dengan aplikasi desktop di C / C ++, dan kemudian beralih ke pengembangan web di PHP. Dia menulis proyek "Rumah" dalam C # dan Python 3, dan dalam PHP dia bereksperimen dengan DDD, CQRS, Event Sourcing, Async Multitasking.

Artikel ini didasarkan pada transkrip laporan Anton di PHP Russia 2019 . Di dalamnya kita akan memahami operasi pemblokiran dan non-pemblokiran dalam PHP, kita akan mempelajari struktur Event Loop dan primitif asinkron, seperti Promise dan coroutine, dari dalam. Akhirnya, kita akan menemukan apa yang menanti kita di ext-async, AMPHP 3, dan PHP 8.


Kami memperkenalkan beberapa definisi. Untuk waktu yang lama saya mencoba menemukan definisi yang tepat dari operasi asinkron dan asinkron, tetapi saya tidak menemukan dan menulis milik saya.
Asynchrony adalah kemampuan sistem perangkat lunak untuk tidak memblokir utas eksekusi.
Operasi asinkron adalah operasi yang tidak memblokir aliran eksekusi program sampai selesai.

Tampaknya sederhana, tetapi pertama-tama Anda harus memahami operasi apa yang menghalangi aliran eksekusi.

Operasi pemblokiran


PHP adalah bahasa juru bahasa. Dia membaca kode baris demi baris, menerjemahkan ke dalam instruksinya dan mengeksekusi. Pada baris mana dari contoh di bawah ini akankah kode diblokir?

public function update(User $user)
{
    try {
        $sql = 'UPDATE users SET ...';
        return $this->connection->execute($sql, $user->data());
    } catch (\PDOException $error) {
        log($error->getMessage());
    }

    return 0;
}

Jika kita menghubungkan ke database melalui PDO, benang eksekusi akan diblokir pada string query untuk SQL-server: return $this->connection->execute($sql, $user->data());.

Ini karena PHP tidak tahu berapa lama server SQL akan memproses kueri ini dan apakah itu akan dijalankan sama sekali. Ia menunggu respons dari server dan program belum berjalan selama ini.

PHP juga memblokir aliran eksekusi pada semua operasi I / O.

  • Sistem File : fwrite, file_get_contents.
  • Basis data : PDOConnection, RedisClient. Hampir semua ekstensi untuk menyambungkan kerja basis data dalam mode pemblokiran secara default.
  • Proses : exec, system, proc_open. Ini memblokir operasi, karena semua pekerjaan dengan proses dibangun melalui panggilan sistem.
  • Bekerja dengan stdin / stdout : readline, echo, print.

Selain itu, eksekusi diblokir pada penghitung waktu : sleep, usleep. Ini adalah operasi di mana kami secara eksplisit memberitahu utas untuk tertidur sebentar. PHP akan diam selama ini.

Asynchronous SQL Client


Tetapi PHP modern adalah bahasa tujuan umum, dan tidak hanya untuk web seperti PHP / FI pada tahun 1997. Oleh karena itu, kita dapat menulis klien SQL asinkron dari awal. Tugasnya bukan yang paling sepele, tetapi bisa dipecahkan.

public function execAsync(string $query, array $params = [])
{
    $socket = stream_socket_client('127.0.0.1:3306', ...);

    stream_set_blocking($socket, false);

    $data = $this->packBinarySQL($query, $params);
    
    socket_write($socket, $data, strlen($data));
}

Apa yang klien lakukan? Terhubung ke server SQL kami, menempatkan soket dalam mode non-blocking, mengemas permintaan dalam format biner yang dipahami oleh server SQL, menulis data ke soket.

Karena soket dalam mode non-blocking, operasi penulisan dari PHP cepat.

Tetapi apa yang akan kembali sebagai hasil dari operasi semacam itu? Kami tidak tahu apa yang akan ditanggapi oleh server SQL. Mungkin perlu waktu lama untuk menyelesaikan permintaan atau tidak sama sekali. Tetapi sesuatu harus dikembalikan? Jika kami menggunakan PDO dan memanggil updatekueri di SQL server, kami dikembalikan affected rows- jumlah baris diubah oleh kueri ini. Kami belum dapat mengembalikannya, oleh karena itu kami hanya menjanjikan pengembalian.

Janji


Ini adalah konsep dari dunia pemrograman asinkron.
Promise adalah objek pembungkus dari hasil operasi asinkron. Selain itu, hasil operasi masih belum diketahui oleh kami.
Sayangnya, tidak ada standar Janji tunggal, dan tidak mungkin untuk mentransfer standar langsung dari dunia JavaScript ke PHP.

Bagaimana Janji Bekerja


Karena belum ada hasil, kami hanya dapat membuat beberapa callbacks.



Ketika data tersedia, perlu untuk melakukan panggilan balik onResolve.



Jika kesalahan terjadi, panggilan balik akan dilakukan onRejectuntuk menangani kesalahan.



Antarmuka Promise terlihat seperti ini.

interface Promise
{
    const
        STATUS_PENDING = 0,
        STATUS_RESOLVED = 1,
        STATUS_REJECTED = 2
    ;

    public function onResolve(callable $callback);
    public function onReject(callable $callback);
    public function resolve($data);
    public function reject(\Throwable $error);
}

Janji memiliki status dan metode untuk mengatur panggilan balik dan mengisi ( resolve) Janji dengan data atau kesalahan ( reject). Tetapi ada perbedaan dan variasi. Metode dapat dipanggil secara berbeda, atau alih-alih metode terpisah untuk membuat panggilan balik, resolvedan rejectmungkin ada beberapa, seperti dalam AMPHP, misalnya.

Seringkali teknik untuk mengisi Janji resolvedan rejectmengeluarkan dalam objek terpisah fungsi penyimpanan tidak sinkron yang Ditangguhkan . Ini dapat dianggap sebagai semacam pabrik untuk Janji. Ini satu kali: satu Ditangguhkan membuat satu Janji.



Bagaimana cara menerapkan ini dalam klien SQL jika kita memutuskan untuk menulisnya sendiri?

Asynchronous SQL Client


Pertama, kami menciptakan Ditangguhkan, melakukan semua pekerjaan dengan soket, menuliskan data dan mengembalikan Janji - semuanya sederhana.

public function execAsync(string $query, array $params = [])
{
    $deferred = new Deferred;

    $socket = stream_socket_client('127.0.0.1:3306', ...);
    stream_set_blocking($socket, false);

    $data = $this->packBinarySQL($query, $params);
    socket_write($socket, $data, strlen($data));

    return $deferred->promise();
}

Ketika kita memiliki Janji, kita dapat, misalnya:

  • atur callback dan dapatkan affected rowsyang kembali kepada kami PDOConnection;
  • menangani kesalahan, tambahkan ke log;
  • Coba lagi permintaan jika server SQL merespons dengan kesalahan.

$promise = $this->execAsync($sql, $user->data());

$promise->onResolve(function (int $rows) {
    echo "Affected rows: {$rows}";
});

$promise->onReject(function (\Throwable $error) {
    log($error->getMessage());
});

Pertanyaannya tetap: kita mengatur panggilan balik, dan siapa yang akan menelepon resolvedan reject?

Perulangan acara


Ada konsep Event Loop - sebuah event loop . Ia dapat memproses pesan dalam lingkungan asinkron. Untuk I / O yang tidak sinkron, ini akan menjadi pesan dari OS yang siap dibaca atau ditulis oleh soket.

Bagaimana itu bekerja.

  • Klien memberi tahu Event Loop bahwa ia tertarik pada beberapa jenis soket.
  • Perulangan Kejadian polling OS melalui system call stream_select: apakah soket siap, semua data tertulis, adalah data yang berasal dari sisi lain.
  • Jika OS melaporkan bahwa soket tidak siap, diblokir, maka Event Loop mengulangi loop.
  • Ketika OS memberitahukan bahwa soket sudah siap, Event Loop mengembalikan kontrol ke klien dan mengaktifkan ( resolveatau reject) Janji.



Kami mengungkapkan konsep ini dalam kode: ambil kasus paling sederhana, hapus penanganan kesalahan dan nuansa lainnya, sehingga satu loop tak terbatas tetap ada. Di setiap iterasi, ini akan polling OS tentang soket yang siap untuk membaca atau menulis, dan memanggil panggilan balik untuk soket tertentu.

public static function run()
{
    while (true) {
        stream_select($readSockets, $writeSockets, null, 0);
        
        foreach ($readSockets as $i => $socket) {
            call_user_func(self::readCallbacks[$i], $socket);
        }

        // Do same for write sockets
    }
}

Kami melengkapi klien SQL kami. Kami memberi tahu Event Loop bahwa segera setelah data dari server SQL sampai pada soket yang kami tangani, kami perlu membawa Deferred ke status β€œselesai” dan mentransfer data dari soket ke Janji.

public function execAsync(string $query, array $params = [])
{
    $deferred = new Deferred;
    ...
    Loop::onReadable($socket, function ($socket) use ($deferred) {
        $deferred->resolve(socket_read($socket));
    });

    return $deferred->promise();
}

Event Loop dapat menangani I / O kami dan bekerja dengan soket . Apa lagi yang bisa dia lakukan?

  • JavaScript setTimeout setInterval β€” . N . Event Loop .
  • Event Loop . process control, .

Event Loop


Menulis Perulangan Acara Anda tidak hanya mungkin, tetapi juga perlu. Jika Anda ingin bekerja dengan asynchronous PHP, penting untuk menulis implementasi sederhana Anda sendiri untuk memahami cara kerjanya. Tetapi dalam produksi, kami, tentu saja, tidak akan menggunakannya, tetapi kami akan mengambil implementasi yang siap pakai: stabil, bebas kesalahan dan terbukti dalam pekerjaan.

Ada tiga implementasi utama.

ReactPHP . Proyek tertua, dimulai kembali di PHP 5.3. Sekarang versi minimum yang diwajibkan dari PHP adalah 5.3.8. Proyek ini mengimplementasikan standar Janji / A dari dunia JavaScript.

AMPHP . Implementasi inilah yang saya lebih suka gunakan. Persyaratan minimum adalah PHP 7.0, dan karena versi berikutnya sudah 7.3. Ini menggunakan coroutine di atas Janji.

Swoole. Ini adalah kerangka kerja Cina yang menarik di mana pengembang mencoba mem-port beberapa konsep dari dunia Go ke PHP. Dokumentasi dalam bahasa Inggris tidak lengkap, sebagian besar di GitHub dalam bahasa Cina. Jika Anda tahu bahasanya, silakan, tapi sejauh ini saya takut bekerja.



ReactPHP


Mari kita lihat seperti apa klien akan menggunakan ReactPHP untuk MySQL.

$connection = (new ConnectionFactory)->createLazyConnection();

$promise = $connection->query('UPDATE users SET ...');
$promise->then(
    function (QueryResult $command) {
        echo count($command->resultRows) . ' row(s) in set.';
    },
    function (Exception $error) {
        echo 'Error: ' . $error->getMessage();
    });

Semuanya hampir sama dengan yang kami tulis: kami membuat onnectiondan menjalankan permintaan. Kami dapat mengatur panggilan balik untuk memproses hasil (kembali affected rows):

    function (QueryResult $command) {
        echo count($command->resultRows) . ' row(s) in set.';
    },

dan panggilan balik untuk penanganan kesalahan:

    function (Exception $error) {
        echo 'Error: ' . $error->getMessage();
    });

Dari panggilan balik ini, Anda dapat membangun rantai yang sangat panjang, karena setiap hasil thendi ReactPHP juga mengembalikan Janji.

$promise
    ->then(function ($data) {
        return new Promise(...);
    })
    ->then(function ($data) {
        ...
    }, function ($error) {
        log($error);
    })
    ...

Ini adalah solusi untuk masalah yang disebut panggilan balik neraka. Sayangnya, dalam implementasi ReactPHP, ini mengarah ke masalah "Janji neraka", ketika 10-11 panggilan balik diperlukan untuk menghubungkan RabbitMQ dengan benar . Bekerja dengan kode seperti itu dan memperbaikinya sulit. Saya segera menyadari bahwa ini bukan milik saya dan beralih ke AMPHP.

Amphp


Proyek ini lebih muda dari ReactPHP dan mempromosikan konsep yang berbeda - coroutine . Jika Anda melihat bekerja dengan MySQL di AMPHP, Anda dapat melihat bahwa ini hampir sama dengan bekerja PDOConnectiondi PHP.

$pool = Mysql\pool("host=127.0.0.1 port=3306 db=test");

try {
    $result = yield $pool->query("UPDATE users SET ...");

    echo $result->affectedRows . ' row(s) in set.';
} catch (\Throwable $error) {
    echo 'Error: ' . $error->getMessage();
}

Di sini kita membuat kumpulan, menghubungkan dan menjalankan permintaan. Kami dapat menangani kesalahan melalui yang biasa try...catch, kami tidak perlu panggilan balik.

Tetapi sebelum panggilan tidak sinkron, kata kunci - muncul di sini yield.

Generator


Kata kunci yieldmengubah fungsi kami menjadi generator.

function generator($counter = 1)
{
    yield $counter++;

    echo "A";

    yield $counter;

    echo "B";

    yield ++$counter;
}

Segera setelah penerjemah PHP menemukan yieldfungsi dalam tubuh, ia menyadari bahwa itu adalah fungsi generator. Alih-alih mengeksekusi, objek kelas dibuat saat dipanggil Generator.

Generator mewarisi antarmuka iterator.

$generator = generator(1);

foreach ($generator as $value) {
    echo $value;
}

while ($generator->valid()) {
    echo $generator->current();

    $generator->next();
}

Dengan demikian, adalah mungkin untuk menjalankan siklus foreachdan whiledan lain-lain. Tetapi, yang lebih menarik, iterator memiliki metode currentdan next. Mari kita lalui mereka langkah demi langkah.

Jalankan fungsi kami generator($counter = 1). Kami menyebutnya metode generator current(). Nilai variabel akan dikembalikan $counter++.

Segera setelah kami menjalankan generator next(), kode akan menuju ke panggilan berikutnya di dalam generator yield. Seluruh bagian kode di antara keduanya yieldakan dieksekusi, dan itu keren. Terus memutar generator, kami mendapatkan hasilnya.

Coroutine


Tetapi generator memiliki fungsi yang lebih menarik - kita dapat mengirim data ke generator dari luar. Dalam hal ini, ini bukan generator, tetapi coroutine atau coroutine.

function printer() {  
    while (true) {     
        echo yield;       
    }                             
}                                

$print = printer();
$print->send('Hello');
$print->send(' PHPRussia');
$print->send(' 2019');
$print->send('!');

Di bagian kode ini, sangat menarik bahwa kode while (true)ini tidak akan memblokir aliran eksekusi, tetapi akan dieksekusi sekali. Kami mengirim data ke Corutin dan menerima 'Hello'. Terkirim lebih banyak - diterima 'PHPRussia'. Prinsipnya jelas.

Selain mengirim data ke generator, Anda dapat mengirim kesalahan dan memprosesnya dari dalam, yang nyaman.

function printer() {
    try {
        echo yield;
    } catch (\Throwable $e) {
        echo $e->getMessage();
    }
}

printer()->throw(new \Exception('Ooops...'));

Untuk meringkas. Corutin adalah komponen dari program yang mendukung penghentian dan melanjutkan eksekusi sambil mempertahankan kondisi saat ini . Corutin ingat tumpukan panggilannya, data di dalamnya, dan dapat menggunakannya di masa depan.

Generator dan Janji


Mari kita lihat antarmuka generator dan Promise.

class Generator
{
    public function send($data);
    public function throw(\Throwable $error);
}

class Promise
{
    public function resolve($data);
    public function reject(\Throwable $error);
}

Mereka terlihat sama, kecuali untuk nama metode yang berbeda. Kami dapat mengirim data dan melemparkan kesalahan ke generator dan Janji.

Bagaimana ini bisa digunakan? Mari kita menulis sebuah fungsi.

function recoil(\Generator $generator)
{
    $promise = $generator->current();

    $promise->onResolve(function($data) use ($generator) {
        $generator->send($data);
        recoil($generator);
    };

    $promise->onReject(function ($error) use ($generator) {
        $generator->throw($error);
        recoil($generator);
    });
}

Fungsi mengambil nilai saat ini dari generator: $promise = $generator->current();.

Saya sedikit melebih-lebihkan. Ya, kami harus memeriksa bahwa nilai saat ini yang dikembalikan kepada kami adalah semacam instanceofJanji. Jika demikian, maka kita bisa memintanya menelepon kembali. Secara internal mengirimkan data kembali ke generator ketika Promise berhasil dan secara rekursif memulai fungsinya recoil.

    $promise->onResolve(function($data) use ($generator) {
        $generator->send($data);
        recoil($generator);
    };

Hal yang sama dapat dilakukan dengan kesalahan. Jika Promise gagal, misalnya, server SQL berkata: "Terlalu banyak koneksi", maka kita dapat membuang kesalahan di dalam generator dan pergi ke langkah berikutnya.

Semua ini membawa kita pada konsep penting multitasking kooperatif.

Multitasking kooperatif


Ini adalah jenis multitasking, di mana tugas berikutnya dilakukan hanya setelah tugas saat ini secara eksplisit menyatakan dirinya siap memberikan waktu prosesor untuk tugas-tugas lain.

Saya jarang menemukan sesuatu yang sederhana, seperti bekerja dengan hanya satu database. Paling sering, dalam proses memperbarui pengguna, Anda perlu memperbarui data dalam database, dalam indeks pencarian, kemudian membersihkan atau memperbarui cache, dan kemudian mengirim 15 pesan lagi ke RabbitMQ. Di PHP, semuanya terlihat seperti ini.



Kami melakukan operasi satu per satu: kami memperbarui database, indeks, dan kemudian cache. Tetapi secara default, PHP memblokir operasi semacam itu (I / O), jadi jika Anda perhatikan lebih dekat, sebenarnya, semuanya demikian.



Pada bagian yang gelap kami memblokir. Mereka mengambil waktu paling banyak.

Jika kita bekerja dalam mode asinkron, maka bagian-bagian ini tidak ada, garis waktu eksekusi terputus-putus.



Anda bisa merekatkan semuanya dan membuat potongan satu per satu.



Untuk apa semua ini? Jika Anda melihat ukuran garis waktu, pada awalnya dibutuhkan banyak waktu, tetapi segera setelah kami merekatkannya, aplikasi akan dipercepat.

Konsep Event Loop dan multitasking kooperatif telah lama digunakan dalam berbagai aplikasi: Nginx, Node.js, Memcached, Redis. Semuanya digunakan di dalam Event Loop dan dibangun dengan prinsip yang sama.

Karena kami mulai berbicara tentang server web Nginx dan Node.js, mari kita ingat bagaimana proses permintaan dalam PHP terjadi.

Meminta pemrosesan dalam PHP


Browser mengirim permintaan, sampai ke server HTTP di belakangnya terdapat kumpulan aliran FPM. Salah satu utas menjalankan permintaan ini, menghubungkan kode kami dan mulai menjalankannya.



Ketika permintaan berikutnya tiba, utas FPM lain akan mengambilnya, sambungkan kode dan itu akan dieksekusi.

Ada beberapa keuntungan dari skema kerja ini .

  • Penanganan kesalahan sederhana . Jika ada yang tidak beres dan salah satu permintaan jatuh, kita tidak perlu melakukan apa pun - yang berikutnya akan datang, dan ini tidak akan memengaruhi pekerjaannya.
  • Kami tidak memikirkan ingatan . Kami tidak perlu membersihkan atau memantau memori. Atas permintaan berikutnya, semua memori akan dihapus.

Ini adalah skema keren yang bekerja di PHP sejak awal dan masih berfungsi dengan baik. Namun ada juga kekurangannya .

  • Batasi jumlah proses . Jika kami memiliki 50 utas FPM di server, maka segera setelah permintaan ke-51 tiba, itu akan menunggu sampai salah satu utas menjadi bebas.
  • Biaya untuk Beralih Konteks . OS mengalihkan permintaan di antara aliran FPM. Operasi tingkat prosesor ini disebut Context Switch. Itu mahal dan menjalankan sejumlah besar tindakan. Penting untuk menyimpan semua register, tumpukan panggilan, semua yang ada di prosesor, kemudian beralih ke proses lain, muat register dan tumpukan panggilannya, lakukan sesuatu di sana lagi, beralih lagi, simpan lagi ... Untuk waktu yang lama.

Mari kita mendekati pertanyaan secara berbeda - kita akan menulis server HTTP dalam PHP itu sendiri.

Server HTTP Asinkron




Itu bisa dilakukan. Kami telah belajar cara bekerja dengan soket dalam mode non-pemblokiran, dan koneksi HTTP adalah soket yang sama. Bagaimana tampilannya dan bekerja?

Ini adalah contoh memulai server HTTP dalam kerangka kerja AMPHP.

Loop::run(function () {
    $app = new Application();
    $app->bootstrap();

    $sockets = [Socket\listen('0.0.0.0:80')];

    $server = new Server($sockets, new CallableRequestHandler(
        function (Request $request) use ($app) {
            $response = yield $app->dispatch($request);

            return new Response(Status::OK, [], $response);
        })
    );

    yield $server->start();
});

Semuanya cukup sederhana: memuat Applicationdan membuat kumpulan soket (satu atau lebih).

Selanjutnya, kami memulai server kami, mengaturnya Handler, yang akan dieksekusi pada setiap permintaan dan mengirimkan permintaan ke kami Applicationuntuk mendapatkan tanggapan.

Hal terakhir yang harus dilakukan adalah memulai server yield $server->start();.

Dalam ReactPHP akan terlihat hampir sama, tetapi hanya akan ada 150 panggilan balik untuk opsi yang berbeda, yang sangat tidak nyaman.

Masalah


Ada beberapa masalah dengan asynchrony di PHP.

Kurangnya standar . Setiap kerangka kerja: Swoole, ReactPHP, atau AMPHP, mengimplementasikan antarmuka Promise sendiri, dan mereka tidak kompatibel.

AMPHP secara teori dapat berinteraksi dengan Promise dari ReactPHP, tetapi ada peringatan. Jika kode untuk ReactPHP tidak ditulis dengan sangat baik, dan di suatu tempat secara implisit memanggil atau membuat Perulangan Acara, maka ternyata dua Peristiwa Putaran akan berputar di dalamnya.

JavaScript memiliki standar Janji / A + yang relatif baik yang mengimplementasikan Guzzle. Akan lebih baik jika kerangka kerjanya mengikutinya. Tapi sejauh ini tidak.

Memori bocor. Ketika kita bekerja di PHP dalam mode FPM biasa, kita mungkin tidak memikirkan memori. Bahkan jika pengembang dari beberapa ekstensi lupa untuk menulis kode yang baik, lupa untuk menjalankan melalui Valgrind, dan di suatu tempat di dalam memori mengalir, maka tidak apa-apa - permintaan berikutnya akan dihapus dan akan mulai lagi. Tetapi dalam mode asinkron, Anda tidak mampu membelinya, karena cepat atau lambat kita akan jatuh OutOfMemoryException.

Dimungkinkan untuk diperbaiki, tetapi sulit dan menyakitkan. Dalam beberapa kasus, Xdebug membantu, dalam kasus lain, strace untuk mengurai kesalahan yang disebabkannya OutOfMemoryException.

Operasi pemblokiran . Sangat penting untuk tidak memblokir Event Loop ketika kita menulis kode asinkron. Aplikasi melambat begitu kita memblokir aliran eksekusi, masing-masing coroutine kita mulai berjalan lebih lambat.

Paket kelunik / loop-block akan membantu menemukan operasi seperti itu untuk AMPHP . Dia mengatur timer ke interval yang sangat kecil. Jika timer tidak berfungsi, maka kita diblokir di suatu tempat. Paket ini membantu dalam menemukan tempat pemblokiran, tetapi tidak selalu: memblokir di beberapa ekstensi mungkin tidak diperhatikan.

Dukungan Perpustakaan: Cassandra, Influx, ClickHouse . Masalah utama dari semua PHP asinkron adalah dukungan perpustakaan. Kita tidak bisa menggunakan biasa PDOConnection, RedisClientdriver lainnya untuk semua orang - kita perlu implementasi non-blocking. Mereka juga harus ditulis dalam PHP dalam mode non-blocking, karena driver C jarang menyediakan antarmuka yang dapat diintegrasikan ke dalam kode asinkron.

Pengalaman aneh yang saya dapatkan dengan driver untuk database Cassandra. Mereka menyediakan operasiExecuteAsync, GetAsyncdan lainnya, tetapi pada saat yang sama mereka mengembalikan objek Futuredengan metode tunggal getyang menghalangi. Ada peluang untuk mendapatkan sesuatu secara tidak sinkron, tetapi untuk menunggu hasilnya, kami masih akan memblokir seluruh Loop kami. Untuk melakukannya dengan cara yang berbeda, misalnya, melalui panggilan balik, itu tidak berfungsi. Saya bahkan menulis klien saya untuk Cassandra, karena kami menggunakannya dalam pekerjaan kami.

Ketikkan indikasi . Ini adalah masalah AMPHP dan corutin.

class UserRepository
{
    public function find(int $id): \Generator
    {
        $data = yield $this->db->query('SELECT ...', $id);

        return User::fill($data);
    }
}

Jika itu terjadi dalam suatu fungsi yield, maka itu menjadi generator. Pada titik ini, kami tidak lagi dapat menentukan tipe data pengembalian yang benar.

PHP 8


Apa yang menanti kita di PHP 8? Saya akan memberi tahu Anda tentang asumsi saya atau, lebih tepatnya, keinginan saya ( catatan editor: Dmitry Stogov tahu apa yang sebenarnya akan muncul di PHP 8 ).

Loop Acara Ada kemungkinan itu akan muncul, karena pekerjaan sedang berlangsung untuk membawa Perulangan Acara dalam beberapa bentuk ke kernel. Jika ini terjadi, kami akan memiliki fungsi await, seperti dalam JavaScript atau C #, yang memungkinkan kami menunggu hasil operasi asinkron di tempat tertentu. Dalam hal ini, kita tidak memerlukan ekstensi apa pun, semuanya akan bekerja secara tidak sinkron pada level kernel.


class UserRepository
{
    public function find(int $id): Promise<User>
    {
        $data = await $this->db->query('SELECT ...', $id);

        return User::fill($data);
    }
}


Generik Go menunggu Generik, kami menunggu Generik, semua orang menunggu Generik.

class UserRepository
{
    public function find(int $id): Promise<User>
    {
        $data = yield $this->db->query('SELECT ...', $id);

        return User::fill($data);
    }
}

Tetapi kami tidak menunggu Generics untuk koleksi, tetapi untuk menunjukkan bahwa hasil dari Janji akan persis menjadi objek Pengguna.

Mengapa semua ini?

Untuk kecepatan dan kinerja.
PHP adalah bahasa di mana sebagian besar operasi terikat I / O. Kami jarang menulis kode yang secara signifikan terkait dengan perhitungan di prosesor. Kemungkinan besar, kami bekerja dengan soket: kita perlu membuat permintaan ke database, membaca sesuatu, mengembalikan respons, mengirim file. Asynchrony memungkinkan Anda untuk mempercepat kode tersebut. Jika kita melihat waktu respons rata-rata untuk 1000 permintaan, kita dapat mempercepat sekitar 8 kali, dan dengan 10.000 permintaan hampir 6!

13 Mei 2020, kami akan berkumpul untuk kedua kalinya di PHP Rusia untuk membahas bahasa, perpustakaan, dan kerangka kerja, cara untuk meningkatkan produktivitas dan perangkap solusi hype. Kami telah menerima 4 laporan pertama , tetapi Call for Papers masih akan datang. Terapkan jika Anda ingin berbagi pengalaman dengan komunitas.

Source: https://habr.com/ru/post/undefined/


All Articles