IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> PHP学习笔记18:协程 -> 正文阅读

[PHP知识库]PHP学习笔记18:协程

PHP学习笔记18:协程

image-20211129162010327

图源:php.net

正如Python关于协程的PEP所讲,异步编程和并发已经是编程的一个热门领域,所以无论是老派语言如Python,或者是新语言Go,要么是添加新特性以支持协程,要么是天生就对协程和并发有完整支持。

但在这方面php就相当落(la)后(kua)了。

或许这和语言的应用领域和使用方式有一些关系,php作为一个和Apache等web service紧密结合的Web开发语言,绝大部分php项目都是依托于web service处理和转发请求的,php本身并不需要花大力气去管理并发和进程,至少开发者不需要。这也就意味着协程和并发对传统的php项目可有可无。

但这也不完全没有用途,否则Swoole也就不会有商业价值。在某些追求高并发高性能而抛弃web service直接监听TCP套接字进行服务的场景(比如游戏或即时聊天服务器等),就需要协程了,而Swoole的价值正在于此。

幸运的是php8.1正式引入了一个核心的协程机制:内置的Fliber类。

虽然围绕该提案有很多讨(si)论(bi),但至少必须要引入协程这是绝大多数人的观点,至于是仅引入一个最小核心类Fliber还是完整的包含了“事件循环”、“线程调度”等的完整框架,这些问题很难在短期内社区达成一致。就我看来,有至少比没有强。

如果你还不知道什么是协程以及协程的基本概念,可以阅读Python学习笔记33:协程,虽然那篇文章讨论的是Python的协程,但其实和php的协程概念是几乎一致的,毕竟本质上Python和php一样是单线程的。

这里我会提供一个使用协程的案例,分别用php、Go lang、Python实现,以对比它们之间的差异。

php

<?php
$create_number = new Fiber(function (): void {
    Fiber::suspend();
    for ($i = 0; $i <= 10; $i++) {
        Fiber::suspend($i);
    }
    Fiber::suspend(null);
});
$double_number = new Fiber(function (Fiber $create_number): void {
    Fiber::suspend();
    while (true) {
        $number = $create_number->resume();
        if ($number === null) {
            $create_number->resume();
            Fiber::suspend(null);
            break;
        }
        Fiber::suspend($number * 2);
    }
});;
$print_number = new Fiber(function (Fiber $double_number) {
    while (true) {
        $number = $double_number->resume();
        if (null === $number) {
            $double_number->resume();
            break;
        }
        echo $number . " ";
    }
});
$create_number->start();
$double_number->start($create_number);
$print_number->start($double_number);
// 0 2 4 6 8 10 12 14 16 18 20 

这里创建了三个协程:

  • $create_number负责产出数据,示例中是1...10
  • $double_number$create_number产出的数据*2,结果是2...20
  • $print_number$double_number处理过的数据输出到屏幕

php的协程是新引入的Fiber类的实例,该类的构造方法接受一个callable类型的参数。这个参数可以是匿名函数、函数变量或者实现了__invoke的对象。callable类型可以接收参数,该参数在调用Fiber实例的start方法时传入。

php的协程由start方法激活。激活后会进入协程绑定的callable的代码执行,直到遇到Fiber::suspend()挂起,该静态方法会将当前正在运行的协程(也就是代码所在callable绑定的协程)挂起。如果suspend没有参数,会向外部传递一个null值,如果有参数,会向外传递给激活或让它恢复执行的调用方。

这也是为什么$create_number$double_number两个协程需要在首行添加Fiber::suspend(),因为需要它们在激活后挂起,直到第三个协程$print_number激活后来间接恢复前两个协程来执行“产出数据”的工作。

在这个示例中协程$print_number依赖于$double_number,而$double_number依赖于$create_number,所以在最里层的协程$create_number结束的时候,必须通知外部的$double_number,而$double_number也必须通知最外层的$print_number

我这里使用向外传递一个null值的方式通知,外部协程在检测到null后,调用$innerCoroutine->resume()来让内侧协程恢复执行,以正常退出。然后外部协程也就可以退出了。

我这里讲的还是很笼统,建议阅读Python学习笔记33:协程,我使用了时序图来说明协程的调用机制。

Python

from typing import Coroutine


def create_number():
    for num in range(11):
        yield num


def double_num(cn: Coroutine):
    while True:
        try:
            num = next(cn)
        except StopIteration:
            break
        num = num*2
        yield num


def print_num(dn: Coroutine):
    while True:
        try:
            num = next(dn)
        except StopIteration:
            break
        print("{:d} ".format(num), sep=' ', end='')


print_num(double_num(create_number()))

Python和php的协程非常相似,不过Python并没有选择使用新的类型或者关键字,而是直接让生成器演化为协程。此外Python用next()来驱动协程,并且协程在执行完毕退出时,会抛出一个StopIteration的异常。外层协程可以通过捕获该异常来判断内层协程是否执行完毕。

Go lang

package main

import (
	"fmt"
	"sync"
)

func create_num(out_chan chan<- int, swg *sync.WaitGroup) {
	defer swg.Done()
	for i := 0; i <= 10; i++ {
		out_chan <- i
	}
	close(out_chan)
}

func double_num(in_chan <-chan int, out_chan chan<- int, swg *sync.WaitGroup) {
	defer swg.Done()
	for {
		num, ok := <-in_chan
		if !ok {
			close(out_chan)
			break
		}
		num = num * 2
		out_chan <- num
	}
}

func print_num(in_chan <-chan int, swg *sync.WaitGroup) {
	defer swg.Done()
	for {
		num, ok := <-in_chan
		if !ok {
			break
		}
		fmt.Printf("%d ", num)
	}
}

func main() {
	chan1 := make(chan int)
	chan2 := make(chan int)
	var swg sync.WaitGroup
	swg.Add(1)
	go create_num(chan1, &swg)
	swg.Add(1)
	go double_num(chan1, chan2, &swg)
	swg.Add(1)
	go print_num(chan2, &swg)
	swg.Wait()
}

Go语言和前两者差别有点大,因为Go是支持多线程的,Go的协程其实叫做“Goroutine”而非"Coroutine"。goroutine是真正的多线程,只不过可以通过非缓冲通道来同步,这样就表现得像是普通协程。在使用非缓冲通道的时候,上面的异步代码大致执行过程和前两者是类似的。只不过Go需要添加一个sync.WaitGroup来实现“线程计数”,以阻塞主goroutine,等待三个子goroutine执行完毕后再退出。

参考资料

往期内容

  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2021-12-13 12:35:45  更:2021-12-13 12:36:01 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/13 14:39:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码