当前位置: 首页 > news >正文

fork():进程界的“分身魔法”

<摘要>
本文将以生动有趣的方式深入解析 Linux 系统编程中至关重要的 fork() 函数。我们会从生活化的故事入手,理解其“进程分身术”的本质;详细介绍其声明来源、返回值含义及参数特点;通过三个典型实例(基础用法展示、错误处理实践、简易服务器并发模型),结合完整代码、流程图、Makefile 及运行说明,让你全面掌握 fork() 的使用场景与机制;最后用图解总结其核心逻辑,助你轻松攻克这个系统编程的基础难点。


<正文>
嘿,朋友!今天咱们来聊聊 Linux 世界里一个特别酷的函数——fork()。你可以把它想象成进程界的“孙悟空拔毫毛”,一根毫毛(一个进程)轻轻一吹,就变出了一个一模一样的分身(新进程)。这分身不仅长得跟本体几乎一样,连手里的金箍棒(内存数据)、脚下的筋斗云(执行状态)都分毫不差。

是不是有点好奇这“分身术”是怎么实现的?它在实际编程中又有哪些妙用?别着急,咱们一步步揭开 fork() 的神秘面纱。

一、初识 fork():进程界的“分身魔法”

1.1 什么是 fork()?用个小故事讲明白

假设你是一家小餐馆的老板(一个进程),店里生意越来越好,一个人忙不过来(需要并发处理任务)。这时候你怎么办?总不能自己劈成两半吧?但在计算机世界里,进程就可以通过 fork() 来“劈成两半”——原来的进程(父进程)会创建一个跟自己一模一样的新进程(子进程)。

子进程诞生的那一刻,它拥有和父进程完全相同的内存数据:比如当前正在处理的订单信息(变量值)、接下来要做的事(程序计数器)、手里的工具(文件描述符)等等。就像孙悟空的分身,刚变出来时和本体没区别。

但从这一刻起,父子进程就成了独立的个体,各自干活。父进程可能继续接待新顾客,子进程则去厨房准备刚才的订单。它们之间互不干扰,就算子进程“累倒了”(退出),父进程也能继续工作(除非父进程特意关注子进程的状态)。

1.2 常见使用场景:哪里需要“分身”?

fork() 的这种特性让它在很多场景中大放异彩:

  • 服务器编程:当服务器收到一个客户端连接时,用 fork() 创建子进程专门处理这个客户端,父进程继续等待新连接。比如你访问网页时,服务器可能就通过这种方式同时处理成百上千个用户的请求。
  • 后台任务处理:有些程序需要在后台默默做一些事(比如日志备份),可以通过 fork() 创建子进程,让子进程脱离终端在后台运行(变成守护进程)。
  • 并行计算:对于一些可以拆分的任务(比如处理多个文件),父进程创建多个子进程,每个子进程处理一部分,提高效率。

二、fork() 的“身份信息”:声明与来源

想使用 fork() 这个“魔法”,得先知道它来自哪里。就像你想用某个工具,得知道它放在哪个工具箱里一样。

fork() 函数定义在 unistd.h 头文件中,它是 POSIX 标准 规定的函数,几乎所有类 Unix 系统(Linux、macOS、FreeBSD 等)都支持。在 Linux 系统中,它由 glibc(GNU C 库) 实现,底层通过系统调用与内核交互,完成进程创建的核心工作。

所以,在代码中使用 fork() 时,记得包含头文件:

#include <unistd.h>

三、fork() 的“魔法反馈”:返回值的秘密

调用 fork() 后,会出现两个进程,那怎么区分它们呢?这就要看 fork() 的返回值了——它是区分父子进程的“身份证”。

fork() 的返回值类型是 pid_t(本质上是一个整数类型,用于表示进程 ID),不同情况返回值不同:

  1. 父进程中:返回新创建的子进程的进程 ID(PID)。就像父母给孩子取了个名字,以后可以通过这个 PID 找到子进程。
  2. 子进程中:返回 0。表示“我是分身,我没有自己的分身(至少现在没有)”。
  3. 错误时:返回 -1。说明“分身术”失败了,可能是因为系统进程数达到上限,或者内存不足等。

这里有个有趣的点:fork() 是唯一一个调用一次却返回两次的函数——父进程一次,子进程一次。就像你喊了一声“分身!”,你自己听到了孩子的名字,而分身听到了“0”。

四、fork() 的“施法条件”:参数详解

fork() 函数的声明是这样的:

