Jadual Kandungan
Kata Pengantar
Apakah itu coroutine?
Pelaksanaan PHP coroutine
Objek boleh lelar
Penjana
Coroutine PHP
0)生成器正确使用
1)Task实现
2)Scheduler实现
3)协程堆栈
4)PHP7中yield from关键字
总结
Rumah pembangunan bahagian belakang PHP7 Menganalisis cara coroutine dilaksanakan di bawah PHP7

Menganalisis cara coroutine dilaksanakan di bawah PHP7

Jan 22, 2022 am 10:49 AM
php

Artikel ini diperkenalkan oleh ruangan tutorial PHP7 untuk memperkenalkan kepada anda cara melaksanakan coroutine di bawah PHP7 Saya harap ia dapat membantu rakan yang memerlukan.

Kata Pengantar

Saya percaya semua orang pernah mendengar tentang konsep "coroutine".

Tetapi sesetengah pelajar tidak memahami konsep ini, dan tidak tahu cara melaksanakannya, cara menggunakannya dan di mana untuk menggunakannya, Sesetengah orang berpendapat bahawa hasil adalah coroutine!

Saya sentiasa percaya bahawa jika anda tidak dapat menyatakan perkara pengetahuan dengan tepat, saya boleh fikir anda tidak faham.

Jika anda pernah belajar tentang menggunakan PHP untuk melaksanakan coroutine sebelum ini, anda mesti pernah membaca artikel Brother Niao: Menggunakan coroutines untuk melaksanakan penjadualan pelbagai tugas dalam PHP | Wind and Snow Corner

artikel Brother Niao telah diterjemahkan daripada pengarang asing Terjemahannya ringkas dan jelas, dan contoh khusus juga diberikan.

Tujuan menulis artikel ini adalah untuk membuat suplemen yang lebih lengkap kepada artikel Saudara Niao Lagipun, beberapa asas pelajar tidak cukup baik dan mereka keliru.

Apakah itu coroutine?

Mula-mula, mari kita fikirkan apa itu coroutine.

Anda mungkin pernah mendengar tentang konsep "proses" dan "benang".

Proses ialah contoh berjalan bagi fail boleh laku binari dalam ingatan komputer, sama seperti fail .exe anda ialah kelas dan prosesnya ialah tika baharu.

Proses ialah unit asas peruntukan sumber dan penjadualan dalam sistem komputer (jangan risau tentang proses urutan dalam unit penjadualan Setiap CPU hanya boleh memproses satu proses pada masa yang sama).

Konkurensi yang dipanggil hanya kelihatan seperti CPU boleh mengendalikan beberapa perkara pada masa yang sama Untuk CPU teras tunggal, ia sebenarnya bertukar antara proses yang berbeza dengan sangat cepat.

Penukaran proses memerlukan panggilan sistem CPU perlu menyimpan pelbagai maklumat tentang proses semasa, dan CPUCache juga akan dimusnahkan.

Jadi jika anda tidak boleh menukar proses, jangan lakukan melainkan anda perlu.

Jadi bagaimana untuk mencapai "Penukaran proses tidak dilakukan melainkan perlu"?

Pertama sekali, syarat untuk proses ditukar adalah: pelaksanaan proses selesai, potongan masa CPU yang diperuntukkan kepada proses tamat, gangguan berlaku dalam sistem yang perlu diproses, atau proses sedang menunggu sumber yang diperlukan (penyekatan proses), dsb. Kalau difikirkan, memang tak ada apa yang nak diperkatakan dalam situasi-situasi sebelum ini, tetapi jika anda menyekat dan menunggu, adakah ianya membazir?

Malah, jika ia disekat, terdapat tempat lain yang boleh dilaksanakan untuk program kami dilaksanakan, jadi kami tidak perlu menunggu dengan bodoh!

Jadi ada benang.

Pemahaman ringkas tentang benang ialah "proses mikro" yang secara khusus menjalankan fungsi (aliran logik).

