百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

(转)2024 重磅更新:Swoole v6 正式发布,增加 16 项新功能

wptr33 2025-02-19 14:09 9 浏览

随着 2024 年的结束,各位PHP开发者们所期待的Swoole v6正式发布了。作为我们技术进步的结晶,这一版本不仅整合了过去一年间社区的反馈与需求,还展现了开发团队的创新与努力。它标志着Swoole在性能优化和功能拓展上的重大突破,为PHP开发者提供了更加强大的工具,助力我们在新的一年里开启更多无穷的可能。借此机会,让我们共同期待Swoole v6能够在实际应用中展现出超乎寻常的能力,推动我们的项目走向更加成功的未来!

Swoole v6版本带来了令人振奋的16项全新功能,本文将详细介绍Swoole v6的各项新功能,包括它们的应用场景以及如何帮助我们更好地应对实际开发中的各种技术难题。

1. 多线程支持

Swoole v6最重要的更新就是增加了对多线程的支持。Swoole v6的多线程是并非类似Python Threading的伪多线程,而是类似于NodeJS Workers Thread的技术实现,是真正的多线程。

得益于PHP ZTS强大的隔离性,与JavaC++Golang等语言提供的多线程相比,Swoole v6的多线程使用起来更简单,更容易掌控,不会出现危险的数据竞争。

创建线程

可以在 new Thread 的构造方法中传入变长参数数组,为新的线程传递参数。

use Swoole\Thread;

// 如果是主线程,$args 为空,如果是子线程,$args 不为空
$args = Thread::getArguments(); 
$c = 4;

if (empty($args)) {
    # 主线程
    for ($i = 0; $i < $c; $i++) {
        $threads[] = new Thread(__FILE__, $i);
    }
    for ($i = 0; $i < $c; $i++) {
        $threads[$i]->join();
    }
} else {
    # 子线程
    echo "Thread #" . $args[0] . "\n";
    while (1) {
        sleep(1);
        file_get_contents('https://www.baidu.com/');
    }
}

在线程中使用协程

Swoole v6 线程中依然可以使用协程 API 实现异步非阻塞 IO

use Swoole\Thread;

$args = Thread::getArguments();
$c = 4;

if (empty($args)) {
    # main thread
    for ($i = 0; $i < $c; $i++) {
        $threads[] = new Thread(__FILE__, $i);
    }
    for ($i = 0; $i < $c; $i++) {
        $threads[$i]->join();
    }
} else {
    # child thread x 4
    echo "Thread #" . $args[0] . "\n";
    Co\run(function() {
       while (1) {
          sleep(1);
          Co\go(function () {
              file_get_contents('https://www.baidu.com/');
          });
      }
    });
}

数据容器

与多进程相比,线程最大的优势是共享内存堆栈。在多线程环境下,我们可以实现更加灵活、强大的并发数据容器。Swoole v6 提供了3种数据容器,包括:ArrayListMapQueue

ArrayListMap实现了ArrayAccess接口,可以直接当做PHP数组来使用。Queue是一个自带线程条件变量和锁的先进先出队列结构,可以作为线程间消息通信的容器。除了数字、字符串等内容外,还可以将StreamCoSocketSocket等资源直接保存到数据容器中。ArrayListMapQueue还支持多维嵌套结构,例如ArrayList可以作为Map的一个元素。

数据容器均是线程安全的,对于容器的读写操作底层会自动使用Mutex互斥锁进行加锁,无需担心出现数据一致性问题。

ArrayList

ArrayList 是一种顺序容器,类似于PHP的数字索引数组。

use Swoole\Thread;
use Swoole\Thread\ArrayList;

$list = new ArrayList();

# 追加元素
$list[] = time();
$list[] = 99999;
$list[2] = 'test';

# 获取长度
count($list);

# 删除一个元素,这会引起批量数据前移,填补空位
unset($list[1]);

# 赋值
$list[0] = 0;

# 抛出 out of range 异常,错误的赋值
$list[1000] = 0;

Map

Map 是一种关联容器,类似于PHP的关联索引数组。

use Swoole\Thread;
use Swoole\Thread\Map;

$map = new Map;

# 写入
$map[time()] = 'value';
$map['hello'] = 3.1415926;

# 读取
echo $map['hello'];

# 删除
unset($map['hello']);

# 获取长度
count($map);

Queue


use Swoole\Thread;
use Swoole\Thread\Queue;

$args = Thread::getArguments();
$c = 4;
$n = 128;

