首頁 >後端開發 >php教程 >PHP與Golang通信

PHP與Golang通信

PHPz
PHPz原創
2017-04-04 14:40:471900瀏覽

最近遇到的一個場景:php專案中需要使用一個第三方的功能(結巴分詞) ,而github上面剛好有一個用Golang寫好的類別庫。呢?

將Golang經過較多封裝,做為php擴充。 #存在的問題:

  • http請求,網路I/O將會消耗大量時間

  • 需要封裝大量程式碼

  • PHP每調取一次Golang程序,就需要一次初始化,時間消耗很多

#優化目標:

  • Golang程式只會初始化一次(因為初始化很耗時)

  • 所有請求不需要走網路

  • 盡量不大量修改程式碼

解決方案:

  • 簡單的Golang封裝,將第三方類別庫編譯產生為一個可執行檔

  • PHP與Golang透過雙向管道通訊

  • 使用雙向管道通訊優勢:

    1:只需要對原有Golang類別庫進行很少的封裝
  • 2:效能最佳(IPC通訊是進程間通訊的最佳途徑)
3:不需要走網路請求,節省大量時間

4:程式只需初始化一次,並且一直保持在記憶體中

    具體實作步驟:
  • 1:類別庫中的原始調取demo
  •       package main
          import (
              "fmt"
              "github.com/yanyiwu/gojieba"
              "strings"
          )
    
          func main() {
              x := gojieba.NewJieba()
              defer x.Free()
    
              s := "小明硕士毕业于中国科学院计算所,后在日本京都大学深造"
              words := x.CutForSearch(s, true)
              fmt.Println(strings.Join(words, "/"))
          }

    儲存檔案為main.go,就可以運行

2:調整後程式碼為:

      package main
      import (
          "bufio"
          "fmt"
          "github.com/yanyiwu/gojieba"
          "io"
          "os"
          "strings"
      )

      func main() {

          x := gojieba.NewJieba(
              "/data/tmp/jiebaDict/jieba.dict.utf8", 
              "/data/tmp/jiebaDict/hmm_model.utf8", 
              "/data/tmp/jiebaDict/user.dict.utf8"
          )
          defer x.Free()

          inputReader := bufio.NewReader(os.Stdin)
          for {
              s, err := inputReader.ReadString('\n')
              if err != nil && err == io.EOF {
                  break
              }
              s = strings.TrimSpace(s)

              if s != "" {
                  words := x.CutForSearch(s, true)
                  fmt.Println(strings.Join(words, " "))
              } else {
                  fmt.Println("get empty \n")
              }
          }
      }

只需要簡單的幾行調整,即可實現:從標準輸入接收
字串,經過分詞再輸出

測試:
  •   # go build test
      # ./test
      # //等待用户输入,输入”这是一个测试“
      # 这是 一个 测试 //程序

    3:使用cat與Golang通訊做簡單測試

      //准备一个title.txt,每行是一句文本
      # cat title.txt | ./test
    正常輸出,表示cat已經可以和Golang正常交互了

  • 4:PHP與Golang通信

      以上所示的cat與Golang通信,使用的是單向管道。即:只能從cat向Golang傳入數據,Golang輸出的數據並沒有傳回給cat,而是直接輸出到螢幕。但文中的需求是:php與Golang通訊。即php要傳遞資料給Golang,同時Golang也必須把執行結果回傳給php。因此,需要引入雙向管道。
      在PHP中管道的使用:popen("/path/test"),具體就不展開說了,因為此方法解決不了文中的問題。

  • 雙向管道:

          $descriptorspec = array( 
              0 => array("pipe", "r"), 
                1 => array("pipe", "w")
          );
          $handle = proc_open(
              '/webroot/go/src/test/test', 
              $descriptorspec, 
              $pipes
          );
          fwrite($pipes['0'], "这是一个测试文本\n");
          echo fgets($pipes[1]);

    解釋:使用proc_open開啟一個進程,呼叫Golang程式。同時傳回一個雙向管道pipes
  • 陣列
  • ,php向$pipe['0']寫數據,從$pipe['1']讀取數據。



    好吧,也許你已經發現,我是標題檔,這裡重點要講的並不只是PHP與Golang如何溝通。而是在介紹一種方法: 透過雙向管道讓任意語言溝通。 (所有語言都會實作管道相關內容)測試:
    透過比較測試

    ,計算出各個流程所佔用的時間。以下提到的title.txt文件,包含100萬行文本,每行文字是從b2b平台取的商品標題

    1: 整體流程耗時time

    cat title.txt | ./test > /dev/
  • null

