资讯专栏INFORMATION COLUMN

PHP 混合 Go 协程并发

zhaofeihao / 1228人阅读

摘要:当协程执行权让渡回来的时候,把原来的上下文恢复。说明协程是并发的。实际的收益取决于后端的服务的延迟,如果耗时很长,通过协程并发则可以收益明显。

想法很简单。通过设置 runtime.GOMAXPROCS(1) 让 golang 的进程变成单线程执行的。类似python用gevent的效果。然后通过调度多个协程实现异步I/O并发。php作为一个子函数跑在go的进程内,php需要yield到其他协程时,通过回调到golang函数来实现。从php里调用go提供的子函数时,go保证保存php的当前上下文。当协程执行权让渡回来的时候,把原来的php上下文恢复。关键的代码在:

</>复制代码

  1. // 保存当前协程上的php上下文
  2. oldServerCtx := engine.ServerContextGet()
  3. fmt.Println(oldServerCtx)
  4. defer engine.ServerContextSet(oldServerCtx)
  5. oldExecutorCtx := engine.ExecutorContextGet()
  6. fmt.Println(oldExecutorCtx)
  7. defer engine.ExecutorContextSet(oldExecutorCtx)
  8. oldCoreCtx := engine.CoreContextGet()
  9. fmt.Println(oldCoreCtx)
  10. defer engine.CoreContextSet(oldCoreCtx)
  11. // 放弃全局的锁,使得其他的协程可以开始执行php
  12. engineLock.Unlock()
  13. defer engineLock.Lock()

ServerContextGet 这几个函数是我加的,获得的是php的(EG/SG/PG)这三个全局context(参见:http://www.cnblogs.com/chance...)。修改过的github.com/deuill/go-php的源代码在:https://github.com/taowen/go-...

完整的php/go混合协程的demo:

</>复制代码

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/deuill/go-php/engine"
  5. "os"
  6. "runtime"
  7. "time"
  8. "sync"
  9. )
  10. type TestObj struct{}
  11. func newTestObj(args []interface{}) interface{} {
  12. return &TestObj{}
  13. }
  14. var engineLock *sync.Mutex
  15. func (self *TestObj) Hello() {
  16. oldServerCtx := engine.ServerContextGet()
  17. fmt.Println(oldServerCtx)
  18. defer engine.ServerContextSet(oldServerCtx)
  19. oldExecutorCtx := engine.ExecutorContextGet()
  20. fmt.Println(oldExecutorCtx)
  21. defer engine.ExecutorContextSet(oldExecutorCtx)
  22. oldCoreCtx := engine.CoreContextGet()
  23. fmt.Println(oldCoreCtx)
  24. defer engine.CoreContextSet(oldCoreCtx)
  25. engineLock.Unlock()
  26. defer engineLock.Lock()
  27. time.Sleep(time.Second)
  28. fmt.Println("sleep done")
  29. }
  30. func main() {
  31. runtime.GOMAXPROCS(1)
  32. theEngine, err := engine.New()
  33. engineLock = &sync.Mutex{}
  34. if err != nil {
  35. fmt.Println(err)
  36. }
  37. _, err = theEngine.Define("TestObj", newTestObj)
  38. wg := &sync.WaitGroup{}
  39. wg.Add(2)
  40. before := time.Now()
  41. fmt.Println("1")
  42. go func() {
  43. engineLock.Lock()
  44. defer engineLock.Unlock()
  45. context1, err := theEngine.NewContext()
  46. if err != nil {
  47. fmt.Println(err)
  48. }
  49. context1.Output = os.Stdout
  50. if err != nil {
  51. fmt.Println(err)
  52. }
  53. fmt.Println("1 enter")
  54. _, err = context1.Eval("$testObj = new TestObj(); $testObj->Hello();")
  55. fmt.Println("1 back")
  56. if err != nil {
  57. fmt.Println(err)
  58. }
  59. //theEngine.DestroyContext(context1)
  60. fmt.Println("1 done")
  61. wg.Done()
  62. }()
  63. fmt.Println("2")
  64. go func() {
  65. engineLock.Lock()
  66. defer engineLock.Unlock()
  67. context2, err := theEngine.NewContext()
  68. if err != nil {
  69. fmt.Println(err)
  70. }
  71. if err != nil {
  72. fmt.Println(err)
  73. }
  74. context2.Output = os.Stdout
  75. fmt.Println("2 enter")
  76. _, err = context2.Eval("$testObj = new TestObj(); $testObj->Hello();")
  77. fmt.Println("2 back")
  78. if err != nil {
  79. fmt.Println(err)
  80. }
  81. //theEngine.DestroyContext(context2)
  82. fmt.Println("2 done")
  83. wg.Done()
  84. }()
  85. wg.Wait()
  86. after := time.Now()
  87. fmt.Println(after.Sub(before))
  88. }