if (empty($args)) {
    $threads = [];
    $queue = new Queue;
    for ($i = 0; $i < $c; $i++) {
        $threads[] = new Thread(__FILE__, $i, $queue);
    }
    while ($n--) {
        $queue->push(base64_encode(random_bytes(16)), Queue::NOTIFY_ONE);
        usleep(random_int(10000, 100000));
    }
    $n = 4;
    while ($n--) {
        $queue->push('', Queue::NOTIFY_ONE);
    }
    for ($i = 0; $i < $c; $i++) {
        $threads[$i]->join();
    }
    var_dump($queue->count());
} else {
    $queue = $args[1];
    while (1) {
        $job = $queue->pop(-1);
        if (!$job) {
            break;
        }
        var_dump($job);
    }
}

线程同步

Swoole v6 除了 Queue 进行一些线程通信和同步之外,还提供了多种线程同步工具来管理多线程,包括:

  • ? Swoole\Thread\Lock:可以在多线程之间访问临界资源时的指令进行互斥
  • ? Swoole\Thread\Atomic:原子计数器,实现各种数字的CAS原子操作
  • ? Swoole\Thread\Barrier:实现发令枪功能,可以让多个线程在资源对齐后并行执行

工具函数

  • ? Thread::getId():获取当前线程的ID
  • ? Thread::getNativeId():获取操作系统为线程分配的唯一ID
  • ? Thread::getArguments():获取父线程传递给子线程的参数列表
  • ? Thread::join():等待子线程退出,请注意 $thread 对象销毁时会自动执行 join() ,这可能会导致进程阻塞
  • ? Thread::joinable():检测子线程是否已退出
  • ? Thread::detach():使子线程独立运行,不再需要 Thread::join()
  • ? Thread::getPriority():获取线程的调度优先级信息
  • ? Thread::setPriority():设置线程的调度优先级信息
  • ? Thread::getAffinity():获取线程的CPU亲缘性
  • ? Thread::setAffinity():设置线程的CPU亲缘性,可以执行线程在哪些CPU核心上运行
  • ? Thread::getExitStatus():获取子线程调用exit()函数退出时设置的状态码
  • ? Thread::setName():为线程设置独特的线程名称,以便于psgdb等工具更好地追踪和分析

2. 多线程服务器

Swoole v6 的服务器端模块也适配了多线程,提供了SWOOLE_THREAD模式。在此模式下所有的Event WorkerTask Worker 以及 User Worker 将改为创建一个线程来执行,而不是进程。在工作线程之间可以传递一些ArrayListMap等线程资源实现数据资源共享。

use Swoole\Process;
use Swoole\Thread;
use Swoole\Http\Server;

$http = new Server("0.0.0.0", 9503, SWOOLE_THREAD);
$http->set([
    'worker_num' => 2,
    'task_worker_num' => 3,
    'bootstrap' => __FILE__,
    // 通过 init_arguments 实现线程间的数据共享
    'init_arguments' => function () use ($http) {
        $map = new Swoole\Thread\Map;
        return [$map];
    }
]);

$http->on('Request', function ($req, $resp) use ($http) {
    $resp->end('hello world');
});

$http->on('pipeMessage', function ($http, $srcWorkerId, $msg) {
    echo "[worker#" . $http->getWorkerId() . "]\treceived pipe message[$msg] from " . $srcWorkerId . "\n";
});

$http->addProcess(new Process(function () {
   echo "user process, id=" . Thread::getId();
   sleep(2000);
}));

$http->on('Task', function ($server, $taskId, $srcWorkerId, $data) {
    var_dump($taskId, $srcWorkerId, $data);
    return ['result' => uniqid()];
});

$http->on('Finish', function ($server, $taskId, $data) {
    var_dump($taskId, $data);
});

$http->on('WorkerStart', function ($serv, $wid) {
    // 通过Swoole\Thread::getArguments()获取配置中的init_arguments传递的共享数据
    var_dump(Thread::getArguments(), $wid);
});

$http->on('WorkerStop', function ($serv, $wid) {
    var_dump('stransform: translateY( T' . Thread::getId());
});

$http->start();

3. 增加 IO-Uring 支持,读写磁盘文件性能大幅提升

io_uring2019Linux 5.1 内核首次引入高性能、革命性的异步 I/O 框架,能显著加速 I/O 密集型应用的性能。 Swoole v6引入io_uring使得Swoole的异步文件读写性能得到了大幅提升。PHP应用层不需要任何更改即可使用。现在基于Swoole v6不仅可编写高性能的网络服务器,也可以实现高性能的文件存储服务器。这必将进一步拓宽 PHP 编程语言的应用范围。

