Mengapa php single threaded?

class workerThread extends Thread {
public function __construct($i){
  $this->i=$i;
}
_

public function run(){
  while(true){
   echo $this->i;
   sleep(1);
  }
}
}

for($i=0;$i<50;$i++){
$workers[$i]=new workerThread($i);
$workers[$i]->start();
}

?>

Selama beberapa minggu terakhir, saya mendapati diri saya menghadapi masalah konkurensi dan diskusi di berbagai tempat. Saya memahami prinsip dasar konkurensi, tetapi saya belum memiliki pengalaman langsung sebanyak yang saya inginkan, jadi saya memutuskan untuk belajar dengan menjalankan beberapa eksperimen dan menulis tentangnya

Eksperimen 1. Server bawaan PHP tidak memiliki konkurensi nol

PHP hadir dengan server bawaan. php -S localhost:8000 -t myapp/ akan memulai server sederhana untuk aplikasi Anda. Beberapa kerangka menyediakan pembungkus di sekitar ini (php artisan serve). Tetapi semuanya menyebutnya sebagai server pengembangan, artinya Anda tidak boleh menggunakannya dalam produksi. Salah satu alasannya adalah single-threaded secara default. Ini berjalan sebagai utas tunggal, sehingga setiap permintaan dilayani oleh utas yang sama. Dan karena PHP bersifat sinkron, itu artinya setiap permintaan harus menunggu permintaan sebelumnya selesai

Saya sudah mengetahui hal ini sejak lama, tetapi saya ingin membuktikannya pada diri saya sendiri, hanya untuk bersenang-senang. Jadi saya melakukan tes cepat

Saya membuat middleware global yang berjalan pada setiap permintaan dan log permintaan mulai dan selesai

class Check
{
    public function handle(Request $request, Closure $next)
    {
        ray("Starting: ". $request->fullUrl());
        sleep(10);        
        $response = $next($request);
        ray("Finished: ". $request->fullUrl())->green();
        return $response;
    }
}

Kemudian saya membuka beberapa tab browser dan memuat ulang secara bersamaan. Benar saja, setiap permintaan mencatat "Mulai" dan "Selesai" dengan jarak 10 detik, satu demi satu. Tidak pernah ada permintaan yang tumpang tindih

Mengapa php single threaded?

Selamat. Bayangkan betapa sulitnya ini dalam produksi. Setiap pengguna harus bergiliran mengakses situs web, karena tidak dapat menangani banyak permintaan sekaligus

Kenapa ini?

Alasannya adalah model eksekusi sinkron PHP. PHP dimaksudkan untuk mati. Model eksekusi PHP sangat mudah. mulai, jalankan perintah Anda, lalu keluar. Tidak ada pengulangan peristiwa, utas pekerja, atau coroutine yang perlu dikhawatirkan. Semuanya dieksekusi satu demi satu. Inilah salah satu alasan mengapa ini adalah bahasa yang sederhana untuk membungkus kepala Anda

Tetapi ketika Anda menggabungkan kekurangan asinkronisitas ini dengan server single-threaded, Anda mendapatkan penanganan permintaan yang tidak "berskala"

Mengapa php single threaded?

Ke samping. PHP 7. 4 memperkenalkan variabel

const requestHandler = function (req, res) {
    console.time(req.url);
    console.log(`Starting: ${req.url}`);
    let i = 0;
    while (i < 100_000) {
        const data = fs.readFileSync(__filename);
        i++;
    }
    res.writeHead(200);
    res.end('Hello, World!');
    console.log(`Finished: ${req.url}`);
    console.timeEnd(req.url);
}

const server = http.createServer(requestHandler);
server.listen(3040);
0 eksperimental yang memungkinkan server menangani permintaan bersamaan, tetapi itu di luar jangkauan kami saat ini

Bagaimana dengan Node.js?. js?

Node. js juga berjalan sebagai server single-threaded. Tapi simpul. js hadir dengan asinkronisitas bawaan dan I/O berbasis peristiwa. Jadi apakah masih memiliki masalah ini? . iya dan tidak