执行结果是

</>复制代码

  1. 1
  2. 2
  3. 2 enter
  4. {0x2cf2930 { 0 0 0 0 [0 0 0 0 0] 0 0 1000 [0 0 0 0]} {{ 0 16 0x7f682e819780 0 [0 0 0 0 0 0 0] } 0 1 [0 0 0] } 0 0 0 [0 0 0 0 0 0] {0 0 0 0 0 0 0 0 0 0 0 {0 0} {0 0} {0 0} [0 0 0]} 0x2a00270 0x2a00f60 8388608 0 1 [0 0 0] 0 {8 7 2 [0 0 0 0] 0 0x29f4520 0x29f4520 0x29f4470 0x29f4420 1 0 0 [0 0 0 0 0]} {0 [0 0 0 0 0 0 0] } 0 [0 0 0 0 0 0 0]}
  5. {0x7ffd30bac588 {[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] 2 0 0 [0 0]} 0x7f682f01b928 {[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] 1 0 0 [0 0]} 0x7f682f01b948 [ ] 0x7f682f01ba60 0x7f682f01b960 0x7f682f167168 0x7f682f01ba88 {64 63 5 [0 0 0 0] 0 0x7f682f1972d8 0x7f682f1972d8 0x7f682f1993f8 0x7f682f1970c8 0x7f682e862d10 0 0 1 [0 0 0 0 0]} {8 0 0 [0 0 0 0] 0 0x7f682f016a00 0 0 1 [0 0 0 0 0]} 0x7ffd30bac590 22527 0 0 [0 0 0 0] 0x7f682f197640 0x29f4f80 0x29f4fd0 0x29f5070 0x2cf2950 0x7f682f1989c0 14 0 1 [0 0 0] 0 1 [0 0 0 0 0 0] {8 0 0 [0 0 0 0] 1 0x7f682f016a00 0x7f682e883140 0 0 1 [0 0 0 0 0]} {8 0 0 [0 0 0 0] 0 0x7f682f016a00 0x7f682e8831d0 1 0 0 [0 0 0 0 0]} 0x7f682f167088 0 [0 0 0 0] {0 0 } {0 0 0 [0 0 0 0 0 0 0]} {0 0 0 [0 0 0 0 0 0 0]} 0 [0 0 0 0] 0 0 0x29fb2e0 {0x7f682f187030 2 1024 -1 [0 0 0 0]} [{0x7f682e915050 [0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0] 0 0 149 8 8 8} {0x7f682e915050 [0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0] 0 0 149 8 8 8} {0x7f682e915050 [0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0] 0 0 149 8 8 8}] 0x7f682f167168 {0 [0 0 0 0] 0 [0 0 0 0] 0 0 [0 0 0 0] 0 [0 0 0 0] } 1 [0 0 0 0 0 0 0] 0x7f682f01bde8 895 [0 0 0 0 0 0] [ ]}
  6. {1 [0 0 0 0 0 0 0] 0 0 0 [0 0 0 0 0 0] 0x29ff9a0 17 134217728 -1 0 0 0 1 [0 0 0 0] 1024 0 0 1 [0 0 0 0 0] 0x2a00870 0x2a010a0 0x7f682ecc58b0 0x7f682ecc5c23 2097152 0x2a00180 0x2a00230 {0x7f682ec91aa8 0x7f682ec91aa8} 0x2a00910 {0 0 0 [0 0 0 0] 0 0 0 0 [0 0 0 0 0]} 0 0 0 [0 0 0] {0x2b6dc10 0x2b6dc10 1 8 1 [0 0 0 0 0 0 0] } [0x7f682f197330 0x7f682f197040 0x7f682f197410 0x7f682f1974f0] 0 1 1 [0 0 0 0 0] 0x7f682ec9544b 0x7f682ec9544b 0 0 [0 0 0 0 0 0] 0 [0 0 0 0 0 0 0 0] 1 1 1 1 1 0 1 [0] 0 [0 0 0 0] 0 [0 0 0 0] 0x2cf27c0 0 0 [0 0 0 0 0 0] 64 1000 0 [0 0 0 0 0 0 0] 0x7f682ecc6270 300 0x2a009b0 1 [0 0 0 0 0 0 0] 0 [0 0 0 0 0 0 0]}
  7. 1 enter
  8. {0x7f6818000aa0 { 0 0 0 0 [0 0 0 0 0] 0 0 1000 [0 0 0 0]} {{ 0 16 0x7f682e819780 0 [0 0 0 0 0 0 0] } 0 1 [0 0 0] } 0 0 0 [0 0 0 0 0 0] {0 0 0 0 0 0 0 0 0 0 0 {0 0} {0 0} {0 0} [0 0 0]} 0x2a00270 0x2a00f60 8388608 0 1 [0 0 0] 0 {8 7 2 [0 0 0 0] 0 0x29f4520 0x29f4520 0x29f4470 0x29f4420 1 0 0 [0 0 0 0 0]} {0 [0 0 0 0 0 0 0] } 0 [0 0 0 0 0 0 0]}
  9. {0x7f682a4cccd8 {[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] 2 0 0 [0 0]} 0x7f682f01b928 {[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] 1 0 0 [0 0]} 0x7f682f01b948 [ ] 0x7f682f01ba60 0x7f682f01b960 0x7f682802f110 0x7f682f01ba88 {64 63 5 [0 0 0 0] 0 0x7f682f197a00 0x7f682f197a00 0x7f682f198368 0x7f682f198fa0 0x7f682e862d10 0 0 1 [0 0 0 0 0]} {8 0 0 [0 0 0 0] 0 0x7f682f016a00 0 0 1 [0 0 0 0 0]} 0x7f682a4ccce0 22527 0 0 [0 0 0 0] 0x7f682f197d28 0x29f4f80 0x29f4fd0 0x29f5070 0x2cf2950 0x7f682f1983e8 14 0 1 [0 0 0] 0 1 [0 0 0 0 0 0] {8 0 0 [0 0 0 0] 1 0x7f682f016a00 0x7f682e883140 0 0 1 [0 0 0 0 0]} {8 0 0 [0 0 0 0] 0 0x7f682f016a00 0x7f682e8831d0 1 0 0 [0 0 0 0 0]} 0x7f682802f030 0 [0 0 0 0] {0 0 } {0 0 0 [0 0 0 0 0 0 0]} {0 0 0 [0 0 0 0 0 0 0]} 0 [0 0 0 0] 0 0 0x29fb2e0 {0x7f682804efd8 2 1024 -1 [0 0 0 0]} [{0x7f682e915050 [0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0] 0 0 149 8 8 8} {0x7f682e915050 [0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0] 0 0 149 8 8 8} {0x7f682e915050 [0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0] 0 0 149 8 8 8}] 0x7f682802f110 {0 [0 0 0 0] 0 [0 0 0 0] 0 0 [0 0 0 0] 0 [0 0 0 0] } 1 [0 0 0 0 0 0 0] 0x7f682f01bde8 895 [0 0 0 0 0 0] [ ]}
  10. {1 [0 0 0 0 0 0 0] 0 0 0 [0 0 0 0 0 0] 0x29ff9a0 17 134217728 -1 0 0 0 1 [0 0 0 0] 1024 0 0 1 [0 0 0 0 0] 0x2a00870 0x2a010a0 0x7f682ecc58b0 0x7f682ecc5c23 2097152 0x2a00180 0x2a00230 {0x7f682ec91aa8 0x7f682ec91aa8} 0x2a00910 {0 0 0 [0 0 0 0] 0 0 0 0 [0 0 0 0 0]} 0 0 0 [0 0 0] {0x2b6dc10 0x2b6dc10 1 8 1 [0 0 0 0 0 0 0] } [0x7f682f197a58 0x7f682f198ce0 0x7f682f197b38 0x7f682f197c18] 0 1 1 [0 0 0 0 0] 0x7f682ec9544b 0x7f682ec9544b 0 0 [0 0 0 0 0 0] 0 [0 0 0 0 0 0 0 0] 1 1 1 1 1 0 1 [0] 0 [0 0 0 0] 0 [0 0 0 0] 0x2cf27c0 0 0 [0 0 0 0 0 0] 64 1000 0 [0 0 0 0 0 0 0] 0x7f682ecc6270 300 0x2a009b0 1 [0 0 0 0 0 0 0] 0 [0 0 0 0 0 0 0]}
  11. sleep done
  12. 1 back
  13. 1 done
  14. sleep done
  15. 2 back
  16. 2 done
  17. 1.00099211s

