PHP 混合 Go 協程併發

想法很簡單。經過設置 runtime.GOMAXPROCS(1) 讓 golang 的進程變成單線程執行的。相似python用gevent的效果。而後經過調度多個協程實現異步I/O併發。php做爲一個子函數跑在go的進程內,php須要yield到其餘協程時,經過回調到golang函數來實現。從php裏調用go提供的子函數時,go保證保存php的當前上下文。當協程執行權讓渡回來的時候,把原來的php上下文恢復。關鍵的代碼在:php

// 保存當前協程上的php上下文
    oldServerCtx := engine.ServerContextGet()
    fmt.Println(oldServerCtx)
    defer engine.ServerContextSet(oldServerCtx)
    oldExecutorCtx := engine.ExecutorContextGet()
    fmt.Println(oldExecutorCtx)
    defer engine.ExecutorContextSet(oldExecutorCtx)
    oldCoreCtx := engine.CoreContextGet()
    fmt.Println(oldCoreCtx)
    defer engine.CoreContextSet(oldCoreCtx)

// 放棄全局的鎖,使得其餘的協程能夠開始執行php
    engineLock.Unlock()
    defer engineLock.Lock()

ServerContextGet 這幾個函數是我加的,得到的是php的(EG/SG/PG)這三個全局context(參見:http://www.cnblogs.com/chance...)。修改過的github.com/deuill/go-php的源代碼在:https://github.com/taowen/go-...html

完整的php/go混合協程的demo:python

package main

import (
    "fmt"
    "github.com/deuill/go-php/engine"
    "os"
    "runtime"
    "time"
    "sync"
)

type TestObj struct{}

func newTestObj(args []interface{}) interface{} {
    return &TestObj{}
}
var engineLock *sync.Mutex

func (self *TestObj) Hello() {
    oldServerCtx := engine.ServerContextGet()
    fmt.Println(oldServerCtx)
    defer engine.ServerContextSet(oldServerCtx)
    oldExecutorCtx := engine.ExecutorContextGet()
    fmt.Println(oldExecutorCtx)
    defer engine.ExecutorContextSet(oldExecutorCtx)
    oldCoreCtx := engine.CoreContextGet()
    fmt.Println(oldCoreCtx)
    defer engine.CoreContextSet(oldCoreCtx)
    engineLock.Unlock()
    defer engineLock.Lock()
    time.Sleep(time.Second)
    fmt.Println("sleep done")
}

func main() {
    runtime.GOMAXPROCS(1)
    theEngine, err := engine.New()
    engineLock = &sync.Mutex{}
    if err != nil {
        fmt.Println(err)
    }
    _, err = theEngine.Define("TestObj", newTestObj)
    wg := &sync.WaitGroup{}
    wg.Add(2)
    before := time.Now()
    fmt.Println("1")
    go func() {
        engineLock.Lock()
        defer engineLock.Unlock()
        context1, err := theEngine.NewContext()
        if err != nil {
            fmt.Println(err)
        }
        context1.Output = os.Stdout
        if err != nil {
            fmt.Println(err)
        }
        fmt.Println("1 enter")
        _, err = context1.Eval("$testObj = new TestObj(); $testObj->Hello();")
        fmt.Println("1 back")
        if err != nil {
            fmt.Println(err)
        }
        //theEngine.DestroyContext(context1)
        fmt.Println("1 done")
        wg.Done()
    }()
    fmt.Println("2")
    go func() {
        engineLock.Lock()
        defer engineLock.Unlock()
        context2, err := theEngine.NewContext()
        if err != nil {
            fmt.Println(err)
        }
        if err != nil {
            fmt.Println(err)
        }
        context2.Output = os.Stdout
        fmt.Println("2 enter")
        _, err = context2.Eval("$testObj = new TestObj(); $testObj->Hello();")
        fmt.Println("2 back")
        if err != nil {
            fmt.Println(err)
        }
        //theEngine.DestroyContext(context2)
        fmt.Println("2 done")
        wg.Done()
    }()
    wg.Wait()
    after := time.Now()
    fmt.Println(after.Sub(before))
}

執行結果是nginx

1
2
2 enter
{0x2cf2930 {<nil> <nil> <nil> 0 <nil> <nil> <nil> <nil> 0 0 0 [0 0 0 0 0] <nil> <nil> <nil> <nil> <nil> <nil> <nil> 0 0 <nil> 1000 [0 0 0 0]} {{<nil> <nil> 0 16 0x7f682e819780 0 [0 0 0 0 0 0 0] <nil>} 0 1 [0 0 0] <nil> <nil>} 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 <nil> 8388608 0 1 [0 0 0] 0 {8 7 2 [0 0 0 0] 0 0x29f4520 0x29f4520 0x29f4470 0x29f4420 <nil> 1 0 0 [0 0 0 0 0]} <nil> {0 [0 0 0 0 0 0 0] <nil> <nil> <nil> <nil>} 0 [0 0 0 0 0 0 0]}
{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 [<nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil>] 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 <nil> <nil> <nil> 0x7f682f016a00 <nil> 0 0 1 [0 0 0 0 0]} 0x7ffd30bac590 22527 0 0 [0 0 0 0] 0x7f682f197640 0x29f4f80 0x29f4fd0 0x29f5070 <nil> 0x2cf2950 0x7f682f1989c0 14 0 1 [0 0 0] <nil> <nil> 0 1 [0 0 0 0 0 0] {8 0 0 [0 0 0 0] 1 <nil> <nil> <nil> 0x7f682f016a00 0x7f682e883140 0 0 1 [0 0 0 0 0]} {8 0 0 [0 0 0 0] 0 <nil> <nil> <nil> 0x7f682f016a00 0x7f682e8831d0 1 0 0 [0 0 0 0 0]} 0x7f682f167088 0 [0 0 0 0] <nil> <nil> {0 0 <nil>} {0 0 <nil> <nil> 0 [0 0 0 0 0 0 0]} {0 0 <nil> <nil> 0 [0 0 0 0 0 0 0]} 0 [0 0 0 0] <nil> 0 0 0x29fb2e0 <nil> <nil> {0x7f682f187030 2 1024 -1 [0 0 0 0]} <nil> <nil> <nil> [{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 <nil> {0 [0 0 0 0] <nil> 0 [0 0 0 0] 0 0 [0 0 0 0] <nil> 0 [0 0 0 0] <nil>} 1 [0 0 0 0 0 0 0] <nil> 0x7f682f01bde8 895 [0 0 0 0 0 0] [<nil> <nil> <nil> <nil>]}
{1 [0 0 0 0 0 0 0] 0 0 0 [0 0 0 0 0 0] <nil> 0x29ff9a0 17 134217728 -1 0 0 0 1 [0 0 0 0] 1024 0 0 1 [0 0 0 0 0] 0x2a00870 <nil> 0x2a010a0 0x7f682ecc58b0 <nil> 0x7f682ecc5c23 <nil> <nil> <nil> 2097152 <nil> <nil> 0x2a00180 0x2a00230 <nil> <nil> <nil> {0x7f682ec91aa8 0x7f682ec91aa8} 0x2a00910 {0 0 0 [0 0 0 0] 0 <nil> <nil> <nil> <nil> <nil> 0 0 0 [0 0 0 0 0]} 0 0 0 [0 0 0] {0x2b6dc10 0x2b6dc10 1 8 <nil> 1 [0 0 0 0 0 0 0] <nil>} [0x7f682f197330 0x7f682f197040 0x7f682f197410 <nil> <nil> 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] <nil> <nil> 0 [0 0 0 0] 0x2cf27c0 <nil> 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] <nil> 0 [0 0 0 0 0 0 0]}
1 enter
{0x7f6818000aa0 {<nil> <nil> <nil> 0 <nil> <nil> <nil> <nil> 0 0 0 [0 0 0 0 0] <nil> <nil> <nil> <nil> <nil> <nil> <nil> 0 0 <nil> 1000 [0 0 0 0]} {{<nil> <nil> 0 16 0x7f682e819780 0 [0 0 0 0 0 0 0] <nil>} 0 1 [0 0 0] <nil> <nil>} 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 <nil> 8388608 0 1 [0 0 0] 0 {8 7 2 [0 0 0 0] 0 0x29f4520 0x29f4520 0x29f4470 0x29f4420 <nil> 1 0 0 [0 0 0 0 0]} <nil> {0 [0 0 0 0 0 0 0] <nil> <nil> <nil> <nil>} 0 [0 0 0 0 0 0 0]}
{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 [<nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil>] 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 <nil> <nil> <nil> 0x7f682f016a00 <nil> 0 0 1 [0 0 0 0 0]} 0x7f682a4ccce0 22527 0 0 [0 0 0 0] 0x7f682f197d28 0x29f4f80 0x29f4fd0 0x29f5070 <nil> 0x2cf2950 0x7f682f1983e8 14 0 1 [0 0 0] <nil> <nil> 0 1 [0 0 0 0 0 0] {8 0 0 [0 0 0 0] 1 <nil> <nil> <nil> 0x7f682f016a00 0x7f682e883140 0 0 1 [0 0 0 0 0]} {8 0 0 [0 0 0 0] 0 <nil> <nil> <nil> 0x7f682f016a00 0x7f682e8831d0 1 0 0 [0 0 0 0 0]} 0x7f682802f030 0 [0 0 0 0] <nil> <nil> {0 0 <nil>} {0 0 <nil> <nil> 0 [0 0 0 0 0 0 0]} {0 0 <nil> <nil> 0 [0 0 0 0 0 0 0]} 0 [0 0 0 0] <nil> 0 0 0x29fb2e0 <nil> <nil> {0x7f682804efd8 2 1024 -1 [0 0 0 0]} <nil> <nil> <nil> [{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 <nil> {0 [0 0 0 0] <nil> 0 [0 0 0 0] 0 0 [0 0 0 0] <nil> 0 [0 0 0 0] <nil>} 1 [0 0 0 0 0 0 0] <nil> 0x7f682f01bde8 895 [0 0 0 0 0 0] [<nil> <nil> <nil> <nil>]}
{1 [0 0 0 0 0 0 0] 0 0 0 [0 0 0 0 0 0] <nil> 0x29ff9a0 17 134217728 -1 0 0 0 1 [0 0 0 0] 1024 0 0 1 [0 0 0 0 0] 0x2a00870 <nil> 0x2a010a0 0x7f682ecc58b0 <nil> 0x7f682ecc5c23 <nil> <nil> <nil> 2097152 <nil> <nil> 0x2a00180 0x2a00230 <nil> <nil> <nil> {0x7f682ec91aa8 0x7f682ec91aa8} 0x2a00910 {0 0 0 [0 0 0 0] 0 <nil> <nil> <nil> <nil> <nil> 0 0 0 [0 0 0 0 0]} 0 0 0 [0 0 0] {0x2b6dc10 0x2b6dc10 1 8 <nil> 1 [0 0 0 0 0 0 0] <nil>} [0x7f682f197a58 0x7f682f198ce0 0x7f682f197b38 <nil> <nil> 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] <nil> <nil> 0 [0 0 0 0] 0x2cf27c0 <nil> 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] <nil> 0 [0 0 0 0 0 0 0]}
sleep done
1 back
1 done
sleep done
2 back
2 done
1.00099211s

能夠看到兩個sleep 1s,最終只用了1.00099211s。說明協程是併發的。git

一些性能指標。走http調用後端,在i7-6700k上,用ab -n 100 -c 4 能夠跑出這樣的結果github

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

若是不用http調用後端,直接php=>go返回"hello",則能夠達到golang

Requests per second:    10073.54 [#/sec] (mean)
Time per request:       0.397 [ms] (mean)
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的包裝,從而實現基於協程的調度。併發

參考資料:

相關文章
相關標籤/搜索