pid_t fork(void);

你没看错,它 没有任何参数!这意味着调用 fork() 时不需要传递任何信息,它会自动复制父进程的所有资源(内存、文件描述符、环境变量等)来创建子进程。

这种“零参数”设计,体现了它“完整复制”的特性——不需要你指定复制什么,它会把能复制的都复制一份。

五、实例与应用场景:让“分身术”落地

光说不练假把式,咱们通过三个实例,看看 fork() 在实际中怎么用。

实例一:基础用法——看看父子进程的“日常”

应用场景:想直观感受 fork() 创建的父子进程是如何运行的,观察它们的 PID 以及执行顺序。

代码实现

/*** @brief 展示 fork() 函数的基础用法* * 该程序通过 fork() 创建子进程,分别在父进程和子进程中输出各自的 PID 及相关信息,* 展示父子进程的独立性和返回值差异。* * @in:无输入参数* * @out:*   - 父进程输出自身 PID、子进程 PID*   - 子进程输出自身 PID、父进程 PID(通过 getppid() 获取)* * 返回值说明:*   程序正常执行返回 0,fork() 失败时返回 1*/
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>  // 用于 wait() 函数int main() {printf("===== 程序开始执行 =====\n");printf("当前进程 PID:%d\n", getpid());  // getpid() 获取当前进程 PID// 调用 fork() 创建子进程pid_t pid = fork();// 检查 fork() 是否失败if (pid == -1) {perror("fork 失败");  // perror 打印错误信息return 1;}// 子进程:fork() 返回 0if (pid == 0) {// 子进程中可以通过 getppid() 获取父进程 PIDprintf("我是子进程,我的 PID 是:%d,我的父进程 PID 是:%d\n", getpid(), getppid());// 子进程做一些简单工作printf("子进程:我要开始干活啦!\n");sleep(2);  // 模拟子进程执行任务printf("子进程:活干完了,我要退出啦!\n");}// 父进程:fork() 返回子进程 PIDelse {printf("我是父进程,我的 PID 是:%d,我创建的子进程 PID 是:%d\n", getpid(), pid);// 父进程等待子进程结束(避免子进程变成僵尸进程)printf("父进程:等待子进程完成工作...\n");wait(NULL);  // 等待任意子进程结束printf("父进程:子进程已经退出,我也准备结束啦!\n");}printf("===== 进程 %d 执行结束 =====\n", getpid());return 0;
}

程序流程图

graph TDA["程序开始 (PID: X)"] --> B["打印当前进程PID (X)"]B --> C["调用 fork()"]C -->|创建成功| D{返回值判断}C -->|创建失败| E["打印错误信息,程序退出 (返回1)"]D -->|返回0 (子进程)| F["子进程:打印自身PID (Y) 和父进程PID (X)"]F --> G["子进程:打印开始工作信息"]G --> H["子进程:sleep(2) 模拟工作"]H --> I["子进程:打印完成工作信息"]I --> J["子进程:打印结束信息,退出"]D -->|返回子进程PID (Y) (父进程)| K["父进程:打印自身PID (X) 和子进程PID (Y)"]K --> L["父进程:打印等待子进程信息"]L --> M["父进程:调用 wait(NULL) 等待子进程"]M --> N["父进程:打印子进程退出信息"]N --> O["父进程:打印结束信息,退出"]

Makefile

# 编译基础示例程序
fork_basic: fork_basic.cgcc -o fork_basic fork_basic.c -Wall  # -Wall 显示警告信息# 清理编译生成的文件
clean:rm -f fork_basic

编译与运行步骤

  1. 将代码保存为 fork_basic.c,Makefile 保存为 Makefile(注意首字母大写)。
  2. 在终端中进入文件所在目录,执行 make fork_basic 编译程序。
    • 若编译成功,会生成 fork_basic 可执行文件。
  3. 运行程序:./fork_basic

运行结果解读

假设运行后输出如下(PID 是动态分配的,每次运行可能不同):

===== 程序开始执行 =====
当前进程 PID:12345
我是父进程,我的 PID 是:12345,我创建的子进程 PID 是:12346
父进程:等待子进程完成工作...
我是子进程,我的 PID 是:12346,我的父进程 PID 是:12345
子进程:我要开始干活啦!
子进程:活干完了,我要退出啦!
===== 进程 12346 执行结束 =====
父进程:子进程已经退出,我也准备结束啦!
===== 进程 12345 执行结束 =====