Jadi kita boleh menggunakan benang untuk menjelmakan fungsi yang boleh dijalankan serentak semasa proses menulis atur cara.

Terdapat dua jenis utas, satu diurus dan dijadualkan oleh kernel.

Kami mengatakan bahawa selagi kernel terlibat dalam pengurusan dan penjadualan, kosnya sangat tinggi. Benang jenis ini sebenarnya menyelesaikan masalah bahawa apabila benang yang sedang dilaksanakan dalam proses menghadapi sekatan, kita boleh menjadualkan benang boleh jalan yang lain untuk dijalankan, tetapi ia masih dalam proses yang sama, jadi tiada penukaran proses.

Terdapat satu lagi jenis utas, yang penjadualannya diuruskan oleh pengaturcara yang menulis program sendiri dan tidak dapat dilihat oleh kernel. Benang jenis ini dipanggil "benang ruang pengguna".

Coroutine boleh difahami sebagai sejenis benang ruang pengguna.

Coroutine mempunyai beberapa ciri:

  • Kolaborasi, kerana ia adalah strategi penjadualan yang ditulis oleh pengaturcara sendiri, ia bertukar melalui kerjasama dan bukannya preemption
  • Penciptaan lengkap, penukaran dan pemusnahan dalam mod pengguna
  • ⚠️ Dari perspektif pengaturcaraan, idea coroutine pada asasnya ialah hasil aktif dan resume Mekanisme aliran kawalan
  • penjana sering digunakan untuk melaksanakan coroutine

Setelah berkata ini, anda harus memahami konsep asas coroutine, bukan?

Pelaksanaan PHP coroutine

Langkah demi langkah, bermula daripada menerangkan konsep!

Objek boleh lelar

PHP5 menyediakan cara untuk mentakrifkan objek supaya ia boleh dilalui melalui senarai sel, seperti menggunakan pernyataan foreach.

Jika anda ingin melaksanakan objek boleh lelar, anda perlu melaksanakan antara muka Iterator:

<?php
class MyIterator implements Iterator
{
    private $var = array();

    public function __construct($array)
    {
        if (is_array($array)) {
            $this->var = $array;
        }
    }

    public function rewind() {
        echo "rewinding\n";
        reset($this->var);
    }

    public function current() {
        $var = current($this->var);
        echo "current: $var\n";
        return $var;
    }

    public function key() {
        $var = key($this->var);
        echo "key: $var\n";
        return $var;
    }

    public function next() {
        $var = next($this->var);
        echo "next: $var\n";
        return $var;
    }

    public function valid() {
        $var = $this->current() !== false;
        echo "valid: {$var}\n";
        return $var;
    }
}

$values = array(1,2,3);
$it = new MyIterator($values);

foreach ($it as $a => $b) {
    print "$a: $b\n";
}
Salin selepas log masuk

Penjana

Boleh dikatakan bahawa untuk mempunyai objek yang boleh menjadi foreachUntuk melintasi objek, anda perlu melaksanakan sekumpulan kaedah Kata kunci yield adalah untuk memudahkan proses ini. Penjana

menyediakan cara yang lebih mudah untuk melaksanakan lelaran objek mudah Berbanding dengan mentakrifkan kelas untuk melaksanakan antara muka Iterator, overhed prestasi dan kerumitan sangat berkurangan.

<?php
function xrange($start, $end, $step = 1) {
    for ($i = $start; $i <= $end; $i += $step) {
        yield $i;
    }
}
 
foreach (xrange(1, 1000000) as $num) {
    echo $num, "\n";
}
Salin selepas log masuk

Ingat, jika yield digunakan dalam fungsi, ia adalah penjana Tidak berguna untuk memanggilnya secara langsung.

Jadi, yield ialah yield Lain kali sesiapa yang mengatakan yield adalah coroutine, saya pasti akan melayan anda xxxx.

Coroutine PHP

Seperti yang dinyatakan sebelum ini semasa memperkenalkan coroutine, coroutine memerlukan pengaturcara untuk menulis mekanisme penjadualan sendiri. Mari lihat cara menulis mekanisme ini.