场景一:direct I/O 1KB 随机读(绕过 Page Cache)

backendIOPScontext switchesIOPS ±% vs io_uringsync814,00027,625,004-42.6%thread pool433,00064,112,335-69.4%io_uring1,417,00011,309,574-

场景二:buffered I/O 1KB 随机读(命中 Page Cache)

backendIOPScontext switchesIOPS ±% vs io_uringsync4,906,000105,797-2.3%thread pool1,070,000114,791,187-78.7%io_uring5,024,000106,683-

4. 全新的 Cookie API

在之前的版本中,我们提供的 Cookie API 风格与 PHPsetcookie() 函数完全一致,随着互联网的发展,Cookie的选项越来越多,导致此函数的参数长达十几项,非常难以维护。Swoole v6提供了全新的Cookie API,使用了更加现代化的面向对象风格来简化Cookie设置。


$server->on('request', function (Request $request, Response $response) {
    $cookie = new Swoole\Http\Cookie();
    
    $cookie->withName('key1')
        ->withValue('val1')
        ->withExpires(time() + 84600)
        ->withPath('/')
        ->withDomain('id.test.com')
        ->withSecure(true)
        ->withHttpOnly(true)
        ->withSameSite('None')
        ->withPriority('High')
        ->withPartitioned(true);

    $response->setCookie($cookie);
    $response->end("

Hello Swoole. #" . rand(1000, 9999) . "

"); });

5. 协程锁

Swoole v6提供了全新协程锁实现,使用协程锁可以更加方便地实现协程之间的互斥保护逻辑。协程锁还允许将自身设置为共享内存模式,实现跨进程的互斥。

use Swoole\Coroutine\Lock;
use Swoole\Coroutine\System;

Co\run(function () {
    $lock = new Lock(false);
    Assert::eq($lock->trylock(), true);
    go(function () use ($lock) {
        Assert::eq($lock->trylock(), false);
        $s = microtime(true);
        Assert::eq($lock->lock(), true);
        Assert::assert(microtime(true) - $s >= 0.05);
        echo "co2 end\n";
    });

    System::sleep(0.05);
    Assert::eq($lock->unlock(), true);
    echo "co1 end\n";
});
echo "DONE\n";

以上代码中处于 lock()unlock() 函数之间的PHP代码是互斥的,不会并发执行。

异步客户端

Swoole v6 恢复了早期版本提供的异步客户端,某些场景下我们希望有一个逻辑直接运行在异步的事件循环之上,不创建协程环境,就可以使用此异步客户端。

$cli = new Swoole\Async\Client(SWOOLE_SOCK_TCP);

$client->on("connect", function(Swoole\Async\Client $client) {
    Assert::true($client->isConnected());
    $client->send(RandStr::gen(1024, RandStr::ALL));
});

$client->on("receive", function(Swoole\Async\Client $client, string $data){
    $recv_len = strlen($data);
    $client->send(RandStr::gen(1024, RandStr::ALL));
    $client->close();
    Assert::false($client->isConnected());
});

$client->on("error", function(Swoole\Async\Client $client) {
    echo "error";
});

$client->on("close", function(Swoole\Async\Client $client) {
    echo "close";
});

$client->connect("127.0.0.1", 9501, 0.2);

7. 支持 PHP 8.4

Swoole v6 版本对新的PHP 8.4进行了适配。

8. 更新到最新版本的 Boost Context 汇编

Swoole v6 版本使用了最新的 boost context 1.84 汇编代码,实现底层的协程上下文切换。性能和稳定性上得到了提升,并且还支持了龙芯等全新的CPU类型。

9. 支持 zstd 压缩格式

Zstd 全称叫 Zstandard,是一个提供高压缩比的快速压缩算法 。ZstdFacebook2016年发布的,采用了有限状态熵(Finite State Entropy,缩写为FSE)编码器。该编码器是由Jarek Duda 基于ANS理论开发的一种新型熵编码器,提供了非常强大的压缩速度/压缩率的折中方案(事实上也的确做到了“鱼”和“熊掌”兼得)。Zstd 在其最大压缩级别上提供的压缩比接近 lzma、lzham 和 ppmx,并且性能优于 lza 或 bzip2。Zstandard 达到了 Pareto frontier(资源分配最佳的理想状态),因为它解压缩速度快于任何其他当前可用的算法,但压缩比类似或更好。

对于小数据,它还特别提供一个载入预置词典的方法优化速度,词典可以通过对目标数据进行训练从而生成。

image

相比常见的gzbrotlizstd压缩算法性能有明显的提升。

10. 支持 HTTP2 分段发送

Swoole v6 版本支持了HTTP2分段发送。现在使用HTTP2协议时也可以支持streaming模式了。

$http = new Swoole\Http\Server("0.0.0.0", 9501);

$http->set([
    'open_http2_protocol' => 1,
]);

$http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) {
    $n = 5;
    while ($n--) {
        $response->write("hello world, #$n 
\n"); Co\System::sleep(1); } $response->end("hello world"); }); $http->start();

运行结果

nghttp -v http://localhost:9501
[  0.001] Connected
[  0.001] send SETTINGS frame 
          (niv=2)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[  0.001] send PRIORITY frame 
          (dep_stream_id=0, weight=201, exclusive=0)
[  0.001] send PRIORITY frame 
          (dep_stream_id=0, weight=101, exclusive=0)
[  0.001] send PRIORITY frame 
          (dep_stream_id=0, weight=1, exclusive=0)
[  0.001] send PRIORITY frame 
          (dep_stream_id=7, weight=1, exclusive=0)
[  0.001] send PRIORITY frame 
          (dep_stream_id=3, weight=1, exclusive=0)
[  0.001] send HEADERS frame 
          ; END_STREAM | END_HEADERS | PRIORITY
          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)
          ; Open new stream
          :method: GET
          :path: /
          :scheme: http
          :authority: localhost:9501
          accept: */*
          accept-encoding: gzip, deflate
          user-agent: nghttp2/1.43.0
[  0.001] recv SETTINGS frame 
          (niv=5)
          [SETTINGS_HEADER_TABLE_SIZE(0x01):4096]
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):4294967295]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
          [SETTINGS_MAX_FRAME_SIZE(0x05):16384]
          [SETTINGS_MAX_HEADER_LIST_SIZE(0x06):4294967295]
[  0.002] send SETTINGS frame 
          ; ACK
          (niv=0)
[  0.003] recv (stream_id=13) :status: 200
[  0.003] recv (stream_id=13) server: swoole-http-server
[  0.003] recv (stream_id=13) date: Tue, 31 Dec 2024 05:04:04 GMT
[  0.003] recv (stream_id=13) content-type: text/html
[  0.003] recv HEADERS frame 
          ; END_HEADERS
          (padlen=0)
          ; First response header
hello world, #4 
[ 0.003] recv DATA frame hello world, #3
[ 1.004] recv DATA frame hello world, #2
[ 2.005] recv DATA frame hello world, #1
[ 3.006] recv DATA frame hello world, #0
[ 4.007] recv DATA frame hello world[ 5.008] recv DATA frame ; END_STREAM [ 5.008] send GOAWAY frame (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])

11. System::waitSignal() 可同时监听多个信号

# 信号触发后,将返回信号的数值
$signal = System::waitSignal([SIGUSR1, SIGUSR2, SIGIO]);

12. 支持接收重复的 HTTP 头

之前的版本中收到重复的HTTP头之后,将丢弃之前的。新版本将变为数组,保留所有收到的HTTP头。

$client = new Swoole\Coroutine\Http\Client('127.0.0.1', 9501);
Assert::true($client->get('/'));
Assert::eq($client->headers['values-1'], ['hello', 'swoole', $uuid]);
Assert::eq($client->headers['values-2'], ['hello', $uuid]);

13. Redis Server 支持了递归嵌套结构

Swoole\Redis\Server现在可以向客户端发送嵌套结构了。

$server->setHandler('GET', function ($fd, $data) use ($server) {
    $key = $data[0];
    if ($key == 'map') {
        $out = Server::format(Server::MAP, [
            'uuid' => UUID,
            'list' => [1, NUMBER, UUID],
            'number' => NUMBER,
        ]);
    } elseif ($key == 'set') {
        $out = Server::format(Server::SET, [
            UUID,
            ['number' => NUMBER, 'uuid' => UUID],
            NUMBER,
        ]);
    } else {
        $out = Server::format(Server::ERROR, 'bad key');
    }
    $server->send($fd, $out);
});

14. CoSocket::getOption() 可获取 TCP_INFO

某些场景下我们希望获取到TCP连接的QoS信息,例如平均延时、丢包率等就可以在 TCP_INFO 信息中寻找。

$content = http_get_with_co_socket('www.baidu.com', function ($cli, $content){
    $info = $cli->getOption(SOL_TCP, TCP_INFO);
    Assert::greaterThan($info['rcv_space'], 0);
    Assert::greaterThan($info['rto'], 0);
    Assert::greaterThan($info['rtt'], 0);
    Assert::greaterThan($info['snd_mss'], 0);
    Assert::greaterThan($info['rcv_mss'], 0);
    echo "DONE\n";
});
Assert::assert(strpos($content, 'map.baidu.com') !== false);

15. 增加 Process::getAffinity()

可获取当前进程的CPU亲缘性设置。

16. 增加更多 Process\Pool 事件回调和属性

  • ? onStart:进程池启动时回调
  • ? onShutdown:进程池终止时回调
  • ? onWorkerExit:异步模式的工作进程即将退出时回调
  • ? workerPid:当前工作进程的PID
  • ? workerId:当前工作进程的ID
  • ? running:进程池是否处于运行状态,在收到SIGTERM信号后将切换为false
  • ? workerRunning:当前工作进程是否处于运行状态,在收到SIGTERM信号后将切换为false

转发

原文来自:
https://mp.weixin.qq.com/s/Ks1x1LNTLdl5jk0sIS6V_w

相关推荐

【推荐】一款开源免费、美观实用的后台管理系统模版

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!项目介绍...

Android架构组件-App架构指南,你还不收藏嘛

本指南适用于那些已经拥有开发Android应用基础知识的开发人员,现在想了解能够开发出更加健壮、优质的应用程序架构。首先需要说明的是:AndroidArchitectureComponents翻...

高德地图经纬度坐标批量拾取(高德地图批量查询经纬度)

使用方法在桌面上新建一个index.txt文件,把下面的代码复制进去保存,再把文件名改成index.html保存,双击运行打开即可...

flutter系列之:UI layout简介(flutter ui设计)

简介对于一个前端框架来说,除了各个组件之外,最重要的就是将这些组件进行连接的布局了。布局的英文名叫做layout,就是用来描述如何将组件进行摆放的一个约束。...

Android开发基础入门(一):UI与基础控件

Android基础入门前言:...

iOS的布局体系-流式布局MyFlowLayout

iOS布局体系的概览在我的CSDN博客中的几篇文章分别介绍MyLayout布局体系中的视图从一个方向依次排列的线性布局(MyLinearLayout)、视图层叠且停靠于父布局视图某个位置的框架布局(M...

TDesign企业级开源设计系统越发成熟稳定,支持 Vue3 / 小程序

TDesing发展越来越好了,出了好几套组件库,很成熟稳定了,新项目完全可以考虑使用。...

WinForm实现窗体自适应缩放(winform窗口缩放)

众所周知,...

winform项目——仿QQ即时通讯程序03:搭建登录界面

上两篇文章已经对CIM仿QQ即时通讯项目进行了需求分析和数据库设计。winform项目——仿QQ即时通讯程序01:原理及项目分析...

App自动化测试|原生app元素定位方法

元素定位方法介绍及应用Appium方法定位原生app元素...

61.C# TableLayoutPanel控件(c# tabcontrol)

摘要TableLayoutPanel在网格中排列内容,提供类似于HTML元素的功能。TableLayoutPanel控件允许你将控件放在网格布局中,而无需精确指定每个控件的位置。其单元格...

想要深入学习Android性能优化?看完这篇直接让你一步到位

...

12个python数据处理常用内置函数(python 的内置函数)

在python数据分析中,经常需要对字符串进行各种处理,例如拼接字符串、检索字符串等。下面我将对python中常用的内置字符串操作函数进行介绍。1.计算字符串的长度-len()函数str1='我爱py...

如何用Python程序将几十个PDF文件合并成一个PDF?其实只要这四步

假定你有一个很无聊的任务,需要将几十个PDF文件合并成一个PDF文件。每一个文件都有一个封面作为第一页,但你不希望合并后的文件中重复出现这些封面。即使有许多免费的程序可以合并PDF,很多也只是简单的将...

Python入门知识点总结,Python三大数据类型、数据结构、控制流

Python基础的重要性不言而喻,是每一个入门Python学习者所必备的知识点,作为Python入门,这部分知识点显得很庞杂,内容分支很多,大部分同学在刚刚学习时一头雾水。...