二十二、Swoole 基础学习笔记 - Swoole 进程间无锁计数器 Atomic

作者: 温新

分类: 【Swoole 系列】

阅读: 1121

时间: 2023-03-13 11:47:13

hi,我是温新,一名PHPer

文章基于 Swoole 5.0.1 版本编写。

**学习目标:学习并了解 atomic 的使用 **

说明:本篇文章结合官方文档编写及参考网络资料编写,虽非全部原创,但也是结合了自己的理解,若转载请附带本文 URL,编写不易,持续编写更不易,谢谢!

Atomic 是 Swoole 底层提供的原子计数操作类,可以方便整数的无锁原子增减。

  • 使用共享内存,可以在不同的进程之间操作计数;
  • 基于 gcc/clang 提供的 CPU 原子指令,无需加锁;
  • 在服务服务器中必须在 $server->start() 前创建才能在 Worker 进程中使用;
  • 默认使用 32 位无符号类型,如需要 64 位有符号整型,可以使用 Swoole\Atomic\Long

注意:

  • 不要在 onReceive 等回调函数中创建计数器,否则内存会持续增长,造成内存泄露;
  • 支持 64 位有符号长整型原子计数,需要使用 new Swoole\Atomic\Long 来创建;
  • Atomic\Long 不支持 waitwakeup 方法。

创建一个 Atomic

<?php

// 22-swoole-atomic.php

// 创建一个原子计数对象实例
$atomic = new Swoole\Atomic();

// 增加计数
$atomic->add(10);

// 获取当前计数的值
echo $atomic->get() . PHP_EOL;

__construct()

含义:构造函数。创建一个原子计数对象。

Swoole\Atomic::__construct(int $init_value = 0);

参数:

  • init_value:指定初始化的数值。

-Atomic 只能操作 32 位无符号整数,最大支持 42 亿,不支持负数; - 在 Server 中使用原子计数器,必须在 Server->start 前创建; - 在 Process 中使用原子计数器,必须在 Process->start 前创建。

关于 add、get、sub、set、cmpset 等这些方请参考官方文档。下面学习 wait 与 wakeup 这两个方法的使用。

wait 与 waitup

pcntl_fork

了解这两个函数之前,先来一个案例:

<?php
// 22-swoole-atomic-wait.php
if (pcntl_fork() > 0) {
	echo '父进程启动' . PHP_EOL;
	sleep(1);
	echo '父亲进程执行结束' . PHP_EOL;
} else {
	echo '子进程启动' . PHP_EOL;
	sleep(1);
	echo '子进程执行结束' . PHP_EOL;
}

再来看看执行结果

$php 22-swoole-atomic-wait.php 
父进程启动
子进程启动
子进程执行结束
父亲进程执行结束
$php 22-swoole-atomic-wait.php 
父进程启动
子进程启动
父亲进程执行结束
子进程执行结束

父进程与子进程在执行任务时,哪个先执行完成这是没法确定的,下面就使用 wait 来等待。

wait()

<?php
// 22-swoole-atomic-wait-1.php

$atomic = new Swoole\Atomic();

if (pcntl_fork() > 0) {
	echo '父进程启动' . PHP_EOL;
    // 等待 5 秒继续执行
	$atomic->wait(5);
	echo '父亲进程执行结束' . PHP_EOL;
} else {
	echo '子进程启动' . PHP_EOL;
	echo '子进程执行结束' . PHP_EOL;
}

执行结果

$php 22-swoole-atomic-wait-1.php 
父进程启动
子进程启动
子进程执行结束
# 等待了 5 秒
父亲进程执行结束

wakeup

<?php
// 22-swoole-atomic-wait-2.php

$atomic = new Swoole\Atomic();

if (pcntl_fork() > 0) {
	echo '父进程启动' . PHP_EOL;
	// 设置 wait 状态
	$atomic->wait(5);
	echo '父亲进程执行结束' . PHP_EOL;
} else {
	echo '子进程启动' . PHP_EOL;
	// 唤醒处于 wait 状态的进程
a
	echo '子进程执行结束' . PHP_EOL;
}

代码解释:

  • 1、在父进程中设置了 wait 状态,当进程执行到该代码时,会进入 wait 状态,状态没有被清除时,后面的代码不会被执行;
  • 2、父进程 fork 出子进程后,当子进程执行代码时,执行到 $atomic-wakeup() 时,处于 wait 状态的进程会被唤醒;

输出结果如下:

$php 22-swoole-atomic-wait-2.php 
父进程启动
子进程启动
子进程执行结束
父亲进程执行结束

Atomic 测试

<?php
// 22-swoole-atomic-test.php
// 
$atomic = new Swoole\Atomic();

$pool = new Swoole\Process\Pool(3, SWOOLE_IPC_NONE, 0, true);

$pool->on('Workerstart', function ($pool, $workerId) use ($atomic) {
	Swoole\Timer::tick(1000, function () use ($atomic, $workerId) {
		echo '工作进程 ' . $workerId . ':' . '原子计数' . $atomic->get() . PHP_EOL;
		$atomic->add(1); 
	});
});

$pool->on('WorkerStop', function ($pool, $workerId) {
	echo $workerId . ' Stop' . PHP_EOL;
});

$pool->start();

输出结果

$php 22-swoole-atomic-test.php 
工作进程 2 : 原子计数 0
工作进程 1 : 原子计数 0
工作进程 0 : 原子计数 2
工作进程 0 : 原子计数 3
工作进程 1 : 原子计数 3
工作进程 2 : 原子计数 5
工作进程 1 : 原子计数 6
工作进程 0 : 原子计数 6
工作进程 2 : 原子计数 8

从输出结果看出问题了吗?

我是温新,下篇文章继续学习。

请登录后再评论