0)生成器正确使用

既然生成器不能像函数一样直接调用,那么怎么才能调用呢?

方法如下:

  1. foreach他
  2. send($value)
  3. current / next...

1)Task实现

Task就是一个任务的抽象,刚刚我们说了协程就是用户空间线程,线程可以理解就是跑一个函数。

所以Task的构造函数中就是接收一个闭包函数,我们命名为coroutine

/**
 * Task任务类
 */
class Task
{
    protected $taskId;
    protected $coroutine;
    protected $beforeFirstYield = true;
    protected $sendValue;

    /**
     * Task constructor.
     * @param $taskId
     * @param Generator $coroutine
     */
    public function __construct($taskId, Generator $coroutine)
    {
        $this->taskId = $taskId;
        $this->coroutine = $coroutine;
    }

    /**
     * 获取当前的Task的ID
     * 
     * @return mixed
     */
    public function getTaskId()
    {
        return $this->taskId;
    }

    /**
     * 判断Task执行完毕了没有
     * 
     * @return bool
     */
    public function isFinished()
    {
        return !$this->coroutine->valid();
    }

    /**
     * 设置下次要传给协程的值,比如 $id = (yield $xxxx),这个值就给了$id了
     * 
     * @param $value
     */
    public function setSendValue($value)
    {
        $this->sendValue = $value;
    }

    /**
     * 运行任务
     * 
     * @return mixed
     */
    public function run()
    {
        // 这里要注意,生成器的开始会reset,所以第一个值要用current获取
        if ($this->beforeFirstYield) {
            $this->beforeFirstYield = false;
            return $this->coroutine->current();
        } else {
            // 我们说过了,用send去调用一个生成器
            $retval = $this->coroutine->send($this->sendValue);
            $this->sendValue = null;
            return $retval;
        }
    }
}
Salin selepas log masuk

2)Scheduler实现

接下来就是Scheduler这个重点核心部分,他扮演着调度员的角色。

/**
 * Class Scheduler
 */
Class Scheduler
{
    /**
     * @var SplQueue
     */
    protected $taskQueue;
    /**
     * @var int
     */
    protected $tid = 0;

    /**
     * Scheduler constructor.
     */
    public function __construct()
    {
        /* 原理就是维护了一个队列,
         * 前面说过,从编程角度上看,协程的思想本质上就是控制流的主动让出(yield)和恢复(resume)机制
         * */
        $this->taskQueue = new SplQueue();
    }

    /**
     * 增加一个任务
     *
     * @param Generator $task
     * @return int
     */
    public function addTask(Generator $task)
    {
        $tid = $this->tid;
        $task = new Task($tid, $task);
        $this->taskQueue->enqueue($task);
        $this->tid++;
        return $tid;
    }

    /**
     * 把任务进入队列
     *
     * @param Task $task
     */
    public function schedule(Task $task)
    {
        $this->taskQueue->enqueue($task);
    }

    /**
     * 运行调度器
     */
    public function run()
    {
        while (!$this->taskQueue->isEmpty()) {
            // 任务出队
            $task = $this->taskQueue->dequeue();
            $res = $task->run(); // 运行任务直到 yield

            if (!$task->isFinished()) {
                $this->schedule($task); // 任务如果还没完全执行完毕,入队等下次执行
            }
        }
    }
}
Salin selepas log masuk

这样我们基本就实现了一个协程调度器。

你可以使用下面的代码来测试:

<?php
function task1() {
    for ($i = 1; $i <= 10; ++$i) {
        echo "This is task 1 iteration $i.\n";
        yield; // 主动让出CPU的执行权
    }
}
 
function task2() {
    for ($i = 1; $i <= 5; ++$i) {
        echo "This is task 2 iteration $i.\n";
        yield; // 主动让出CPU的执行权
    }
}
 
