协程编程注意事项

举报
lxw1844912514 发表于 2022/03/26 23:51:45 2022/03/26
【摘要】 1.协程内部禁止使用全局变量,以免发生数据错乱;(非多协程协作场景) 原因:协程是共享进程资源的,也就是全局变量共享,用来处理任务时,全局变量很容易被别的协程篡改,导致数据错乱。 2.协程使用 use 关键字引入外部变量到当前作用域禁止使用引用,以免发生数据错乱; (非多协程协作场景) 原因:引用是原变...

1.协程内部禁止使用全局变量,以免发生数据错乱;(非多协程协作场景)

原因:协程是共享进程资源的,也就是全局变量共享,用来处理任务时,全局变量很容易被别的协程篡改,导致数据错乱。

2.协程使用 use 关键字引入外部变量到当前作用域禁止使用引用,以免发生数据错乱;

(非多协程协作场景)

原因:引用是原变量的真实地址,由于协程是共享进程资源的,会导致原变量很容易被别的协程篡改,导致数据错乱。

3.不能使用  (非多协程协作场景)

(1)类静态变量 Class::$array 

(2)全局变量 $_array 

(3)全局对象属性 $object->array

(4)其他超全局变量$GLOBALS   等保存协程上下文内容,以免发生数据错乱;

上下文Context类实际上采用标记协程id的方式来分发存储各个协程对应的数据资源(数据池):


   
  1. use Swoole\Coroutine;
  2. class Context
  3. {
  4. protected static $pool = []; //进程创建后此静态变量就会存在,但只会根据对应的id去覆盖对应协程下的数据
  5. // 基于协程 `ID` 获取数据
  6. static function get($key)
  7. {
  8. $cid = Coroutine::getCid();
  9. if ($cid < 0)
  10. {
  11. return null;
  12. }
  13. if(isset(self::$pool[$cid][$key])){
  14. return self::$pool[$cid][$key];
  15. }
  16. return null;
  17. }
  18. // 基于协程 `ID` 写入数据
  19. static function put($key, $item)
  20. {
  21. $cid = Coroutine::getCid();
  22. if ($cid > 0)
  23. {
  24. self::$pool[$cid][$key] = $item;
  25. }
  26. }
  27. // 基于协程 `ID` 删除数据
  28. static function delete($key = null)
  29. {
  30. $cid = Coroutine::getCid();
  31. if ($cid > 0)
  32. {
  33. if($key){
  34. unset(self::$pool[$cid][$key]);
  35. }else{
  36. unset(self::$pool[$cid]);
  37. }
  38. }
  39. }
  40. }

4.协程之间通讯必须使用通道(Channel)场景:如果需要使用多协程协作执行任务时

Coroutine\Channel 使用本地内存,不同的进程之间内存是隔离的。

只能在同一进程的不同协程内进行 push 和 pop 操作。

不过理论上仍然有共享内存的方式,只是需要进行上锁,保持同步机制

5.不能在多个协程间共用一个客户端连接,以免发生数据错乱;可以使用连接池实现;

原因:同样是因为连接标识共享,有可能前脚一个协程刚对链接做了操作,后脚被别的协程改了数据。(非多协程协作场景)


   
  1. $pool = new RedisPool();
  2. $server = new Swoole\Http\Server('127.0.0.1', 9501);
  3. $server->set([
  4. // 如开启异步安全重启, 需要在workerExit释放连接池资源
  5. 'reload_async' => true
  6. ]);
  7. $server->on('start', function (swoole_http_server $server) {
  8. var_dump($server->master_pid);
  9. });
  10. $server->on('workerExit', function (swoole_http_server $server) use ($pool) {
  11. $pool->destruct();
  12. });
  13. $server->on('request', function (swoole_http_request $req, swoole_http_response $resp) use ($pool) {
  14. //从连接池中获取一个Redis协程客户端
  15. $redis = $pool->get();
  16. //连接失败
  17. if ($redis === false) {
  18. $resp->end("ERROR");
  19. return;
  20. }
  21. $result = $redis->hgetall('key');
  22. $resp->end(var_export($result, true));
  23. //释放客户端,其他协程可复用此对象
  24. $pool->put($redis);
  25. });
  26. $server->start();
  27. class RedisPool
  28. {
  29. protected $available = true;
  30. protected $pool;
  31. public function __construct()
  32. {
  33. $this->pool = new SplQueue;
  34. }
  35. public function put($redis)
  36. {
  37. $this->pool->push($redis);
  38. }
  39. /**
  40. * @return bool|mixed|\Swoole\Coroutine\Redis
  41. */
  42. public function get()
  43. {
  44. //有空闲连接且连接池处于可用状态
  45. if ($this->available && count($this->pool) > 0) {
  46. return $this->pool->pop();
  47. }
  48. //无空闲连接,创建新连接
  49. $redis = new Swoole\Coroutine\Redis();
  50. $res = $redis->connect('127.0.0.1', 6379);
  51. if ($res == false) {
  52. return false;
  53. } else {
  54. return $redis;
  55. }
  56. }
  57. public function destruct()
  58. {
  59. // 连接池销毁, 置不可用状态, 防止新的客户端进入常驻连接池, 导致服务器无法平滑退出
  60. $this->available = false;
  61. while (!$this->pool->isEmpty()) {
  62. $this->pool->pop();
  63. }
  64. }
  65. }

6.在 Swoole\Server 中,客户端连接应当在 onWorkerStart 中创建;

原因:使得客户端链接在整个进程周期中可用。

7.在 Swoole\Process 中,客户端连接应当在 Swoole\Process->start 后,子进程的回调函数中创建;

原因:使得客户端链接在整个子进程周期中可用。

8.必须在协程内捕获异常,不得跨协程捕获异常;

原因:多协程下,try/catch和throw在不同的协程中,协程内无法捕获到此异常。当协程退出时,发现有未捕获的异常,将引起致命错误。


   
  1. 错误:
  2. try {
  3. Swoole\Coroutine::create(function () {
  4. throw new \RuntimeException(__FILE__, __LINE__);
  5. });
  6. }
  7. catch (\Throwable $e) {
  8. echo $e;
  9. }
  10. #try/catch和throw在不同的协程中,
  11. 协程内无法捕获到此异常。
  12. 当协程退出时,发现有未捕获的异常,将引起致命错误。
  13. 正解:
  14. function test() {
  15. throw new \RuntimeException(__FILE__, __LINE__);
  16. }
  17. Swoole\Coroutine::create(function () {
  18. try {
  19. test();
  20. }
  21. catch (\Throwable $e) {
  22. echo $e;
  23. }
  24. });

9.在__get /__set魔术方法中不能有协程切换。(跟php本身有关)

b875e5181ffc4aadd9d9b462b04a69b5.png

文章来源: blog.csdn.net,作者:lxw1844912514,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/lxw1844912514/article/details/122632218

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。