C++多进程编程

发布时间:2023-05-19

一、进程介绍

进程是指在操作系统中正在运行的一个程序,它在操作系统中占有一定的资源,如内存、I/O、CPU时间等。 每个进程都有自己的地址空间,互相之间无法访问,是通过操作系统提供的IPC(进程间通信)机制来进行进程间的通信和数据交互。 在C++中,可以通过一些库函数来创建和控制进程的运行,如fork()exec()wait()等函数。

二、多进程编程

多进程编程是指同时运行多个进程,它可以提高程序的并发性和效率,对于系统性能和响应时间的要求高时非常有用。 在C++中,创建一个新进程可以使用fork()函数,它会复制父进程的所有资源,包括代码、数据、栈、堆等,然后子进程继续执行fork()函数后的语句,而父进程则返回子进程的进程ID,这样父子进程可以在不同的代码分支中执行不同的任务。 可以通过判断fork()函数的返回值来确定当前代码执行的是父进程还是子进程:

pid_t pid = fork();
if (pid == 0) {
    // 子进程
} else if (pid > 0) {
    // 父进程
} else {
    // 出错处理
}

其中pid_t是一个整型类型,代表进程ID,如果pid等于0,则表示当前代码执行的是子进程的分支;如果pid大于0,则表示当前代码执行的是父进程的分支,pid就是子进程的进程ID;如果pid小于0,则表示创建新进程失败。 除了fork()函数,还可以使用exec系列函数和wait()函数来控制进程的运行。

三、进程间通信

进程间通信(IPC)是指两个或多个进程之间的数据交换和通信,可以使用多种方式进行进程间通信,常见的有管道、消息队列、共享内存和信号量等。 其中,管道是最简单的通信方式,它可以在两个进程之间传递字符流,但只能用于具有亲缘关系的进程之间通信(即通过fork()函数创建的父子进程);消息队列可以传递复杂的数据结构,但如果数据量比较大时会影响性能;共享内存可以实现多个进程之间对同一块内存的访问,但需要解决进程同步和互斥的问题;信号量可以用来实现进程之间的同步和互斥,但需要掌握一定的信号量编程技巧。 下面是一个使用管道在父子进程之间传递数据的例子:

const int BUFFER_SIZE = 1024;
int pipe_fd[2];
char message[BUFFER_SIZE];
pid_t pid;
if (pipe(pipe_fd) < 0) {
    std::cerr << "create pipe error." << std::endl;
    exit(-1);
}
if ((pid = fork()) == -1) {
    std::cerr << "fork error." << std::endl;
    exit(-1);
} else if (pid == 0) {
    close(pipe_fd[1]);    // 关闭写通道
    if (read(pipe_fd[0], message, BUFFER_SIZE) == -1) {
        std::cerr << "read error." << std::endl;
        exit(-1);
    }
    std::cout << "child process received message: " << message << std::endl;
    close(pipe_fd[0]);    // 关闭读通道
} else {
    close(pipe_fd[0]);    // 关闭读通道
    std::string input_message;
    std::cout << "input message to be sent to child process: ";
    std::getline(std::cin, input_message);
    if (write(pipe_fd[1], input_message.c_str(), input_message.size()) == -1) {
        std::cerr << "write error." << std::endl;
        exit(-1);
    }
    close(pipe_fd[1]);    // 关闭写通道
    wait(NULL);           // 等待子进程结束
}

四、进程管理

进程管理是指对操作系统中的进程进行管理和调度,包括创建进程、终止进程、监控进程等操作,常用的进程管理工具有pstopkill等。 在C++中,可以使用系统函数kill()来终止一个进程,需要传入进程ID和终止信号:

if (kill(pid, SIGKILL) == -1) {
    std::cerr << "kill process error." << std::endl;
    exit(-1);
}

其中,SIGKILL是一个宏定义,代表终止信号。

五、多进程编程实例

下面是一个简单的多进程ping程序,它可以并行的ping多个IP地址,同时显示结果:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
const int MAX_PROC_NUM = 10;
const int MAX_IP_NUM = 10;
const int MAX_RESULT_SIZE = 1024;
int proc_num = 0;
char ip_list[MAX_IP_NUM][16];
bool ping(const char* ip) {
    char cmd[1024] = {0};
    sprintf(cmd, "ping -c 4 %s", ip);
    FILE* pipe = popen(cmd, "r");
    if (!pipe) {
        return false;
    }
    char buffer[MAX_RESULT_SIZE];
    memset(buffer, 0, MAX_RESULT_SIZE);
    while (!feof(pipe)) {
        if (fgets(buffer, MAX_RESULT_SIZE, pipe)) {
            std::cout << buffer;
        }
    }
    if (pclose(pipe) == -1) {
        return false;
    }
    return true;
}
void handle_signal(int signo) {
    std::cout << "process " << getpid() << " received signal " << signo << std::endl;
    exit(signo);
}
void start_proc(int idx) {
    const char* ip = ip_list[idx];
    std::cout << "start ping " << ip << ", process id " << getpid() << std::endl;
    if (signal(SIGINT, handle_signal) == SIG_ERR) {
        std::cerr << "error setting signal handler." << std::endl;
        exit(-1);
    }
    ping(ip);
    std::cout << "ping " << ip << " finish, process id " << getpid() << std::endl;
    exit(0);
}
void check_procs() {
    int status;
    pid_t pid;
    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
        std::cout << "process " << pid << " exit with status " << WEXITSTATUS(status) << std::endl;
        proc_num--;
    }
}
int main(int argc, char* argv[]) {
    if (argc < 2) {
        std::cerr << "usage: " << argv[0] << " ip1 [ip2] [...]" << std::endl;
        return -1;
    }
    proc_num = argc - 1;
    if (proc_num > MAX_IP_NUM) {
        std::cerr << "too many ip addresses." << std::endl;
        return -1;
    }
    for (int i = 0; i < argc - 1; ++i) {
        strncpy(ip_list[i], argv[i + 1], sizeof(ip_list[i]));
    }
    for (int i = 0; i < proc_num; ++i) {
        pid_t pid = fork();
        if (pid == 0) {
            start_proc(i);
        } else if (pid > 0) {
            // parent
        } else {
            std::cerr << "fork process error." << std::endl;
            return -1;
        }
    }
    while (proc_num > 0) {
        check_procs();
        sleep(1);
    }
    return 0;
}