$scheduler = new Scheduler; // 实例化一个调度器
$scheduler->addTask(task1()); // 添加不同的闭包函数作为任务
$scheduler->addTask(task2());
$scheduler->run();
Salin selepas log masuk

关键说下在哪里能用得到PHP协程。

function task1() {
        /* 这里有一个远程任务,需要耗时10s,可能是一个远程机器抓取分析远程网址的任务,我们只要提交最后去远程机器拿结果就行了 */
        remote_task_commit();
        // 这时候请求发出后,我们不要在这里等,主动让出CPU的执行权给task2运行,他不依赖这个结果
        yield;
        yield (remote_task_receive());
        ...
}
 
function task2() {
    for ($i = 1; $i <= 5; ++$i) {
        echo "This is task 2 iteration $i.\n";
        yield; // 主动让出CPU的执行权
    }
}
Salin selepas log masuk

这样就提高了程序的执行效率。

关于『系统调用』的实现,鸟哥已经讲得很明白,我这里不再说明。

3)协程堆栈

鸟哥文中还有一个协程堆栈的例子。

我们上面说过了,如果在函数中使用了yield,就不能当做函数使用。

所以你在一个协程函数中嵌套另外一个协程函数:

<?php
function echoTimes($msg, $max) {
    for ($i = 1; $i <= $max; ++$i) {
        echo "$msg iteration $i\n";
        yield;
    }
}
 