可以看到两个sleep 1s,最终只用了1.00099211s。说明协程是并发的。

一些性能指标。走http调用后端,在i7-6700k上,用ab -n 100 -c 4 可以跑出这样的结果

</>复制代码

  1. Requests per second: 3183.70 [#/sec] (mean)
  2. Time per request: 1.256 [ms] (mean)
  3. Time per request: 0.314 [ms] (mean, across all concurrent requests)

如果不用http调用后端,直接php=>go返回"hello",则可以达到

</>复制代码

  1. Requests per second: 10073.54 [#/sec] (mean)
  2. Time per request: 0.397 [ms] (mean)
  3. Time per request: 0.099 [ms] (mean, across all concurrent requests)

这些指标只说明了协程切换的成本。实际的收益取决于后端的http服务的延迟,如果耗时很长,通过协程并发则可以收益明显。

这个实验说明了可以用golang实现一个代替nginx+php-fpm的应用服务器。并且提供了一条从php向golang迁移的平滑迁移路径。在一个应用里混合PHP和Go两种语言。

并且可以通过提供golang函数给php调用的方式实现I/O的异步化。像libcurl这样的扩展自身是支持异步回调的,只是php是同步的所以只给php暴露了同步的execute。有了Golang之后,可以把execute变成对异步execute+callback的包装,从而实现基于协程的调度。

参考资料:

https://wiki.php.net/internal...

http://www.cunmou.com/phpbook...

http://www.phpinternalsbook.c...

http://www.php-internals.com/...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/21976.html

相关文章

  • PHP 协程Go + Chan + Defer

    摘要:为语言提供了强大的协程编程模式。提供的协程语法借鉴自,在此向开发组致敬协程可以与很好地互补。并发执行使用创建协程,可以让和两个函数变成并发执行。协程需要拿到请求的结果。 Swoole4为PHP语言提供了强大的CSP协程编程模式。底层提供了3个关键词,可以方便地实现各类功能。 Swoole4提供的PHP协程语法借鉴自Golang,在此向GO开发组致敬 PHP+Swoole协程可以与...

    nidaye 评论0 收藏0
  • 聊聊 2018 年后端技术趋势

    摘要:现在在后端业务开发编程方面,技术力量强的团队已经开始将技术栈从同步模式切换为异步了。使用这些技术方案是无法兼容已有程序的。影响了异步回调技术栈的普及。将会成为未来后端开发领域的主流技术方案。 今天太忙,少写一点,后面再补充。 异步模式 Go 语言越来越热门,很多大型互联网公司后端正在转向 GO 。Java 圈知名的服务化框架 Dubbo 也宣布转型异步模式。这是一个大趋势,异步模式已经...

    Miyang 评论0 收藏0
  • Easyswoole 源码学习和个人解析 目录

    摘要:易用稳定,本次想通过对的学习和个人解析,吸收框架的思想和设计知识,加强自己对的认知和理解。当然,笔者能力水平有限,后续的文章如有错误,还请指出和谅解。目录如下后续添加文章都会记录在此服务启动过程以及主体设计流程源码解析 前言 swoole是什么?官网的原话介绍是这样的: Swoole 使用纯 C 语言编写,提供了 PHP 语言的异步多线程服务器,异步 TCP/UDP 网络客户端,异步 ...

    CoXie 评论0 收藏0
  • Swoole4.x探究之多进程TCP协程服务实现

    摘要:有研究过框架的同学就会发现,其实最核心的,就是用了拓展加上拓展来实现其底层的网络服务和多进程调度。我们在模式下,测试起五个进程主进程要等待回收我们,这样就很简单的实现了一个多进程的协程服务。 有研究过Workman框架的同学就会发现,其实workman最核心的,就是用了php socket拓展加上pcntl拓展来实现其底层的网络服务和多进程调度。那我们今天就来探讨如何使用Swoole的...

    ad6623 评论0 收藏0
  • PHP下用Swoole实现Actor并发模型

    摘要:协程与信箱得益于,我们可以基于的协程与快速实现一个信箱模式调度。样例代码比如在一个聊天室中,我们可以定义一个房间模型。 什么是Actor? Actor对于PHPer来说,可能会比较陌生,写过Java的同学会比较熟悉,Java一直都有线程的概念(虽然PHP有Pthread,但不普及),它是一种非共享内存的并发模型,每个Actor内的数据独立存在,Actor之间通过消息传递的形式进行交互调...

    GeekQiaQia 评论0 收藏0

发表评论

0条评论

zhaofeihao

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<