這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
想法很簡單。通過設定 runtime.GOMAXPROCS(1) 讓 golang 的進程變成單線程執行的。類似python用gevent的效果。然後通過調度多個協程實現非同步I/O並發。php作為一個子函數跑在go的進程內,php需要yield到其他協程時,通過回調到golang函數來實現。從php裡調用go提供的子函數時,go保證儲存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... )。修改過的php-go的原始碼在: https://github.com/taowen/go-...
完整的php/go混合協程的demo:
package mainimport ( "fmt" "github.com/deuill/go-php/engine" "os" "runtime" "time" "sync")type TestObj struct{}func newTestObj(args []interface{}) interface{} { return &TestObj{}}var engineLock *sync.Mutexfunc (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))