Pertama, penangan permintaan di Node. js sebenarnya disebut secara sinkron (satu demi satu). Ini penting, karena banyak orang berasumsi bahwa peristiwa dipicu di Node.js. js ditangani secara asinkron. Mereka tidak. Node. js server menggunakan loop yang mirip dengan server PHP di atas. pilih permintaan, jalankan penangannya, pindah ke permintaan berikutnya

Mengapa php single threaded?

Saya menjalankan percobaan lain untuk memverifikasi ini

const requestHandler = function (req, res) {
    console.time(req.url);
    console.log(`Starting: ${req.url}`);
    let i = 0;
    while (i < 100_000) {
        const data = fs.readFileSync(__filename);
        i++;
    }
    res.writeHead(200);
    res.end('Hello, World!');
    console.log(`Finished: ${req.url}`);
    console.timeEnd(req.url);
}

const server = http.createServer(requestHandler);
server.listen(3040);
_

Kode ini benar-benar sinkron, seperti dengan PHP. Saya membaca dari file 100.000 kali, hanya untuk membuat beberapa pekerjaan palsu untuk dilakukan server yang membutuhkan waktu

Selanjutnya, saya menjalankan tiga permintaan pada waktu yang bersamaan, dan

Starting: /
Finished: /
/: 4.414s
Starting: /ping
Finished: /ping
/ping: 4.269s
Starting: /dfd
Finished: /dfd
/dfd: 4.281s

Nah, apa kamu tahu? . Setiap permintaan harus menunggu yang pertama selesai sebelum dapat dilayani

Mari kita ubah sedikit

const readFileManyTimes = (i, callback) => {
    fs.readFile(__filename, (err, data) => {
        if (i < 100_000) {
            readFileManyTimes(++i, callback);
        } else {
            callback();
        }
    });
}
const requestHandler = function (req, res) {
    console.time(req.url);
    console.log(`Starting: ${req.url}`);
    readFileManyTimes(0, () => {
        res.writeHead(200);
        res.end('Hello, World!');
        console.log(`Finished: ${req.url}`);
        console.timeEnd(req.url);
    });
}
_

Kami masih membaca dari file 100.000 kali sebelum mengirimkan respons, tetapi sekarang kami melakukannya secara asinkron

Kali ini, hasilnya berbeda. Permintaan ditangani secara bersamaan

Starting: /
Starting: /ping
Starting: /dfd
Finished: /
/: 11.415s
Finished: /ping
/ping: 11.415s
Finished: /dfd
/dfd: 11.420s

Jika Anda terbiasa dengan konkurensi, hasil ini seharusnya tidak terlalu mengejutkan. Konkurensi tidak berarti melakukan banyak tugas secara paralel. Sebaliknya, ini melibatkan sejumlah strategi yang memungkinkan Anda dengan cerdas beralih di antara tugas-tugas tersebut sehingga Anda terlihat menanganinya pada saat yang bersamaan. Manfaat utama konkurensi adalah tidak membuat satu orang menunggu terlalu lama

Bagi yang kurang informasi, ini mungkin terlihat seperti Node. js menjalankan ketiga permintaan sekaligus, tetapi sebenarnya tidak. Perbedaan utamanya adalah pembacaan file asinkron, yang memungkinkan Node. js untuk menyerahkannya ke sistem operasi dan beralih ke tugas lain

So, no. Node. js tidak akan memiliki batasan konkurensi yang sama dengan PHP untuk sebagian besar aplikasi web, karena sebagian besar operasi terikat I/O-nya asinkron secara default, tetapi jika Anda menulis kode sinkron, Anda akan mendapatkan hasil sinkron. Pada contoh pertama, penggunaan pembacaan file sinkron kami mengubah konkurensi kami menjadi nol

Mengetahui hal ini dapat membantu Anda menghindari beberapa bug yang membuat frustrasi dan masalah debug. Misalnya, pengguna Anda mungkin mengeluh bahwa situs Anda lambat, tetapi ketika Anda menambahkan beberapa kode untuk mengukur waktu respons Anda, ini memberi tahu Anda bahwa permintaan sedang ditangani dalam satu detik atau kurang. Karena setiap permintaan hanya ditangani setelah yang sebelumnya, jika kode Anda sinkron, tidak ada cara untuk mencatat waktu yang tepat saat permintaan dikirim. Solusi untuk itu adalah memantau waktu respons Anda dari luar, bukan dari dalam aplikasi Anda

