目录

十、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 系统中的实现与进程间通信](#四、多进程在 Linux 和 Windows 系统中的实现与进程间通信)

[1、Linux 系统](#1、Linux 系统)

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

1.2、常见使用场景:

2、windows系统

3、进程间通信(5种)

五、进程的特殊用法

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

1.1、原理

[1.2、Linux 系统](#1.2、Linux 系统)

[Linux 实现示例](#Linux 实现示例)

[1.3、Windows 实现示例](#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系统:

cpp 复制代码
/**
 *  多进程实践,这里是在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()函数会创建一个与父进程几乎完全相同的子进程,子进程会继承父进程的大部分资源,包括文件描述符、环境变量等。下面是一个简单的示例:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    pid_t pid;
    pid_t pid2;

    // 获取当前父进程Id
    pid = getpid();
    printf("before fork: pid = %d\n",pid);//fork之前获取当前进程的pid
    pid_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,使子进程处理此请求。父进程则继续等待下一个服务请求到达。

代码示例:

cpp 复制代码
#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());//子进程打印自己的pid
    	              sleep(3);//延时一会
                 }
             }
         }
         else
         {
               printf("wait,do nothing\n");//收到的数据不是1则继续等待
         }
     }
    return 0;
}

2、windows系统

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

cpp 复制代码
#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 实现示例
cpp 复制代码
#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 中,可以使用服务来实现类似守护进程的功能。下面是一个简单的服务示例:

cpp 复制代码
#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 系统,多进程技术都在不断演进,为软件的高效执行提供了强有力的基础。

下一章节

十一、C++速通秘籍---多线程-CSDN博客https://blog.csdn.net/weixin_36323170/article/details/147055932?spm=1001.2014.3001.5501

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
码农新猿类1 小时前
服务器本地搭建
linux·网络·c++
GOTXX2 小时前
【Qt】Qt Creator开发基础:项目创建、界面解析与核心概念入门
开发语言·数据库·c++·qt·图形渲染·图形化界面·qt新手入门
徐行1102 小时前
C++核心机制-this 指针传递与内存布局分析
开发语言·c++
序属秋秋秋2 小时前
算法基础_数据结构【单链表 + 双链表 + 栈 + 队列 + 单调栈 + 单调队列】
c语言·数据结构·c++·算法
mldl_4 小时前
(个人题解)第十六届蓝桥杯大赛软件赛省赛C/C++ 研究生组
c语言·c++·蓝桥杯
一个小白14 小时前
C++ 用红黑树封装map/set
java·数据库·c++
Lenyiin4 小时前
《 C++ 点滴漫谈: 三十三 》当函数成为参数:解密 C++ 回调函数的全部姿势
c++·回调函数·lenyiin
埜玊4 小时前
C++之 多继承
c++
1024熙6 小时前
【C++】——lambda表达式
开发语言·数据结构·c++·算法·lambda表达式
mahuifa7 小时前
(2)VTK C++开发示例 --- 绘制多面锥体
c++·vtk·cmake·3d开发