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

十、C++速通秘籍—多进程

上一章节:

九、C++速通秘籍—类和函数-CSDN博客https://blog.csdn.net/weixin_36323170/article/details/147017358?spm=1001.2014.3001.5502

本章节代码

cpp/processEx.cpp · CuiQingCheng/cppstudy - 码云 - 开源中国https://gitee.com/cuiqingcheng/cppstudy/blob/master/cpp/processEx.cpp

目录

上一章节:

本章节代码

一、引言

深入剖析多进程:从原理到跨平台实践

二、理解高并行/高并发,解锁高性能系统的密钥

三、进程与进程状态切换:进程生命周期的蜕变

1、进程的定义

1.1、一个可执行文件与进程的关系

2、进程状态

进程常见的状态划分:

状态切换

四、多进程在 Linux 和 Windows 系统中的实现与进程间通信

1、Linux 系统

1.1、常见实现方式在 Linux 中,创建新进程最常用的方式是使用

1.2、常见使用场景:

2、windows系统

3、进程间通信(5种)

五、进程的特殊用法

1、守护进程:后台运行的默默守护者

1.1、原理

1.2、Linux 系统

Linux 实现示例

1.3、Windows 实现示例

2、唯一进程

六、总结


一、引言

深入剖析多进程:从原理到跨平台实践

        在软件开发的宏大版图中,多进程技术犹如一座桥梁,横跨在提升系统性能与处理复杂任务的两岸。无论是高并发的服务器场景,还是对资源进行高效管理的桌面应用,多进程都扮演着不可或缺的角色。本文从多进程实现高并发的原理出发,逐步探索多进程在 Linux 和 Windows 系统下的运作机制

二、理解高并行/高并发,解锁高性能系统的密钥

在讲解多进程之前,先让我们理解“什么是并行?什么是并发?并了解二者的区别”

并行的前提是多核CPU,指的是多个任务同时在多个核心上同时执行。如下图:

并发执行是指系统能够同时处理多个任务,但这些任务并不一定在同一时刻执行,而是通过快速切换来模拟同时执行的效果

多进程正是利用了并发和并行的优势来实现高并发。以 Web 服务器为例,当大量用户同时访问网站时,服务器可以为每个请求创建一个新的进程来处理。在单核 CPU 环境下,操作系统通过快速切换这些进程,使得它们看起来像是在同时执行。而在多核 CPU 环境下,不同的进程可以被分配到不同的核心上并行执行,大大提高了系统的吞吐量。

三、进程与进程状态切换:进程生命周期的蜕变

1、进程的定义

        进程是操作系统进行资源分配和调度的基本单位。简单来说,进程包含了正在运行的程序的代码、数据以及执行上下文等信息。

1.1、一个可执行文件与进程的关系

        一个可执行文件(程序/软件)可以对应多个进程也可以只对应一个进程,这取决于程序的设计和使用方式。一个可执行文件最少要有一个进程。

        当你多次启动同一个可执行文件时,操作系统会为每次启动创建一个新的进程。例如,你可以同时打开多个记事本程序(假设记事本程序的可执行文件是notepad.exe),每个记事本窗口都对应一个独立的进程。这些进程虽然都源自同一个可执行文件,但它们在内存中是相互独立的,拥有各自的内存空间和执行上下文,彼此之间互不干扰。另外,有些程序在设计上会主动创建多个进程来完成不同的任务。例如,浏览器通常会为每个标签页创建一个独立的进程,这样可以提高浏览器的稳定性和性能。当一个标签页崩溃时,不会影响其他标签页的正常运行。

2、进程状态

进程在整个执行周期中存在多种状态;

进程常见的状态划分:

1、就绪(Ready):进程已经准备好执行,等待CPU调度。就像运动员已经站在起跑线上,等待发令枪响;

2、运行(Running):进程正在CPU上执行。这是进程最活跃的阶段,如同运动员正在全力奔跑;

3、阻塞(Blocked):进程因等待某个事件(如 I/O 操作完成、信号量获取等)而暂时无法执行。比如运动员在比赛中等待接力棒。

4、终止(Terminated):进程已经完成执行或因异常而终止。就好像运动员完成了最后冲刺。

状态切换

        进程状态的切换由操作系统内核负责。当一个运行状态的进程需要等待 I/O 操作完成时,内核会将其状态切换为阻塞状态,并将 CPU 资源分配给其他就绪状态的进程。当 I/O 操作完成后,内核会将该进程的状态切换为就绪状态,等待再次调度。在抢占式调度系统中,内核还会根据进程的优先级,在合适的时机将运行状态的进程切换为就绪状态,以便让优先级更高的进程获得 CPU 资源。

下面是一个设置进程优先级的代码,基于Windows系统:

/***  多进程实践,这里是在windows系统下*/#include <iostream>
#include <windows.h>int main() {// 获取当前进程的句柄HANDLE hProcess = GetCurrentProcess();// 设置进程优先级为高if (SetPriorityClass(hProcess, HIGH_PRIORITY_CLASS)) {std::cout << "Process priority has been set to high." << std::endl;} else {// 获取错误代码DWORD errorCode = GetLastError();std::cerr << "Failed to set process priority. Error code: " << errorCode << std::endl;return 1;}// 这里可以添加其他需要执行的任务代码return 0;
}    