Konkurensi vs latensi

Ini sedikit pengalihan dari maksud awal saya, tetapi hal yang menarik untuk dilihat di sini adalah pertukaran antara konkurensi dan latensi. Dalam contoh sinkron, setiap permintaan ditangani dalam waktu sekitar 4 detik

Dalam versi bersamaan, setiap permintaan memakan waktu sekitar 11. 4 detik. Itu lebih banyak. Tapi ada faktor penting. waktu tunggu. Dalam versi sinkron, setiap permintaan harus menunggu permintaan sebelumnya sebelum dapat ditangani. Jadi sementara itu ditangani hanya dalam beberapa detik dari akhir kami, pengguna mungkin sudah menunggu satu menit atau lebih. Untuk mendapatkan waktu respons sebenarnya dari sisi pengguna, kita perlu menambahkan waktu yang dihabiskan untuk menunggu permintaan sebelumnya

PermintaanDitangani dalam Waktu tungguWaktu respons aktual/4. 4s04. 4s/ping4. 3s4. 4s8. 7s/dfd4. 3s8. 7s13s

Jadi satu orang yang tidak beruntung harus menunggu 13 detik. Perbedaan antara versi bersamaan dan sinkron di sini tidak banyak karena kami hanya menguji dengan 3 permintaan bersamaan. Bayangkan jika kita memiliki 50 permintaan pada saat yang sama, dengan setiap permintaan memerlukan waktu 4 detik. Itu berarti kita akan memiliki waktu respons sebesar (4 * 50) = 200 detik. Saya menguji ini dengan autocannon

❯ autocannon --connections 50 --amount 50 --timeout 10000 http://localhost:3040
Running 50 requests test @ http://localhost:3040
50 connections

50 requests in 208.69s, 7.7 kB read
_

Benar saja, permintaan terakhir ditangani dalam 4 detik, tetapi butuh 208 detik sejak permintaan dikirim hingga akhirnya ditangani

Inilah yang terjadi dengan versi bersamaan

❯ autocannon --connections 50 --amount 50 --timeout 10000 http://localhost:3040
Running 50 requests test @ http://localhost:3040
50 connections

50 requests in 122.73s, 7.7 kB read

Dalam hal ini, kami telah menghemat waktu tunggu keseluruhan—pengguna tidak perlu menunggu lebih dari 122 detik—tetapi kami telah kehilangan waktu respons rata-rata kami—semua orang sekarang harus menunggu selama 122 detik. Jadi, bahkan konkurensi bukanlah peluru perak—konkurensi membantu kami menangani beberapa permintaan sekaligus, tetapi membutuhkan waktu lebih lama karena kami melakukan banyak hal sekaligus

Tetap saja, 122 detik adalah waktu yang sangat lama untuk mengharapkan pengguna menunggu tanggapan. Tetapi Anda tidak perlu khawatir tentang itu. Di dunia nyata, API Anda harus mengembalikan respons dalam satu detik, sehingga penyiapan bersamaan akan dapat menangani 50 permintaan dalam beberapa detik

Mengatasi keterbatasan

Menangani banyak permintaan adalah persyaratan untuk aplikasi web apa pun, tetapi terlebih lagi jika Anda menjalankan aplikasi yang sangat interaktif seperti server websockets. Memaksa setiap koneksi untuk menunggu yang sebelumnya kemungkinan besar akan menyebabkan waktu soket habis sebelum Anda mendapatkannya. Dalam konteks non-web (seperti game), interaksi pengguna akan macet jika setiap interaksi dan tindakan yang ditimbulkannya harus diproses sebelum hal lain dapat dilakukan

Jadi bagaimana kita mengatasi batasan single-threaded ini dan mencapai konkurensi (lebih baik)?

Jawaban langsung. menambahkan lebih banyak benang. Kami dapat menambah jumlah utas dengan menambahkan lebih banyak server. Jika saya memulai beberapa PHP atau Node. js server dengan penyeimbang beban yang mendistribusikan permintaan di antara mereka, maka saya akan dapat menangani lebih banyak permintaan secara bersamaan

