业务上,C++编写的进程A和GO编写的进程B,需要在同一设备内通信。为此测试不同通信方式的性能。
1. 创建命名管道
这里创建了4个,1个用于单线程测试中,另外3个用于多线程测试中。
管道数和线程数最好相等,否则需要使用锁,影响性能。
mkfifo mypipe
mkfifo mypipe0
mkfifo mypipe1
mkfifo mypipe2
2. C++程序写数据 (cpipe.cpp)
每次测试发送100w条32字节的数据。可以指定发送数据的线程数,最大是3
#include <iostream>
#include <fstream>
#include <string>
#include <thread>
#include <vector>
#define SEND_TIMES 1000000
#define SEND_DATA "01234567890123456789012345678901"
using namespace std;
void writeToPipe(int numThreads, int threadId, const std::string& pipeName) {
std::ofstream pipe;
pipe.open(pipeName.c_str(), std::ios::out);
if(!pipe.is_open()) {
std::cerr << "Error opening pipe " << pipeName << std::endl;
return;
}
for(int i = 0; i < SEND_TIMES/numThreads; i++) {
pipe << "Thread " << threadId << SEND_DATA << std::endl; //每次写32字节
}
pipe.close();
}
void singleThd() {
writeToPipe(1, 0, "mypipe");
return;
}
void multiThds(int numThreads)
{
std::vector<std::thread> threads;
// 创建多个线程并启动
for(int i = 0; i < numThreads; i++) {
std::string pipeName = "mypipe" + std::to_string(i); // 不同的管道名称
threads.push_back(std::thread(writeToPipe, numThreads, i, pipeName));
}
// 等待所有线程执行完成
for(auto& t : threads) {
t.join();
}
return;
}
int main(int argc, char* argv[]) {
if(argc != 2) {
cerr << "Usage: " << argv[0] << " <numThreads>" << endl;
return 1;
}
int numThreads = std::stoi(argv[1]);
if (numThreads == 1) {
singleThd();
cout << "使用"<< "1" << "个线程向1个管道中发送:" <<SEND_TIMES << " 次,"<< sizeof(SEND_DATA)-1 << "字节数据。" << endl;
} else {
if (numThreads > 3) {
cout << "numThreads cannot large than 3." << endl;
}
multiThds(numThreads);
cout << "使用"<< numThreads<< "个线程向"<<numThreads<<"个管道中发送:" <<SEND_TIMES << " 次,"<< sizeof(SEND_DATA)-1 << "字节数据。" << endl;
}
return 0;
}
编译&运行
运行的第一个参数为发送数据使用的线程数
g++ cpipe.cpp -o cpipe
./cpipe 1
3. GO程序读数据 (gpipe.go)
不同的线程从不同的管道中读数据
package main
import (
"fmt"
"os"
"sync"
"time"
"strconv"
)
func readFromPipe(wg *sync.WaitGroup, ch chan []byte, pipeName string) {
defer wg.Done()
file, err := os.Open(pipeName)
if err != nil {
fmt.Println("Error opening pipe:", err)
return
}
defer file.Close()
buf := make([]byte, 100)
for {
n, err := file.Read(buf)
if err != nil {
if err.Error() == "EOF" {
break // 读取到EOF时退出循环
}
fmt.Println("Error reading from pipe:", err)
return
}
data := make([]byte, n)
copy(data, buf[:n])
ch <- data
}
}
func multiThreadRead(numThreads int) {
var wg sync.WaitGroup
ch := make(chan []byte)
entryCount := 0
t1 := time.Now().UnixNano()
for i := 0; i < numThreads; i++ {
pipeName := "mypipe" + strconv.Itoa(i) // 每个线程读取不同的管道
wg.Add(1)
go readFromPipe(&wg, ch, pipeName)
}
go func() {
wg.Wait()
close(ch)
}()
for data := range ch {
_ = string(data)
entryCount++
}
t2 := (time.Now().UnixNano() - t1) / 1000000
fmt.Printf("数据条目数量:%d, 多线程(%d个线程)所用时长:%d毫秒\n", entryCount, numThreads, t2)
}
func singleThreadRead() {
file, err := os.Open("mypipe")
if err != nil {
fmt.Println("Error opening pipe:", err)
return
}
defer file.Close()
buf := make([]byte, 100)
entryCount := 0
t1 := time.Now().UnixNano()
for {
n, err := file.Read(buf)
if err != nil {
if err.Error() == "EOF" {
break // 读取到EOF时退出循环
}
fmt.Println("Error reading from pipe:", err)
return
}
_ = string(buf[:n])
entryCount++
}
t2 := (time.Now().UnixNano() - t1) / 1000000
fmt.Printf("数据条目数量:%d, 单线程所用时长:%d毫秒\n", entryCount, t2)
}
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: go run main.go <numThreads>")
return
}
numThreads, err := strconv.Atoi(os.Args[1])
if err != nil {
fmt.Println("Invalid number of threads")
return
}
if numThreads <= 1 {
singleThreadRead()
} else {
multiThreadRead(numThreads)
}
}
运行
使用1个线程读管道数据
go run gpipe.go 1
4. 实验结果
实验环境:Intel(R) Xeon(R) Gold 6161 CPU @ 2.20GHz
实验数据如下表,可以看到使用多管道、多线程可以提高数据读写性能,但非线性提升。
读(go)-写(c++) 线程数 | 使用管道数 | 读写数据量 | 每次实验时长(ms) | 平均时长 (ms) |
1-1 | 1 | 32MB | 2856 2924 2837 2757 2883 2826 2795 2774 2705 2739 | 2805.5 |
2-2 | 2 | 32MB | 2195 2130 2035 2080 2174 1981 2309 2242 2075 2115 | 2136.6 |
3-3 | 3 | 32MB | 1754 2128 1887 1960 1966 2076 1956 2043 2098 1892 | 1987.2 |