Preface
A scenario I encountered recently at work was that a third-party function needed to be used in the PHP project, and there happened to be a class library written in Golang. So the question becomes, how to achieve communication between different languages? Let’s take a look below.
Conventional solution
1. Use Golang to write an http/TCP service, and php communicates with Golang through http/TCP
2. Put Golang through more Encapsulated as a php extension.
3. PHP calls the Golang executable file through system commands
Existing problems
1. http request, network I/O will consume a lot of time
2. A large amount of code needs to be encapsulated
3. Every time PHP calls a Golang program, it needs an initialization, which consumes a lot of time
Optimization goals
1. The Golang program is only initialized once (because initialization is time-consuming)
2. All requests do not need to go through Network
3. Try not to modify the code a lot
Solution
1. Simple Golang encapsulation, compile the third-party class library Generate an executable file
2. PHP and Golang communicate through two-way pipes
Advantages of using two-way pipe communication
1: Only Little encapsulation of the original Golang class library is required
2: Best performance (IPC communication is the best way to communicate between processes)
3: No Network requests are required, saving a lot of time
4: The program only needs to be initialized once and remains in memory
Specific implementation steps
1 : The original call demo in the class library
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, "/")) }
Save the file as main.go and you can run it
2: The adjusted code is:
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") } } }
Just need a simple A few lines of adjustments can be achieved: receiving a string from the standard input, segmenting it and then outputting it
Test:
# go build test # ./test # //等待用户输入,输入”这是一个测试“ # 这是 一个 测试 //程序
3: Use cat to communicate with Golang for a simple test
//准备一个title.txt,每行是一句文本 # cat title.txt | ./test
Normal output means that cat can interact with Golang normally
4: PHP communicates with Golang
The cat shown above communicates with Golang using a single to the pipeline. That is: data can only be passed from cat to Golang. The data output by Golang is not passed back to cat, but is output directly to the screen. But the requirement in the article is: php communicates with Golang. That is, php needs to pass data to Golang, and Golang must also return the execution results to php. Therefore, a bidirectional pipeline needs to be introduced.
The use of pipes in PHP: popen("/path/test"), I will not go into details because this method cannot solve the problem in the article.
Bidirectional pipe:
$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]);
Explanation: Use proc_open to open a process and call the Golang program. At the same time, a bidirectional pipes array is returned. PHP writes data to $pipe['0'] and reads data from $pipe['1'].
Okay, maybe you have discovered that I am the title file, and the focus here is not just how PHP and Golang communicate. Instead, we are introducing a method: allowing any language to communicate through a two-way pipe. (All languages will implement pipeline related content)
Test:
Through comparative testing, calculate the time occupied by each process. The title.txt file mentioned below contains 1 million lines of text. Each line of text is the product title taken from the b2b platform
1: The overall process takes time
time cat title.txt | ./test > /dev/null
takes: 14.819 seconds , the time consumed includes:
Process cat reads the text
Passes the data into Golang through the pipe
Golang processes the data and returns the result to the screen
2: It takes time to calculate the word segmentation function. Plan: Remove the call of the word segmentation function, that is: comment out the line of code that calls the word segmentation in the Golang source code
time cat title.txt | ./test > /dev/null
Time consuming: 1.817 seconds, the time consumed includes:
Process cat reads the text
Pass the data into Golang through the pipeline
Golang processes the data and returns the result to the screen
Word segmentation takes time = (The first step takes time) - (The above command takes time)
Word segmentation time: 14.819 - 1.817 = 13.002 seconds
3: Test the cat process and the Golang process Inter-communication time
time cat title.txt > /dev/null
Time consumption: 0.015 seconds, time consumption includes:
Process cat reads text
Pass data into Golang through pipes
go process the data and return the result to the screen and return the result to the screen.
## Pipeline communication time consumption: 1.817 - 0.015 = 1.802 seconds
4: Time consumption of PHP and Golang communication
Write a simple php file:
<?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);
The process is basically the same as above. Read the title.txt content, pass it into the Golang process word segmentation through a two-way pipe, and then return it to php (one more step than the above test: the data is returned through the pipe)
time php popen.php > /dev/nullTime consuming: 24.037 seconds, the time consumed includes:
Process PHP reads text
Pass data into Golang through pipe
Golang processes data
Golang will return the result Write to the pipe, PHP receives data through the pipe
Returns the result to the screen
Conclusion:
1: The entire word segmentation process Time-consuming distribution
Use cat to control logic: 14.819 seconds
Use PHP to control logic: 24.037 seconds (one more pipeline communication than cat)
One-way pipeline communication takes 1.8 seconds
The word segmentation function in Golang takes 13.002 seconds
2: Performance of the word segmentation function: Single process, It takes 13 seconds to segment 1 million product titles.
The above time only includes the word segmentation time and does not include the dictionary loading time. But in this solution, the dictionary is only loaded once, so the dictionary loading time can be ignored (about 1 second)
3: PHP is slower than cat (this conclusion is a bit redundant, haha)
Slow language level: (24.037 - 1.8 - 14.819) / 14.819 = 50%
In single-process comparison test, there should be no language faster than cat.
Related questions:
1: What is written in the above Golang source code is a loop, that is, data will always be read from the pipe. So there is a question: will the Golang process still exist after the php process ends?
The pipeline mechanism itself can solve this problem. Pipes provide two interfaces: reading and writing. When the writing process ends or hangs up unexpectedly, the reading process will also report an error, and the err logic in the above Golang source code will be executed, and the Golang process will end.
But if the PHP process has not ended, but there is no data coming in for the time being, the Golang process will keep waiting. The Golang process will not end automatically until php ends.
2: Can multiple php processes read and write the same pipe in parallel, and the Golang process serves them at the same time?
Can't. The pipe is one-way. If multiple processes write to the pipe at the same time, the return value of Golang will be confused.
You can open several more Golang processes to achieve this, and each php process corresponds to a Golang process.
Finally, the above is all nonsense. If you understand pipes and bidirectional pipes, the above explanation will be of little use to you. But if you don’t understand pipelines, it’s okay to debug the above code, but you may fall into a trap if you make slight modifications.
Summary
The above is the entire content of this article. I hope the content of this article can be helpful to everyone in learning or using Golang. If you have any questions, you can leave a message to communicate. Thank you for your support for PHP. Chinese website support.
For more detailed explanations of communication between PHP and Go language, please pay attention to the PHP Chinese website!