# 耗時:14.819秒,消耗時間包含:

進程cat讀出文字


透過管道將資料傳入Golang<a href="http://www.php.cn/wiki/1268.html" target="_blank"></a><a href="http://www.php.cn/wiki/62.html" target="_blank">Golang處理數據,將結果傳回螢幕</a>

2:計算分詞

函數
    耗時。方案:去除分詞函數的調取,即:
  • 註解

    掉Golang原始程式碼中的調取分詞那行的程式碼

  • time cat title.txt | ./test > /dev /null
  • 耗時:1.817秒時間,消耗時間包含:
  • ##進程cat讀出文字

透過管道將資料傳入Golang
Golang處理資料,將結果傳回螢幕

分词耗时 = (第一步耗时) - (以上命令所耗时)
分词耗时 : 14.819 - 1.817 = 13.002

3:测试cat进程与Golang进程之间通信所占时间
time cat title.txt > /dev/null

耗时:0.015秒,消耗时间包含:

  • 进程cat读出文本

  • 通过管道将数据传入Golang

  • go处理数据,将结果返回到屏幕

管道通信耗时:(第二步耗时)  - (第三步耗时)
管道通信耗时: 1.817 - 0.015 = 1.802秒

4:PHP与Golang通信的时间消耗
编写简单的php文件:

        <?php
            $descriptorspec = array( 
                0 => array("pipe", "r"), 
                1 => array("pipe", "w")
            );

            $handle = proc_open(
                '/webroot/go/src/test/test', 
                $descriptorspec, 
                $pipes
            );

            $fp = fopen("title.txt", "rb");

            while (!feof($fp)) {
                fwrite($pipes['0'], trim(fgets($fp))."\n");
                echo fgets($pipes[1]);
            }

            fclose($pipes['0']);
            fclose($pipes['1']);
            proc_close($handle);

流程与上面基本一致,读出title.txt内容,通过双向管道传入Golang进程分词后,再返回给php (比上面的测试多一步:数据再通过管道返回)
time php popen.php > /dev/null

耗时:24.037秒,消耗时间包含:

  • 进程PHP读出文本

  • 通过管道将数据传入Golang

  • Golang处理数据

  • Golang将返回结果再写入管道,PHP通过管道接收数据

  • 将结果返回到屏幕

结论:

1 :整个分词过程中的耗时分布

使用cat控制逻辑耗时:        14.819 秒
使用PHP控制逻辑耗时:         24.037 秒(比cat多一次管道通信)
单向管道通信耗时:           1.8    秒
Golang中的分词函数耗时:     13.002 秒

2:分词函数的性能: 单进程,100万商品标题分词,耗时13秒
以上时间只包括分词时间,不包括词典载入时间。但在本方案中,词典只载入一次,所以载入词典时间可以忽略(1秒左右)

3:PHP比cat慢 (这结论有点多余了,呵呵)
语言层面慢: (24.037 - 1.8 - 14.819) / 14.819 = 50%
单进程对比测试的话,应该不会有哪个语言比cat更快。

相关问题:

  • 1:以上Golang源码中写的是一个循环,也就是会一直从管道中读数据。那么存在一个问题:是不是php进程结束后,Golang的进程还会一直存在?

    管道机制自身可解决此问题。管道提供两个接口:读、写。当写进程结束或者意外挂掉时,读进程也会报错,以上Golang源代码中的err逻辑就会执行,Golang进程结束。
    但如果PHP进程没有结束,只是暂时没有数据传入,此时Golang进程会一直等待。直到php结束后,Golang进程才会自动结束。

  • 2:能否多个php进程并行读写同一个管道,Golang进程同时为其服务?

    不可以。管道是单向的,如果多个进程同时向管道中写,那Golang的返回值就会错乱。
    可以多开几个Golang进程实现,每个php进程对应一个Golang进程。

以上是PHP與Golang通信的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn