C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++ fork()

C++中fork()函数

作者:Ralph_Y

fork()是Unix/Linux下创建子进程的核心系统调用,调用一次返回两次,本文就来详细的介绍一下C++中fork()函数的使用,感兴趣的可以了解一下

一、fork函数核心概念

fork() 是Unix/Linux系统下的系统调用(C++可通过<unistd.h>头文件调用),核心作用是创建一个新进程(子进程),原进程称为父进程。

关键特性(新手必懂)

二、基本用法与代码示例

1. 最简示例(区分父子进程)

#include <iostream>
#include <unistd.h>   // fork() 头文件
#include <sys/wait.h> // wait() 头文件
#include <cstdlib>    // exit() 头文件
int main() {
    // 调用fork创建子进程
    pid_t pid = fork();
    // 错误处理:fork失败
    if (pid == -1) {
        std::cerr << "fork failed!" << std::endl;
        exit(EXIT_FAILURE);
    }
    // 子进程分支(pid == 0)
    else if (pid == 0) {
        std::cout << "子进程:PID = " << getpid() 
                  << ",父进程PID = " << getppid() << std::endl;
        // 子进程执行完退出,避免继续执行后续代码
        exit(EXIT_SUCCESS);
    }
    // 父进程分支(pid > 0)
    else {
        std::cout << "父进程:PID = " << getpid() 
                  << ",创建的子进程PID = " << pid << std::endl;
        // 父进程等待子进程结束,避免子进程变成“僵尸进程”
        wait(NULL); 
        std::cout << "子进程已退出" << std::endl;
    }
    return 0;
}

2. 代码解释

3. 运行结果示例

父进程:PID = 1234,创建的子进程PID = 1235
子进程:PID = 1235,父进程PID = 1234
子进程已退出

(注:父子进程输出顺序可能互换,取决于系统调度。)

三、常见使用场景

  1. 并发执行任务:父进程处理主逻辑,子进程处理耗时/独立任务(如文件读写、网络请求);
  2. 守护进程创建:通过两次fork()脱离终端,成为后台守护进程;
  3. 多进程服务器:父进程监听端口,每接收到一个客户端连接就fork()子进程处理该连接。

四、注意事项(新手易踩坑)

  1. 僵尸进程问题:若父进程不调用wait()/waitpid()等待子进程退出,子进程退出后资源无法回收,会变成僵尸进程(可通过ps -ef | grep defunct查看);
  2. 孤儿进程问题:若父进程先于子进程退出,子进程会被init进程(PID=1)接管,成为孤儿进程(通常无危害);
  3. 资源共享与竞争:父子进程共享文件描述符,但私有内存空间;若需通信,需用管道、共享内存等IPC机制;
  4. 信号处理fork()后子进程会继承父进程的信号处理方式,但未处理的信号会被重置。

五、进阶示例:循环创建多个子进程

#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdlib>
int main() {
    const int child_num = 3; // 创建3个子进程
    for (int i = 0; i < child_num; ++i) {
        pid_t pid = fork();
        if (pid == -1) {
            std::cerr << "fork failed!" << std::endl;
            exit(EXIT_FAILURE);
        } else if (pid == 0) {
            // 子进程:打印自身编号和PID
            std::cout << "子进程" << i+1 << ":PID = " << getpid() << std::endl;
            exit(EXIT_SUCCESS); // 关键:子进程退出,避免继续循环创建孙进程
        }
    }
    // 父进程:等待所有子进程退出
    for (int i = 0; i < child_num; ++i) {
        wait(NULL);
    }
    std::cout << "所有子进程已退出" << std::endl;
    return 0;
}

总结

  1. fork() 是Unix/Linux下创建子进程的核心系统调用,调用一次返回两次(父进程返回子PID,子进程返回0);
  2. 核心特性包括写时复制、执行流并行、资源继承(文件描述符)与私有(内存);
  3. 使用时需注意回收子进程资源(避免僵尸进程)、区分父子执行逻辑、处理进程间通信需求。

一、进程间通信(IPC)核心机制详解

fork()创建的父子进程中,由于内存空间完全私有(写时复制仅延迟复制,最终仍是独立空间),需通过专门的IPC机制实现数据交互。以下是最常用的管道、共享内存,以及补充的其他核心IPC机制:

1. 管道(Pipe)—— 最简单的单向通信

管道是半双工(单向)的通信通道,分为匿名管道和命名管道两类,核心是基于文件描述符的字节流传输。

(1)匿名管道(Pipe)

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <sys/wait.h>
int main() {
    int fd[2];
    // 创建匿名管道
    if (pipe(fd) == -1) {
        perror("pipe failed");
        return 1;
    }
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork failed");
        return 1;
    }
    if (pid == 0) { // 子进程:读数据
        close(fd[1]); // 关闭写端
        char buf[100];
        ssize_t bytes_read = read(fd[0], buf, sizeof(buf)-1);
        if (bytes_read > 0) {
            buf[bytes_read] = '\0';
            std::cout << "子进程读取到:" << buf << std::endl;
        }
        close(fd[0]); // 读完关闭读端
        return 0;
    } else { // 父进程:写数据
        close(fd[0]); // 关闭读端
        const char* msg = "Hello from parent process!";
        write(fd[1], msg, strlen(msg));
        close(fd[1]); // 写完关闭写端
        wait(NULL); // 等待子进程
        std::cout << "父进程完成通信" << std::endl;
    }
    return 0;
}

(2)命名管道(FIFO)

2. 共享内存(Shared Memory)—— 最快的IPC机制

共享内存是让多个进程直接访问同一块物理内存区域,无需数据拷贝(管道/消息队列需内核拷贝),是效率最高的IPC方式。

#include <iostream>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <cstring>

#define SHM_KEY 1234 // 共享内存键值(唯一标识)
#define SHM_SIZE 1024 // 共享内存大小

int main() {
    // 创建共享内存段
    int shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0666);
    if (shmid == -1) {
        perror("shmget failed");
        return 1;
    }

    // 将共享内存映射到进程地址空间
    char* shm_addr = (char*)shmat(shmid, NULL, 0);
    if (shm_addr == (char*)-1) {
        perror("shmat failed");
        return 1;
    }

    pid_t pid = fork();
    if (pid == -1) {
        perror("fork failed");
        return 1;
    }

    if (pid == 0) { // 子进程:读取共享内存
        std::cout << "子进程读取共享内存:" << shm_addr << std::endl;
        // 解除映射
        shmdt(shm_addr);
        return 0;
    } else { // 父进程:写入共享内存
        strcpy(shm_addr, "Hello from parent via shared memory!");
        wait(NULL);
        // 解除映射并删除共享内存
        shmdt(shm_addr);
        shmctl(shmid, IPC_RMID, NULL);
        std::cout << "父进程释放共享内存" << std::endl;
    }

    return 0;
}

补充:其他常见IPC机制

机制核心特点适用场景
消息队列按类型/优先级存储消息,内核管理,有容量限制进程间异步、带优先级的消息传输
信号量用于进程/线程同步,实现互斥/计数配合共享内存解决竞争问题
套接字(Socket)支持跨网络、跨主机通信,全双工网络进程通信、本机跨进程通信
信号(Signal)简单的异步通知(如SIGUSR1/SIGUSR2)进程间简单事件通知(如退出)

二、fork(进程)与thread(线程)的关系

fork()创建进程和pthread创建线程是两种完全不同的并发方式,但存在关联和关键区别,核心总结如下:

1. 核心区别(最易混淆的点)

维度fork(进程)thread(线程)
内存空间父子进程私有地址空间(写时复制)同一进程的线程共享地址空间(代码、数据、堆、文件描述符)
资源开销高(复制页表、文件描述符等)低(仅创建栈和寄存器上下文)
通信复杂度高(需IPC机制)低(直接读写共享变量,需加锁)
调度单位进程是OS调度的独立单位线程是OS调度的最小单位(进程是资源分配单位)
独立性进程崩溃不影响其他进程线程崩溃会导致整个进程退出
系统调用fork()、wait()、exit()pthread_create()、pthread_join()、pthread_exit()

2. fork与线程的关联场景

(1)进程内有线程时调用fork的风险

若父进程创建了多个线程后调用fork(),子进程仅会复制调用fork的那个线程,其他线程会被终止,且可能导致:

(2)fork与线程的选择逻辑
(3)混合使用场景

例如:父进程创建线程处理网络请求,同时fork子进程处理耗时的磁盘IO(避免IO阻塞主线程),但需严格管理资源和同步。

3. 通俗类比

总结

  1. IPC机制核心:管道(匿名/命名)是基于文件描述符的单向字节流,适用于简单通信;共享内存是最快的IPC,直接共享物理内存,但需同步机制;
  2. fork与线程的核心关系:两者都是并发实现方式,fork创建独立内存空间的进程(通信需IPC),线程共享进程内存(通信简单但需同步),进程内有线程时fork需注意资源和锁的问题。

一、线程间通信(Inter-thread Communication)详解

线程间通信是同一进程内多个线程之间的数据交互或同步机制。由于线程共享进程的地址空间(代码段、数据段、堆、文件描述符等),通信方式比进程间通信(IPC)更简单,但需解决数据竞争问题。

1. 线程间通信的核心方式(按场景分类)

(1)共享内存(最基础、最常用)

线程天然共享进程的内存空间(全局变量、堆变量、静态变量),这是线程通信的核心基础。

#include <iostream>
#include <thread>
#include <mutex>

// 共享变量(线程间通信的载体)
int shared_data = 0;
// 互斥锁:保护共享变量,避免数据竞争
std::mutex mtx;