function task() {
    echoTimes(&#39;foo&#39;, 10); // print foo ten times
    echo "---\n";
    echoTimes(&#39;bar&#39;, 5); // print bar five times
    yield; // force it to be a coroutine
}
 
$scheduler = new Scheduler;
$scheduler->addTask(task());
$scheduler->run();
Salin selepas log masuk

这里的echoTimes是执行不了的!所以就需要协程堆栈。

不过没关系,我们改一改我们刚刚的代码。

把Task中的初始化方法改下,因为我们在运行一个Task的时候,我们要分析出他包含了哪些子协程,然后将子协程用一个堆栈保存。(C语言学的好的同学自然能理解这里,不理解的同学我建议去了解下进程的内存模型是怎么处理函数调用)

 /**
     * Task constructor.
     * @param $taskId
     * @param Generator $coroutine
     */
    public function __construct($taskId, Generator $coroutine)
    {
        $this->taskId = $taskId;
        // $this->coroutine = $coroutine;
        // 换成这个,实际Task->run的就是stackedCoroutine这个函数,不是$coroutine保存的闭包函数了
        $this->coroutine = stackedCoroutine($coroutine); 
    }
Salin selepas log masuk

当Task->run()的时候,一个循环来分析:

/**
 * @param Generator $gen
 */
function stackedCoroutine(Generator $gen)
{
    $stack = new SplStack;

    // 不断遍历这个传进来的生成器
    for (; ;) {
        // $gen可以理解为指向当前运行的协程闭包函数(生成器)
        $value = $gen->current(); // 获取中断点,也就是yield出来的值

        if ($value instanceof Generator) {
            // 如果是也是一个生成器,这就是子协程了,把当前运行的协程入栈保存
            $stack->push($gen);
            $gen = $value; // 把子协程函数给gen,继续执行,注意接下来就是执行子协程的流程了
            continue;
        }

        // 我们对子协程返回的结果做了封装,下面讲
        $isReturnValue = $value instanceof CoroutineReturnValue; // 子协程返回`$value`需要主协程帮忙处理
        
        if (!$gen->valid() || $isReturnValue) {
            if ($stack->isEmpty()) {
                return;
            }
            // 如果是gen已经执行完毕,或者遇到子协程需要返回值给主协程去处理
            $gen = $stack->pop(); //出栈,得到之前入栈保存的主协程
            $gen->send($isReturnValue ? $value->getValue() : NULL); // 调用主协程处理子协程的输出值
            continue;
        }

        $gen->send(yield $gen->key() => $value); // 继续执行子协程
    }
}
Salin selepas log masuk

然后我们增加echoTime的结束标示:

class CoroutineReturnValue {
    protected $value;
 
    public function __construct($value) {
        $this->value = $value;
    }
     
    // 获取能把子协程的输出值给主协程,作为主协程的send参数
    public function getValue() {
        return $this->value;
    }
}

function retval($value) {
    return new CoroutineReturnValue($value);
}
Salin selepas log masuk

然后修改echoTimes

function echoTimes($msg, $max) {
    for ($i = 1; $i <= $max; ++$i) {
        echo "$msg iteration $i\n";
        yield;
    }
    yield retval("");  // 增加这个作为结束标示
}
Salin selepas log masuk

Task变为:

function task1()
{
    yield echoTimes(&#39;bar&#39;, 5);
}
Salin selepas log masuk

这样就实现了一个协程堆栈,现在你可以举一反三了。

4)PHP7中yield from关键字

PHP7中增加了yield from,所以我们不需要自己实现携程堆栈,真是太好了。

把Task的构造函数改回去:

    public function __construct($taskId, Generator $coroutine)
    {
        $this->taskId = $taskId;
        $this->coroutine = $coroutine;
        // $this->coroutine = stackedCoroutine($coroutine); //不需要自己实现了,改回之前的
    }
Salin selepas log masuk

echoTimes函数:

function echoTimes($msg, $max) {
    for ($i = 1; $i <= $max; ++$i) {
        echo "$msg iteration $i\n";
        yield;
    }
}
Salin selepas log masuk

task1生成器:

function task1()
{
    yield from echoTimes('bar', 5);
}
Salin selepas log masuk

这样,轻松调用子协程。

总结

这下应该明白怎么实现PHP协程了吧?

建议不要使用PHP的Yield来实现协程,推荐使用swoole,2.0已经支持了协程,并附带了部分案例。

End...

Atas ialah kandungan terperinci Menganalisis cara coroutine dilaksanakan di bawah PHP7. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn

Alat AI Hot

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

Video Face Swap

Video Face Swap

Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Panduan Pemasangan dan Naik Taraf PHP 8.4 untuk Ubuntu dan Debian Panduan Pemasangan dan Naik Taraf PHP 8.4 untuk Ubuntu dan Debian Dec 24, 2024 pm 04:42 PM

PHP 8.4 membawa beberapa ciri baharu, peningkatan keselamatan dan peningkatan prestasi dengan jumlah penamatan dan penyingkiran ciri yang sihat. Panduan ini menerangkan cara memasang PHP 8.4 atau naik taraf kepada PHP 8.4 pada Ubuntu, Debian, atau terbitan mereka

7 Fungsi PHP Saya Menyesal Saya Tidak Tahu Sebelum ini 7 Fungsi PHP Saya Menyesal Saya Tidak Tahu Sebelum ini Nov 13, 2024 am 09:42 AM

Jika anda seorang pembangun PHP yang berpengalaman, anda mungkin merasakan bahawa anda telah berada di sana dan telah melakukannya. Anda telah membangunkan sejumlah besar aplikasi, menyahpenyahpepijat berjuta-juta baris kod dan mengubah suai sekumpulan skrip untuk mencapai op

Cara Menyediakan Kod Visual Studio (Kod VS) untuk Pembangunan PHP Cara Menyediakan Kod Visual Studio (Kod VS) untuk Pembangunan PHP Dec 20, 2024 am 11:31 AM

Kod Visual Studio, juga dikenali sebagai Kod VS, ialah editor kod sumber percuma — atau persekitaran pembangunan bersepadu (IDE) — tersedia untuk semua sistem pengendalian utama. Dengan koleksi sambungan yang besar untuk banyak bahasa pengaturcaraan, Kod VS boleh menjadi c

Jelaskan JSON Web Tokens (JWT) dan kes penggunaannya dalam PHP API. Jelaskan JSON Web Tokens (JWT) dan kes penggunaannya dalam PHP API. Apr 05, 2025 am 12:04 AM

JWT adalah standard terbuka berdasarkan JSON, yang digunakan untuk menghantar maklumat secara selamat antara pihak, terutamanya untuk pengesahan identiti dan pertukaran maklumat. 1. JWT terdiri daripada tiga bahagian: header, muatan dan tandatangan. 2. Prinsip kerja JWT termasuk tiga langkah: menjana JWT, mengesahkan JWT dan muatan parsing. 3. Apabila menggunakan JWT untuk pengesahan di PHP, JWT boleh dijana dan disahkan, dan peranan pengguna dan maklumat kebenaran boleh dimasukkan dalam penggunaan lanjutan. 4. Kesilapan umum termasuk kegagalan pengesahan tandatangan, tamat tempoh, dan muatan besar. Kemahiran penyahpepijatan termasuk menggunakan alat debugging dan pembalakan. 5. Pengoptimuman prestasi dan amalan terbaik termasuk menggunakan algoritma tandatangan yang sesuai, menetapkan tempoh kesahihan dengan munasabah,

Bagaimana anda menghuraikan dan memproses HTML/XML dalam PHP? Bagaimana anda menghuraikan dan memproses HTML/XML dalam PHP? Feb 07, 2025 am 11:57 AM

Tutorial ini menunjukkan cara memproses dokumen XML dengan cekap menggunakan PHP. XML (bahasa markup extensible) adalah bahasa markup berasaskan teks yang serba boleh yang direka untuk pembacaan manusia dan parsing mesin. Ia biasanya digunakan untuk penyimpanan data

Program PHP untuk mengira vokal dalam rentetan Program PHP untuk mengira vokal dalam rentetan Feb 07, 2025 pm 12:12 PM

Rentetan adalah urutan aksara, termasuk huruf, nombor, dan simbol. Tutorial ini akan mempelajari cara mengira bilangan vokal dalam rentetan yang diberikan dalam PHP menggunakan kaedah yang berbeza. Vokal dalam bahasa Inggeris adalah a, e, i, o, u, dan mereka boleh menjadi huruf besar atau huruf kecil. Apa itu vokal? Vokal adalah watak abjad yang mewakili sebutan tertentu. Terdapat lima vokal dalam bahasa Inggeris, termasuk huruf besar dan huruf kecil: a, e, i, o, u Contoh 1 Input: String = "TutorialSpoint" Output: 6 menjelaskan Vokal dalam rentetan "TutorialSpoint" adalah u, o, i, a, o, i. Terdapat 6 yuan sebanyak 6

Terangkan pengikatan statik lewat dalam php (statik: :). Terangkan pengikatan statik lewat dalam php (statik: :). Apr 03, 2025 am 12:04 AM

Mengikat statik (statik: :) Melaksanakan pengikatan statik lewat (LSB) dalam PHP, yang membolehkan kelas panggilan dirujuk dalam konteks statik dan bukannya menentukan kelas. 1) Proses parsing dilakukan pada masa runtime, 2) Cari kelas panggilan dalam hubungan warisan, 3) ia boleh membawa overhead prestasi.

Apakah kaedah Magic PHP (__construct, __destruct, __call, __get, __set, dll) dan menyediakan kes penggunaan? Apakah kaedah Magic PHP (__construct, __destruct, __call, __get, __set, dll) dan menyediakan kes penggunaan? Apr 03, 2025 am 12:03 AM

Apakah kaedah sihir PHP? Kaedah sihir PHP termasuk: 1. \ _ \ _ Membina, digunakan untuk memulakan objek; 2. \ _ \ _ Destruct, digunakan untuk membersihkan sumber; 3. \ _ \ _ Call, mengendalikan panggilan kaedah yang tidak wujud; 4. \ _ \ _ Mendapatkan, melaksanakan akses atribut dinamik; 5. \ _ \ _ Set, melaksanakan tetapan atribut dinamik. Kaedah ini secara automatik dipanggil dalam situasi tertentu, meningkatkan fleksibiliti dan kecekapan kod.

See all articles