解读:

  • 程序开始时只有一个进程(PID 12345)。
  • 调用 fork() 后,出现两个进程:父进程(12345)和子进程(12346)。
  • 父进程中 fork() 返回子进程 PID(12346),子进程中返回 0。
  • 子进程执行时会 sleep 2 秒(模拟工作),父进程调用 wait(NULL) 等待子进程结束,避免子进程变成“僵尸进程”(已退出但资源未释放的进程)。
  • 父子进程的执行顺序不是严格的“父先子后”或“子先父后”,取决于操作系统的调度,但这里父进程等待子进程,所以最终父进程后结束。
实例二:错误处理——当“分身术”失灵时

应用场景fork() 并非总能成功,比如系统进程数达到上限(Linux 中可通过 /proc/sys/kernel/pid_max 查看最大 PID 数,间接限制进程数)。此时需要妥善处理错误,避免程序崩溃。

代码实现

/*** @brief 展示 fork() 失败时的错误处理* * 该程序尝试创建大量子进程,直到 fork() 失败,展示如何检测并处理 fork() 错误,* 同时演示如何通过 waitpid() 回收多个子进程资源。* * @in:无输入参数* * @out:*   - 每次创建子进程成功时,输出子进程 PID*   - fork() 失败时,输出错误原因及已创建的子进程总数*   - 父进程回收所有子进程后,输出回收完成信息* * 返回值说明:*   程序正常执行返回 0,初始化失败时返回 1*/
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>  // 用于 exit()int main() {printf("===== 开始尝试创建多个子进程 =====\n");printf("父进程 PID:%d\n", getpid());int count = 0;  // 记录成功创建的子进程数pid_t pid;while (1) {pid = fork();if (pid == -1) {perror("fork 失败,原因是");printf("已成功创建 %d 个子进程\n", count);break;} else if (pid == 0) {// 子进程不需要做太多事,直接退出exit(0);  // 子进程退出,状态码 0 表示正常} else {// 父进程:记录子进程数,继续创建count++;printf("成功创建子进程,PID:%d(累计:%d)\n", pid, count);// 每创建100个子进程,短暂休息,避免系统压力过大if (count % 100 == 0) {sleep(1);}}}// 回收所有子进程资源,避免僵尸进程printf("开始回收子进程资源...\n");int status;pid_t exited_pid;while ((exited_pid = waitpid(-1, &status, 0)) > 0) {// WIFEXITED(status) 检查子进程是否正常退出if (WIFEXITED(status)) {printf("子进程 %d 正常退出,退出码:%d\n", exited_pid, WEXITSTATUS(status));} else {printf("子进程 %d 异常退出\n", exited_pid);}}printf("所有子进程资源已回收,父进程退出\n");return 0;
}

程序流程图

返回-1 (失败)
返回0 (子进程)
返回子进程PID (Y) (父进程)
返回>0 (成功回收)
返回<=0 (无更多子进程)
程序开始 (父进程 PID: X)
打印父进程PID (X)
初始化子进程计数器 count=0
进入循环:尝试创建子进程
调用 fork()
perror 打印错误原因
打印已创建子进程数 count
退出循环,开始回收子进程
子进程直接 exit(0) 退出
count 加1,打印子进程PID和累计数
count 是否为100的倍数?
sleep(1) 休息
继续循环
调用 waitpid(-1, &status, 0) 回收子进程
子进程是否正常退出?
打印子进程PID和退出码
打印子进程PID和异常信息
打印所有子进程已回收信息
父进程退出

Makefile

# 编译错误处理示例程序
fork_error: fork_error.cgcc -o fork_error fork_error.c -Wall# 清理编译生成的文件
clean:rm -f fork_error

编译与运行步骤

  1. 代码保存为 fork_error.c,Makefile 同上。
  2. 编译:make fork_error
  3. 运行:./fork_error(注意:此程序会创建大量进程,可能消耗较多系统资源,运行后若未自动结束,可按 Ctrl+C 终止)。

运行结果解读

程序会不断创建子进程,输出类似:

===== 开始尝试创建多个子进程 =====
父进程 PID:12347
成功创建子进程,PID:12348(累计:1)
成功创建子进程,PID:12349(累计:2)
...
成功创建子进程,PID:12647(累计:300)
fork 失败,原因是: Resource temporarily unavailable
已成功创建 300 个子进程
开始回收子进程资源...
子进程 12348 正常退出,退出码:0
子进程 12349 正常退出,退出码:0
...
所有子进程资源已回收,父进程退出