// 线程1:修改共享变量
void write_data() {
    for (int i = 0; i < 5; ++i) {
        // 加锁:保证临界区(修改共享变量)原子执行
        std::lock_guard<std::mutex> lock(mtx);
        shared_data++;
        std::cout << "写线程:shared_data = " << shared_data << std::endl;
    }
}

// 线程2:读取共享变量
void read_data() {
    for (int i = 0; i < 5; ++i) {
        std::lock_guard<std::mutex> lock(mtx);
        std::cout << "读线程:shared_data = " << shared_data << std::endl;
    }
}

int main() {
    std::thread t1(write_data);
    std::thread t2(read_data);

    t1.join();
    t2.join();
    return 0;
}

(2)条件变量(同步+通信,解决“等待-唤醒”场景)

条件变量用于线程间的同步通信,核心是“一个线程等待某个条件满足,另一个线程满足条件后唤醒它”,避免线程空等浪费CPU资源。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>

std::queue<int> msg_queue; // 共享队列(通信载体)
std::mutex mtx;
std::condition_variable cv; // 条件变量

// 生产者线程:写入数据
void producer() {
    for (int i = 1; i <= 3; ++i) {
        std::lock_guard<std::mutex> lock(mtx);
        msg_queue.push(i);
        std::cout << "生产者:写入数据 " << i << std::endl;
        cv.notify_one(); // 唤醒等待的消费者
    }
}

// 消费者线程:读取数据
void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        // 等待条件:队列非空(避免虚假唤醒,用while而非if)
        cv.wait(lock, []() { return !msg_queue.empty(); });
        
        int data = msg_queue.front();
        msg_queue.pop();
        std::cout << "消费者:读取数据 " << data << std::endl;
        lock.unlock();
        
        if (data == 3) break; // 结束条件
    }
}

int main() {
    std::thread t_prod(producer);
    std::thread t_cons(consumer);

    t_prod.join();
    t_cons.join();
    return 0;
}

(3)信号量(计数型同步,兼顾通信)

信号量本质是“计数器+锁”,可用于线程间的同步,也能间接实现通信(如通过计数标记数据是否就绪)。

#include <iostream>
#include <thread>
#include <semaphore>

std::counting_semaphore<1> sem(0); // 初始值0:无数据
int shared_data = 0;

void producer() {
    shared_data = 100;
    std::cout << "生产者:设置shared_data = " << shared_data << std::endl;
    sem.release(); // 信号量+1,通知消费者
}

void consumer() {
    sem.acquire(); // 信号量-1,无数据则阻塞
    std::cout << "消费者:读取shared_data = " << shared_data << std::endl;
}

int main() {
    std::thread t1(producer);
    std::thread t2(consumer);

    t1.join();
    t2.join();
    return 0;
}

(4)其他辅助方式

2. 线程间通信的核心原则

二、线程间通信 vs 进程间通信(IPC):区别与联系

1. 核心区别(表格对比)

维度线程间通信进程间通信(IPC)
内存基础共享同一进程地址空间(天然共享)进程地址空间私有(需内核/文件系统中介)
通信效率极高(直接读写内存,无数据拷贝)较低(管道/消息队列需内核拷贝,共享内存除外)
实现复杂度简单(共享变量+同步机制)复杂(需调用专门的IPC系统调用,如pipe/shmget)
隔离性/安全性低(一个线程崩溃导致整个进程退出)高(进程崩溃不影响其他进程)
同步机制互斥锁、条件变量、原子变量、信号量信号量(System V/POSIX)、文件锁
适用范围同一进程内的线程任意进程(有无亲缘关系均可)
典型方式共享变量、条件变量、原子变量管道、共享内存、消息队列、套接字

2. 核心联系

3. 通俗类比

线程间通信进程间通信(IPC)
同一间办公室的同事交流:
直接说话(共享变量)、举手示意(条件变量),无需出门
不同办公室的同事交流:
发邮件(管道)、共享网盘(共享内存)、打电话(套接字),需借助外部工具

三、实战选型建议(新手必看)

  1. 选线程间通信
    • 场景:高频数据交互(如实时计算、UI刷新)、资源共享多(如共享缓存、数据库连接池)、低延迟要求;
    • 注意:必须做好同步,避免数据竞争和死锁。
  2. 选IPC
    • 场景:进程隔离(如子进程执行危险操作)、跨进程/跨主机通信(如客户端-服务器)、避免单个进程崩溃影响整体;
    • 注意:优先选共享内存(高效)或管道(简单),套接字适用于跨网络场景。

总结

  1. 线程间通信核心:依托进程内共享内存,通过共享变量+同步机制(锁、条件变量、原子变量)实现,简单高效但需解决数据竞争;
  2. 与IPC的核心区别:线程通信基于进程内共享内存,IPC基于内核/文件系统中介;线程通信效率高但隔离性低,IPC效率低但隔离性高;
  3. 核心联系:共享内存是两者的共性基础,同步机制可互通,场景上互补。

到此这篇关于C++中fork()函数的文章就介绍到这了,更多相关C++ fork()内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文