Memutar proses baru bisa menjadi operasi yang mahal. Server khusus seperti Apache dan Nginx melakukan ini dengan lebih efisien—mereka menangani pembuatan dan pengelolaan proses atau utas untuk setiap permintaan. Inilah bacaan yang bagus tentang bagaimana Nginx melakukannya

Jika Anda masih ingin mencapai konkurensi dalam server PHP, ada beberapa proyek yang mencoba menyediakan ini, yang paling terkenal adalah ReactPHP dan Amp. Laravel Octane yang baru dirilis juga menyediakan ini, dengan API yang jauh lebih bersih dibandingkan proyek lain, Swoole

Inilah tampilan server web bersamaan dengan Amp

use Amp\Http\Server\RequestHandler\CallableRequestHandler;
use Amp\Http\Server\HttpServer;
use Amp\Http\Server\Request;
use Amp\Http\Server\Response;
use Amp\Http\Status;
use Amp\Socket\Server;
use Psr\Log\NullLogger;
use Amp\Loop;

Loop::run(function () {
    $sockets = [
        Server::listen("0.0.0.0:8080"),
    ];

    $server = new HttpServer($sockets, new CallableRequestHandler(function (Request $request) {
        ray("Starting: ". $request->getUri());
        $file = yield \Amp\File\get(__FILE__);
        ray("Finished: ". $request->getUri())->green();
        return new Response(Status::OK, [
            "content-type" => "text/plain; charset=utf-8"
        ], "Hello, World!");
    }), new NullLogger);

    echo "Server running at http://127.0.0.1:8080\n";
    yield $server->start();
});
_

Yap, permintaan tumpang tindih. ✅

Mengapa php single threaded?


Konkurensi hadir dengan tantangannya sendiri. Ini memperumit banyak hal. Kode Anda harus menyadari bahwa itu akan dijalankan secara bersamaan, jika tidak, Anda akan menjadi korban banyak bug. Dan ada seluruh kelas masalah konkurensi dalam ilmu komputer, yang telah dipelajari dan ditangani orang selama beberapa dekade. Tapi itu sangat penting dalam memberikan pengalaman pengguna yang baik, dan saya berharap untuk menjadi lebih baik dalam memberikan alasan tentang hal itu seiring berjalannya waktu

Saya senang menjalankan eksperimen ini untuk memverifikasi beberapa asumsi saya tentang platform yang saya gunakan, dan saya harap Anda senang membaca. Bereksperimen adalah cara yang bagus untuk belajar. Saya merekomendasikan untuk menjalankan eksperimen sendiri, dan bahkan merancang eksperimen Anda sendiri


Hei👋. Saya menulis tentang tantangan rekayasa perangkat lunak yang menarik. Ingin mendapat pembaruan ketika saya menerbitkan posting baru? . aplikasi/blog. shalvah. Saya

(Pengakuan. Saya membangun Tentakel. ✋ Ini membantu Anda menjaga kotak masuk tetap bersih dengan menggabungkan blog favorit Anda menjadi satu buletin mingguan. )

Apakah multithreading dimungkinkan dalam PHP?

Aplikasi PHP, tidak diragukan lagi bekerja secara efektif dengan kemampuan multithreading . Multithreading adalah sesuatu yang mirip dengan multitasking, tetapi memungkinkan untuk memproses banyak pekerjaan sekaligus, bukan pada banyak proses.

Mengapa single threaded lebih baik?

Keuntungan nyata dari pendekatan single-threaded adalah bahwa hal itu meminimalkan waktu eksekusi aplikasi individual (khususnya aplikasi yang sudah ada sebelumnya atau aplikasi lawas .

Mengapa browser berulir tunggal?

Secara default, browser menggunakan thread tunggal untuk menjalankan semua JavaScript di halaman Anda, serta untuk melakukan tata letak, reflow, dan pengumpulan sampah. This means that long-running JavaScript functions can block the thread, leading to an unresponsive page and a bad user experience.

Mengapa single threaded lebih baik daripada multi

Perbedaan utama antara utas tunggal dan multi utas di Java adalah bahwa utas tunggal menjalankan tugas suatu proses sementara dalam multi-utas, banyak . Proses adalah program yang sedang dieksekusi. Pembuatan proses adalah tugas yang memakan sumber daya.