解读:

  • fork() 失败时,perror 会打印错误原因,这里 “Resource temporarily unavailable” 表示系统资源不足(通常是进程数达到上限)。
  • 父进程通过 waitpid(-1, &status, 0) 回收所有子进程:-1 表示等待任意子进程,WIFEXITEDWEXITSTATUS 用于检查子进程退出状态。
  • 子进程创建后立即 exit(0),模拟“完成任务后退出”的场景。
  • 此实例展示了“资源耗尽”场景下的错误处理,以及批量回收子进程的方法,避免系统中积累大量僵尸进程。
实例三:实际应用——简易并发服务器模型

应用场景:实现一个简单的 TCP 服务器,父进程负责监听端口,收到客户端连接后,创建子进程处理该客户端的请求,父进程继续监听新连接。这是早期服务器常用的并发处理模型。

代码实现

/*** @brief 基于 fork() 的简易并发 TCP 服务器* * 父进程监听指定端口(8080),收到客户端连接后,创建子进程处理该客户端的消息(读取并回复),* 父进程继续等待新连接。子进程处理完后自动退出,父进程通过信号处理回收子进程资源。* * @in:无输入参数(固定监听 8080 端口)* * @out:*   - 服务器启动信息、客户端连接信息*   - 子进程处理客户端消息的日志*   - 错误信息(如端口绑定失败、accept 错误等)* * 返回值说明:*   程序正常运行时不会主动退出,发生致命错误时返回 1*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>#define PORT 8080
#define BUFFER_SIZE 1024/*** @brief 信号处理函数:回收子进程资源* * 当子进程退出时,系统会发送 SIGCHLD 信号,此函数捕获该信号并调用 waitpid 回收子进程,* 避免僵尸进程产生。*/
void handle_sigchld(int sig) {// 忽略信号处理函数的返回值,重点是回收资源(void)sig;pid_t pid;int status;// WNOHANG 表示非阻塞:没有子进程退出时立即返回while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {printf("子进程 %d 已退出,资源已回收\n", pid);}
}/*** @brief 子进程处理客户端请求* * 读取客户端发送的消息,打印后回复"收到消息:xxx",然后关闭连接并退出。* * @param client_fd:客户端连接的文件描述符* @param client_addr:客户端的地址信息*/
void handle_client(int client_fd, struct sockaddr_in client_addr) {char buffer[BUFFER_SIZE];ssize_t n;// 打印客户端信息printf("子进程 %d 开始处理客户端 %s:%d 的请求\n", getpid(), inet_ntoa(client_addr.sin_addr),  // 转换IP地址为字符串ntohs(client_addr.sin_port));     // 转换端口号为本地字节序// 读取客户端消息(阻塞,直到有数据或连接关闭)n = read(client_fd, buffer, BUFFER_SIZE - 1);if (n < 0) {perror("读取客户端消息失败");close(client_fd);exit(1);} else if (n == 0) {printf("客户端 %s:%d 主动关闭连接\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));close(client_fd);exit(0);}// 确保字符串以 '\0' 结尾buffer[n] = '\0';printf("子进程 %d 收到消息:%s\n", getpid(), buffer);// 构造回复消息char response[BUFFER_SIZE];snprintf(response, BUFFER_SIZE, "收到消息:%s", buffer);// 发送回复给客户端n = write(client_fd, response, strlen(response));if (n < 0) {perror("回复客户端失败");close(client_fd);exit(1);}// 处理完毕,关闭连接并退出printf("子进程 %d 处理完毕,关闭与客户端 %s:%d 的连接\n", getpid(), inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));close(client_fd);exit(0);
}int main() {int server_fd, client_fd;struct sockaddr_in server_addr, client_addr;socklen_t client_len = sizeof(client_addr);// 注册 SIGCHLD 信号处理函数,用于回收子进程signal(SIGCHLD, handle_sigchld);// 1. 创建 TCP 套接字(socket)server_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_fd < 0) {perror("创建套接字失败");return 1;}// 设置套接字选项:允许端口重用(避免服务器重启时出现 "地址已在使用" 错误)int opt = 1;if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {perror("设置套接字选项失败");close(server_fd);return 1;}// 2. 绑定套接字到端口(bind)memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;         // IPv4server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网络接口server_addr.sin_port = htons(PORT);       // 端口号(转换为网络字节序)if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {perror("绑定端口失败");close(server_fd);return 1;}// 3. 开始监听端口(listen),最多允许 5 个连接排队if (listen(server_fd, 5) < 0) {perror("监听端口失败");close(server_fd);return 1;}printf("服务器已启动,监听端口 %d...\n", PORT);printf("父进程 PID:%d,等待客户端连接...\n", getpid());// 4. 循环接受客户端连接(accept)while (1) {// accept 阻塞等待客户端连接client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);if (client_fd < 0) {perror("接受连接失败");continue;  // 继续等待下一个连接}// 打印新连接信息printf("收到来自 %s:%d 的连接请求\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));// 创建子进程处理该客户端pid_t pid = fork();if (pid == -1) {perror("fork 失败,无法处理该连接");close(client_fd);  // 关闭客户端连接continue;} else if (pid == 0) {// 子进程:不需要服务器套接字,关闭它close(server_fd);// 处理客户端请求handle_client(client_fd, client_addr);// handle_client 中会调用 exit,这里不会执行到} else {// 父进程:不需要客户端套接字(子进程会处理),关闭它close(client_fd);// 继续等待新连接printf("父进程继续等待新连接...\n");}}// 正常情况下不会执行到这里,关闭服务器套接字close(server_fd);return 0;
}