四、多进程在 Linux 和 Windows 系统中的实现与进程间通信

1、Linux 系统

1.1、常见实现方式在 Linux 中,创建新进程最常用的方式是使用

fork()函数。

fork()函数会创建一个与父进程几乎完全相同的子进程,子进程会继承父进程的大部分资源,包括文件描述符、环境变量等。下面是一个简单的示例:

#include <stdio.h>
#include <unistd.h>int main() {pid_t pid;pid_t pid2;// 获取当前父进程Idpid = getpid();printf("before fork: pid = %d\n",pid);//fork之前获取当前进程的pidpid_t retFork = fork(); // 创建子进程pid2 = getpid();if (retFork == -1) {perror("fork failed"); // 子进程创建失败return -1;} else if (pid != pid2) {// 子进程printf("I am the child process. My PID is %d\n", getpid());} else {// 父进程printf("I am the parent process. My PID is %d\n", getpid());}return 0;
}

1.2、常见使用场景:

一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的——父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。

代码示例:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{pid_t pid;int data;while(1){{// 此处可以改成监听网络中信号或者外部请求printf("please input a data\n");//父进程一直等待客户的消息scanf("%d",&data);   }if(data == 1)//收到数据为1就创建一个子进程来处理客户的消息,父进程继续等待其他客户的消息{pid = fork();if(0 == pid)//在子进程里处理{while(1)//这里假设子进程处理时间比较长{printf("do net request,pid = %d\n",getpid());//子进程打印自己的pidsleep(3);//延时一会}}}else{printf("wait,do nothing\n");//收到的数据不是1则继续等待}}return 0;
}

2、windows系统

        在 Windows 中,创建新进程使用CreateProcess()函数。该函数不仅可以创建新进程,还可以指定新进程的启动参数、安全属性等。下面是一个简单的示例:

#include <windows.h>
#include <stdio.h>int main() {// 创建进程STARTUPINFO si;PROCESS_INFORMATION pi;ZeroMemory(&si, sizeof(si));si.cb = sizeof(si);ZeroMemory(&pi, sizeof(pi));if (!CreateProcess(NULL,TEXT("processEx.exe"), // 调用可执行程序NULL,NULL,FALSE,0,NULL,NULL,&si,&pi)){printf("CreateProcess failed: %d\n", GetLastError());return 1;}else{printf("CreateProcess succeed: %d\n", pi.dwProcessId);}// 等待子进程结束// WaitForSingleObject(pi.hProcess, INFINITE);CloseHandle(pi.hProcess);CloseHandle(pi.hThread);return 0;
}

3、进程间通信(5种)

管道(Pipe)管道是一种半双工的通信方式,数据只能单向流动,有匿名管道/命名管道之分,匿名管道用于有亲缘关系的进程,命名管道可用于任意进程匿名管道通常用于父子进程之间的通信。匿名管道使用pipe()函数创建,命名管道使用mkfifo()函数创建。用于简单数据传递,且实时性高的场景。

消息队列(Message Queue):以消息链表形式存在于内核中,可克服信号传递信息少、管道数据无格式和缓冲区受限等问题。一般用于多对多进程间通信,并且对实时性要求不高的异步场景下。例如,一个日志记录进程和多个业务进程之间的通信,业务进程将日志信息发送到消息队列,日志记录进程从队列中取出消息并进行记录(该方式是linux系统中独有的)。

共享内存(Shared Memory)共享内存允许不同进程访问同一块物理内存无需数据复制是最快的 IPC 方式,但需要同步和互斥操作。通过shmget()、shmat()等函数实现。一般用于多个进程需要频繁共享大量数据,例如数据库系统中多个进程共享数据库索引信息,对实时性要求较高的应用,如视频处理、音频处理等,共享内存可以减少数据传输的延迟。

信号量(Semaphore):信号量用于实现进程间的同步和互斥。在 Linux 中,可以使用semget()、semop()等函数操作信号量。适用于进程间资源互斥,以及进程同步,用于协调多个进程的执行顺序,确保一个进程在另一个进程完成某个操作后再继续执行。

前面四个都在一个主机上的操作。

套接字(Socket)可用于不同主机之间的进程通信,也可用于同一主机上的进程通信,支持多种协议。网络通信中服务器和客户端之间的通信,分布式系统中各个节点间通信;

五、进程的特殊用法

1、守护进程:后台运行的默默守护者

1.1、原理

        守护进程是一种在后台运行的特殊进程,它独立于控制终端,通常在系统启动时自动启动,并一直运行直到系统关闭。守护进程的主要作用是执行一些需要长期运行的任务,如系统日志记录、定时任务执行等。

1.2、Linux 系统

创建守护进程通常需要以下步骤:

(1)、使用fork()函数创建一个子进程,然后父进程退出,这样可以使子进程脱离控制终端。

(2)、使用setsid()函数创建一个新的会话,使子进程成为新会话的领导者,从而脱离原有的控制终端。

(3)、改变当前工作目录为根目录,防止占用其他文件系统。

(4)、重设文件权限掩码,防止继承的文件权限影响守护进程的运行。

(5)、关闭不需要的文件描述符,防止资源泄漏。

Linux 实现示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main() {pid_t pid = fork();if (pid == -1) {perror("fork");return 1;} else if (pid != 0) {// 父进程退出exit(0);}// 创建新会话if (setsid() == -1) {perror("setsid");return 1;}// 改变工作目录if (chdir("/") == -1) {perror("chdir");return 1;}// 重设文件权限掩码umask(0);// 关闭标准输入、输出和错误输出close(STDIN_FILENO);close(STDOUT_FILENO);close(STDERR_FILENO);// 守护进程主体,例如记录系统日志while (1) {// 模拟日志记录int fd = open("/var/log/daemon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);if (fd != -1) {write(fd, "Daemon is running\n", 16);close(fd);}sleep(10);}return 0;
}

1.3、Windows 实现示例

在 Windows 中,可以使用服务来实现类似守护进程的功能。下面是一个简单的服务示例:

#include <windows.h>
#include <stdio.h>SERVICE_STATUS serviceStatus;
SERVICE_STATUS_HANDLE serviceStatusHandle;VOID WINAPI ServiceMain(DWORD argc, LPTSTR *argv);
VOID WINAPI ServiceCtrlHandler(DWORD fdwControl);int main(int argc, char **argv) {SERVICE_TABLE_ENTRY serviceTable[] = {{TEXT("MyService"), ServiceMain},{NULL, NULL}};if (!StartServiceCtrlDispatcher(serviceTable)) {printf("StartServiceCtrlDispatcher failed: %d\n", GetLastError());}return 0;
}VOID WINAPI ServiceMain(DWORD argc, LPTSTR *argv) {serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;serviceStatus.dwCurrentState = SERVICE_START_PENDING;serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;serviceStatus.dwWin32ExitCode = 0;serviceStatus.dwServiceSpecificExitCode = 0;serviceStatus.dwCheckPoint = 0;serviceStatus.dwWaitHint = 0;serviceStatusHandle = RegisterServiceCtrlHandler(TEXT("MyService"), ServiceCtrlHandler);if (serviceStatusHandle == NULL) {return;}// 标记服务已启动serviceStatus.dwCurrentState = SERVICE_RUNNING;SetServiceStatus(serviceStatusHandle, &serviceStatus);// 服务主体,例如记录系统日志while (serviceStatus.dwCurrentState == SERVICE_RUNNING) {// 模拟日志记录FILE *fp = fopen("C:\\Logs\\ServiceLog.txt", "a");if (fp != NULL) {fprintf(fp, "Service is running\n");fclose(fp);}Sleep(10000);}
}VOID WINAPI ServiceCtrlHandler(DWORD fdwControl) {switch (fdwControl) {case SERVICE_CONTROL_STOP:serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;SetServiceStatus(serviceStatusHandle, &serviceStatus);// 执行清理操作serviceStatus.dwCurrentState = SERVICE_STOPPED;SetServiceStatus(serviceStatusHandle, &serviceStatus);break;default:break;}
}

2、唯一进程

这里用来避免在一台设备上进程多开。

六、总结

        多进程技术作为操作系统和软件开发的核心技术之一,为我们提供了强大的工具来构建高性能、高可靠性的应用程序。通过深入理解多进程的原理和跨平台实现,我们能够更好地应对复杂的业务需求,为用户提供更优质的服务。无论是 Linux 还是 Windows 系统,多进程技术都在不断演进,为软件的高效执行提供了强有力的基础。


http://www.mrgr.cn/news/97424.html

相关文章:

  • Java八股文-List集合
  • 量子计算入门:Qiskit实战量子门电路设计
  • 【QT】QT的多界面跳转以及界面之间传递参数
  • 【stm32--HAL库DMA+USART+空闲中断不定长收发数据】
  • py文件打包为exe可执行文件,涉及mysql连接失败以及找不到json文件
  • 时间梯度匹配损失 TGMLoss
  • 12.青龙面板自动化我的生活
  • 【MySQL】常用SQL--持续更新ing
  • 08RK3568 gpio模拟i2c 配置hym8563 RTC时钟
  • C++设计模式总结-汇总了全部23种设计模式的详细说明
  • 大语言模型在端到端智驾中的应用
  • 机器视觉3D中激光偏镜的优点
  • 专栏:区块链入门到放弃查看目录
  • Java面试33-fail-safe机制与fail-fast机制分别有什么作用
  • Linux:页表详解(虚拟地址到物理地址转换过程)
  • Dart 语法
  • 字符串-JS
  • 项目总结之常问的一些问题
  • uniapp如何接入星火大模型
  • Java面试34-Kafka的零拷贝原理