程序流程图

失败
成功
失败
成功
失败
成功
失败
成功
失败
返回0 (子进程)
返回子进程PID (父进程)
服务器启动
注册 SIGCHLD 信号处理函数(回收子进程)
创建 TCP 套接字 (server_fd)
打印错误,程序退出 (返回1)
设置 SO_REUSEADDR 选项(允许端口重用)
绑定套接字到 8080 端口
监听端口(最大排队 5 个连接)
打印服务器启动信息,父进程 PID
进入循环:等待客户端连接
调用 accept() 阻塞等待连接
打印错误,继续等待下一个连接
获取客户端 socket (client_fd) 和地址信息
打印客户端连接信息(IP:端口)
调用 fork() 创建子进程
打印错误,关闭 client_fd,继续等待
子进程:关闭 server_fd(不需要)
调用 handle_client() 处理客户端请求
读取客户端消息 -> 打印 -> 回复 -> 关闭 client_fd -> 退出
父进程:关闭 client_fd(子进程会处理)
打印继续等待新连接信息
子进程退出
系统发送 SIGCHLD 信号给父进程
父进程执行 handle_sigchld() 函数
调用 waitpid() 回收子进程资源
打印子进程已退出信息

Makefile

# 编译并发服务器示例程序
fork_server: fork_server.cgcc -o fork_server fork_server.c -Wall# 清理编译生成的文件
clean:rm -f fork_server

编译与运行步骤

  1. 代码保存为 fork_server.c,Makefile 同上。
  2. 编译:make fork_server
  3. 运行服务器:./fork_server(若提示“绑定端口失败”,可能是 8080 端口被占用,可修改代码中的 PORT 宏)。
  4. 测试客户端连接:
    • 打开新终端,用 telnet 测试:telnet localhost 8080,输入消息后回车,会收到服务器回复。
    • 或用 nc(netcat):echo "Hello Server" | nc localhost 8080,会显示服务器回复。
    • 可同时打开多个终端连接服务器,测试并发处理。

运行结果解读

服务器端输出类似:

服务器已启动,监听端口 8080...
父进程 PID:12348,等待客户端连接...
收到来自 127.0.0.1:54321 的连接请求
父进程继续等待新连接...
子进程 12349 开始处理客户端 127.0.0.1:54321 的请求
子进程 12349 收到消息:Hello Server
子进程 12349 处理完毕,关闭与客户端 127.0.0.1:54321 的连接
子进程 12349 已退出,资源已回收
收到来自 127.0.0.1:54322 的连接请求
父进程继续等待新连接...
...

客户端(telnet)输出:

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello Server
收到消息:Hello Server
Connection closed by foreign host.

解读:

  • 父进程主要负责“监听”和“创建子进程”:通过 socket()bind()listen() 初始化服务器,然后循环调用 accept() 等待客户端连接,每次收到连接就 fork() 子进程处理。
  • 子进程专注于“处理客户端请求”:在 handle_client() 中读取客户端消息、回复消息,完成后关闭连接并退出。
  • 信号处理:子进程退出时会发送 SIGCHLD 信号,父进程通过 handle_sigchld() 函数调用 waitpid() 回收子进程资源,避免僵尸进程。
  • 资源管理:父子进程各自关闭不需要的文件描述符(子进程关闭服务器套接字,父进程关闭客户端套接字),避免资源泄露。
  • 并发能力:多个客户端可同时连接,每个客户端由独立的子进程处理,父进程始终等待新连接,实现了简单的并发服务。

六、fork() 的“背后故事”:写时复制(Copy-On-Write)

你可能会好奇:fork() 创建子进程时复制父进程的所有内存,会不会很浪费资源?比如父进程有 1GB 内存,fork() 后难道瞬间变成 2GB ?

早期的 fork() 确实是这样做的(完全复制),但现在的操作系统都采用了更高效的“写时复制(Copy-On-Write,COW)”机制:

  • fork() 时,子进程并不实际复制父进程的内存,而是和父进程共享同一块内存(只读)。
  • 当父进程或子进程试图修改内存中的数据时,操作系统才会为修改的部分创建副本,只复制这部分数据,而不是整个内存。

这就像两个人共享一本书(内存),平时一起看(只读),谁想在书上做笔记(修改),就把那一页复印下来(复制)再改,不影响对方的书。这种机制大大提高了 fork() 的效率,让创建子进程变得又快又省资源。

七、总结:fork() 的核心要点

咱们用一张图来总结 fork() 的核心机制:

在这里插入图片描述

核心要点回顾:

  1. 功能fork() 用于创建子进程,子进程是父进程的副本。
  2. 声明#include <unistd.h>pid_t fork(void);,属于 POSIX 标准,glibc 实现。
  3. 返回值:父进程中返回子进程 PID,子进程中返回 0,错误返回 -1。
  4. 特性
    • 调用一次,返回两次(父子进程各一次)。
    • 采用写时复制(COW)机制,高效复制资源。
    • 父子进程独立运行,互不干扰。
  5. 注意事项
    • 需回收子进程资源(通过 wait()waitpid() 或信号处理),避免僵尸进程。
    • 父子进程共享文件描述符,但各自关闭不需要的描述符。
    • fork() 失败的常见原因:系统进程数上限、内存不足。

通过今天的讲解,相信你对 fork() 这个“进程分身术”已经了如指掌了。它虽然简单(零参数),但却是 Linux 系统编程中实现并发的基础,很多复杂的服务都离不开它的身影。下次写服务器程序时,不妨试试用 fork() 来实现并发处理,感受一下“分身术”的魅力吧!

http://www.dtcms.com/a/458017.html

相关文章:

  • cesium126,230301,只需要部分倾斜摄影(整个地图太大了):
  • 网站上传文件存储方式爱站网
  • 门户网站架构做一个网站要多久
  • 管理软件网站模板广州专业建设网站
  • redis队列操作
  • 个人怎样申请网站玻璃钢产品哪个网站做推广好
  • ISO 27001 foundation/lead auditor认证比较
  • 【开题答辩全过程】以 爱车小档案应用系统开发与实现为例,包含答辩的问题和答案
  • Java学习之旅第二季-11:继承
  • LangGraph学习笔记 (一)
  • 微网站建设的第一步是进行首页的设置昆明会员网站建设
  • 【自适应滤波】基于新息协方差匹配的自适应EKF (CM-AEKF) vs 经典EKF对比,附MATLAB代码
  • AIGC侵权谁来担责?
  • 职业教育网站建设方案网站美工做专题尺寸多少
  • MySQL包安装 -- SUSE系列(离线RPM包安装MySQL)
  • 老卡文迪许的“四论”密码
  • 个人可以建购物网站吗建站哪个网站好
  • 南京环力建设有限公司网站WordPress 经典博客
  • 网站模块化网站建设记什么科目
  • SSM电影售票管理系统n9y72(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 网站展现形式免费企业注册
  • [工作流节点17] 数据校验与错误处理机制:为自动化流程建立安全阀门
  • ESP32-S3 入门第十天:图像识别基础与 NPU 应用
  • 视频重新照明新突破!北大中科大浙大等提出重照明方法Lumen:一句话让视频秒变电影级光影。
  • 亚马逊商标备案是否必须做网站新手做电商怎么起步
  • 邢台集团网站建设报价网站定制哪家正规
  • 57.Nginx重写,if,基于浏览器分离,防盗链
  • 【多线程】死锁
  • 自学阿里云认证,能救一个是一个!
  • 买域名做网站跳转网新科技集团有限公司