C语言-进程

news/2024/9/28 0:22:57 标签: 服务器, linux, 运维

一,进程的基本认识

1,进程的简介

        进程描述是一个程序执行过程。当程序执行后,执行过程开始,则进程产生;执行过程结束,则进程也就结束了.进程和我们普通电费程序最大的区别就是,进程是动态的,他是一个过程,而程序是静态的.

2,进程的定义

        在 C 语言中,进程通常被认为是一个正在执行的程序的实例。

        从操作系统的角度来看,进程包含了程序的代码、数据、堆、栈以及各种系统资源(如文件描述符、信号处理等)。每个进程都在自己独立的内存空间中运行,与其他进程相互隔离,以确保安全性和稳定性。

        在 C 语言编程中,可以通过系统调用和库函数来操作进程。例如,可以使用 fork 系统调用创建一个新的进程,新创建的子进程会继承父进程的一部分资源,但拥有独立的内存空间和运行状态。

3,进程和程序的区别

一、定义

  • 程序:是一组指令的有序集合,是一个静态的概念。它以文件的形式存储在磁盘等存储介质上,如一个 C 语言源程序经过编译链接后生成的可执行文件。
  • 进程:是程序的一次执行过程,是一个动态的概念。它包含了程序执行所需的各种资源,如内存空间、寄存器状态、文件描述符等。

二、特征

  • 程序
    • 具有永久性,只要不被删除或损坏,程序会一直存在于存储介质中。
    • 是被动的实体,本身不能运行,只有被加载到内存中并被执行时才成为进程。
  • 进程
    • 具有动态性,其状态会随着时间不断变化,如从创建到运行、暂停、终止等不同状态的转换。
    • 是活动的实体,在系统中可以独立运行,并且可以和其他进程并发执行。

三、资源占用

  • 程序:不占用系统的运行资源,如 CPU、内存等,只是存储在磁盘上的代码和数据。
  • 进程:在运行时需要占用系统资源,包括 CPU 时间片、内存空间、I/O 设备等。每个进程都有自己独立的地址空间,确保进程之间的数据隔离。

四、组成部分

  • 程序:由代码和数据组成,代码是一系列指令,数据包括常量、变量等。
  • 进程:由程序代码、数据、进程控制块(PCB)等组成。PCB 包含了进程的各种状态信息、资源分配情况、调度信息等,是操作系统管理进程的重要依据。

例如,一个用 C 语言编写的文本编辑器程序,当它存储在磁盘上时,它只是一个程序。只有当这个程序被加载到内存中执行时,才成为一个进程。在运行过程中,进程会占用 CPU 时间进行文本编辑操作,使用内存来存储正在编辑的文本内容等数据。如果有多个用户同时运行这个文本编辑器程序,那么系统中会有多个不同的进程,每个进程都有自己独立的内存空间和运行状态,但它们都在执行相同的程序代码。

4, C语言的并发和并行.

        在学习进程和线程的主要目的就是并行和并发编程,所以了解这类知识是很重要的.

        并行执行 : 表示多个任务能够同时执行,依赖于物理的支持,比如 cpu 8 核芯,则可以同时执行 8 个 任务.

        并发执行 : 同一时间段 有多个任务在同时执行,由操作系统调度算法来实现,比较典型的就是时间片轮转.
        并发和并行简单的来说就是一个是硬件一个是软件.

5,进程管理

        1. 在 Linux 系统中,管理进程使用 树型 管理方式。
        2. 每个进程都需要与其它某一个进程建立 父子关系 ,对应的进程叫做父进程。
        3. Linux 系统会为每个进程分配 id , 这个 id 作为当前进程的唯一标识,当进程结束,则会被回收
        4. 进程的 id 与父进程的 id 分别通过 getpid() getppid() 来获取

二,进程的空间分配和堆栈大小

1,进程的空间分配

        进程建立之后,系统则要为这个进程分配相应的空间,32 Linux 系统中,会为每个进程分配 4G 的空间。
        4G的进程空间主要分为两部分,高位 1G 是内核空间,低位 3G 是用户空间。

用户空间又具体分为如下区间

        stack : 存放非静态的局部变量
        heap : 动态申请的内存
        .bss : 未初始化过的全局变量(包括初始化为 0 , 未初始化过的静态变量 ( 包括初始化为 0)
        .data : 初始化过并且值不为 0 的全局变量 , 初始化过的不为 0 静态变量
        .rodata : 只读变量(字符串之类)
        .text : 程序文本段(包括函数,符号常量)
Tips:
        1. 当用户进程需要通过内核获取资源时,会切换到内核态运行,此时当前进程会使用内核空间的资源
        2. 用户需要切换到内核态运行时 , 主要是通过 系统调用

2,虚拟地址与物理地址

虚拟地址空间中的每个地址都是一个虚拟地址
        虚拟地址 : 虚拟地址并不代表真实的内存空间 , 而是一个用于寻址的编号
        物理地址 : 是指内存设备中真实存在的存储空间的编号
        虚拟地址通过映射的方式建立与物理地址的关联, 从而达到访问虚拟地址就可以访问到对应的物理地址
       1, 在 cpu 中有一个硬件 MMU( 内存管理单元 ) , 负责虚拟地址与物理地址的映射管理以及虚拟地址访问
        2,操作系统可以设置 MMU 中的映射内存段.
在操作系统中使用虚拟地址空间主要是基于以下原因 :
      1  直接访问物理地址, 会导致地址空间没有隔离 , 很容易导致数据被修改
      2  通过虚拟地址空间可以实现每个进程空间都是独立的,操作系统会映射到不用的物理地址区间,在 访问时互不干扰.

3,进程的堆栈大小

        <1>Linux下进程栈的默认大小是 8M ,可以通过 ulimit -s 查看
        <2>堆的大小理论上大概等于进程虚拟空间大小 - 内核虚拟内存大小, Linux 下,进程的高位 1G 留给内核, 低位3G留给用户,所以进程堆大小小于 3G
        <3>最大进程数
Linux 下,通过 ulimit -u 查看系统的最大进程数

三,进程的状态管理

1、进程的主要状态

 
  1. 就绪状态(Ready)

    • 进程已准备好运行,等待被操作系统调度分配 CPU 时间片。
    • 此时进程的所有资源都已准备好,只等 CPU 可用。
    • 例如,一个在等待队列中的进程,随时可以被调度执行。
  2. 运行状态(Running)

    • 进程正在被 CPU 执行。
    • 处于这个状态的进程占用 CPU 资源,执行其指令。
    • 例如,正在进行计算或处理任务的进程。
  3. 阻塞状态(Blocked)

    • 进程由于等待某个事件(如 I/O 操作完成、等待信号等)而暂停执行。
    • 此时进程不能继续执行,直到等待的事件发生。
    • 例如,一个进程正在等待用户输入或者等待从磁盘读取数据。

2、状态转换

 
  1. 就绪 -> 运行:

    • 当操作系统选择一个就绪进程并分配 CPU 时间片给它时,该进程从就绪状态转换为运行状态。
    • 例如,操作系统的调度程序从就绪队列中选择一个进程,并将其加载到 CPU 上执行。
  2. 运行 -> 就绪:

    • 当正在运行的进程时间片用完或者被更高优先级的进程抢占时,它会从运行状态转换回就绪状态,重新等待被调度。
    • 例如,一个进程的时间片到期,操作系统将其从 CPU 上移除,放入就绪队列。
  3. 运行 -> 阻塞:

    • 如果正在运行的进程需要等待某个事件发生,它会从运行状态转换为阻塞状态。
    • 例如,一个进程发起一个磁盘 I/O 操作,此时它会进入阻塞状态,等待 I/O 完成。
  4. 阻塞 -> 就绪:

    • 当进程等待的事件发生时,它会从阻塞状态转换为就绪状态,等待被调度执行。
    • 例如,一个进程等待的磁盘 I/O 操作完成,操作系统将其状态改为就绪,放入就绪队列。

3,通过用户输入来理解进程状态的变化

#include <stdio.h>
int main()
{
int num=-1;
printf("please input number:");
scanf("%d",&num);
printf("num=%d\n",num);
return 0;
}

状态的变化过程:

四,进程的相关命令

一、查看进程信息

 
  1. ps

    • 功能:报告当前系统的进程状态。
    • 常用参数:
      • -e:显示所有进程。
      • -f:全格式显示,包括 UID、PID、PPID、C、STIME、TTY、TIME、CMD 等信息。
    • 示例:ps -ef将显示系统中所有进程的详细信息。
  2. top

    • 功能:实时显示系统中各个进程的资源占用情况,类似于 Windows 系统中的任务管理器。
    • 可以动态地查看 CPU、内存等资源的使用情况,并可以按照不同的字段进行排序。

 

二、终止进程

 
  1. kill

    • 功能:向进程发送信号,以控制进程的行为。最常用的是发送终止信号(SIGTERM)来终止进程。
    • 用法:kill [信号编号] 进程ID。例如,kill -9 1234表示向进程 ID 为 1234 的进程发送强制终止信号(SIGKILL)。
  2. killall

    • 功能:通过进程名称来终止进程。
    • 用法:killall [进程名称]。例如,killall firefox将终止所有名为 firefox 的进程。
 

三、启动和停止进程

 
  1. bg

    • 功能:将一个在前台运行的进程放到后台运行,并继续执行。
    • 用法:在前台运行的进程中按下Ctrl+Z暂停进程,然后输入bg将其放到后台。
  2. fg

    • 功能:将一个在后台运行的进程调到前台运行。
    • 用法:fg [作业编号]。如果只有一个后台作业,可以直接输入fg将其调到前台。

 

四、查看进程树

 
  1. pstree
    • 功能:以树状结构显示系统中的进程关系。
    • 常用参数:
      • -p:显示进程的 PID。
      • -u:显示进程的所属用户。
    • 示例:pstree -p将以树状结构显示系统中所有进程的 PID。

五、进程优先级调整

 
  1. nice

    • 功能:在启动进程时设置进程的优先级。优先级的值越低,进程的优先级越高。
    • 用法:nice -n [优先级值] [命令]。例如,nice -n -10 firefox将以较高的优先级启动 firefox 浏览器。
  2. renice

    • 功能:调整已经运行的进程的优先级。
    • 用法:renice [优先级值] [进程ID]。例如,renice -5 1234将调整进程 ID 为 1234 的进程的优先级为 -5。
 

这些命令在 Linux 系统中非常有用,可以帮助用户管理和监控系统中的进程。

五,进程的基本使用

1,创建进程

     在 Unix/Linux 系统中,可以使用fork()函数来创建新进程。

  1. fork()函数介绍:

    • fork()函数会创建一个新的进程,这个新进程几乎是当前进程的一个副本,包括代码、数据和打开的文件描述符等。
    • fork()函数返回两次,一次在父进程中,返回新创建子进程的进程 ID;一次在子进程中,返回 0。
    • 如果fork()失败,会返回 -1。
  2. #include <stdio.h>
    #include <unistd.h>
    
    int main() {
        pid_t pid;
        pid = fork();
        if (pid == 0) {
            // 子进程
            printf("This is child process.\n");
        } else if (pid > 0) {
            // 父进程
            printf("This is parent process. Child process ID is %d.\n", pid);
        } else {
            // fork 失败
            perror("fork");
            return -1;
        }
        return 0;
    }

2,进程多任务

一、进程多任务的概念

 

进程多任务是指操作系统能够同时管理多个进程,让它们在不同的时间段内共享系统资源,如 CPU、内存、I/O 设备等。每个进程都被认为是独立的执行单元,拥有自己的地址空间、数据和代码。通过快速切换不同进程的执行,操作系统给用户一种多个任务同时进行的错觉。

 

二、实现进程多任务的方式

 
  1. 时间片轮转调度

    • 操作系统将 CPU 的时间划分为固定长度的时间片。
    • 每个进程在被分配到一个时间片内执行,如果时间片用完,操作系统会暂停该进程的执行,并切换到另一个进程。
    • 这样,多个进程可以轮流使用 CPU,从而实现多任务。
  2. 优先级调度

    • 每个进程被赋予一个优先级。
    • 操作系统优先执行优先级高的进程,当高优先级进程执行完毕或进入等待状态时,再执行低优先级的进程。
    • 这种方式可以确保重要的任务能够及时得到处理。
 

三、进程多任务的优点

 
  1. 提高系统资源利用率

    • 多个进程可以同时使用 CPU、内存和 I/O 设备等资源,避免了资源的闲置浪费。
    • 例如,当一个进程在等待 I/O 操作完成时,CPU 可以被分配给其他进程使用。
  2. 增强系统的响应性

    • 用户可以同时运行多个应用程序,每个程序都能及时得到响应。
    • 即使某个进程占用了大量的 CPU 时间,其他进程也不会被完全阻塞,系统仍然能够保持一定的响应能力。
  3. 实现并行处理

    • 在多核处理器系统中,进程多任务可以充分利用多个 CPU 核心,实现真正的并行处理。
    • 不同的进程可以被分配到不同的核心上同时执行,从而大大提高系统的处理能力。
 

四、进程多任务的挑战

 
  1. 进程切换开销

    • 频繁地进行进程切换会带来一定的开销,包括保存和恢复进程的上下文、更新调度数据结构等。
    • 这些开销可能会影响系统的性能,特别是在进程数量较多或切换频率较高的情况下。
  2. 资源竞争和同步问题

    • 多个进程同时访问共享资源时,可能会出现资源竞争和冲突。
    • 为了解决这些问题,需要使用同步机制,如互斥锁、信号量等,但这些机制也会增加系统的复杂性和开销。
  3. 内存管理问题

    • 每个进程都需要一定的内存空间来存储代码、数据和栈等。
    • 当系统中同时运行的进程数量较多时,内存可能会成为瓶颈,需要有效的内存管理策略来确保系统的稳定性和性能。
 

总之,进程多任务是现代操作系统的重要特征之一,它为用户提供了更加高效和便捷的计算环境。然而,在实现进程多任务的过程中,也需要解决一系列的技术挑战,以确保系统的性能和稳定性。

五、在 Unix/Linux 系统下,可以使用fork()函数创建多个进程来实现多任务。

#include <stdio.h>
#include <unistd.h>

void task1() {
    for (int i = 0; i < 5; i++) {
        printf("Task 1: %d\n", i);
        sleep(1);
    }
}

void task2() {
    for (int i = 0; i < 5; i++) {
        printf("Task 2: %d\n", i);
        sleep(1);
    }
}

int main() {
    pid_t pid;
    pid = fork();
    if (pid == 0) {
        // 子进程执行 task1
        task1();
    } else if (pid > 0) {
        // 父进程执行 task2
        task2();
    } else {
        perror("fork");
        return -1;
    }
    return 0;
}

3进程的退出

一、正常退出

 
  1. return语句:
    • main函数中使用return语句可以使进程正常退出。return语句的返回值通常被用作进程的退出状态码。
    • 例如:
 
   int main() {
       // 一些操作
       return 0; // 0 通常表示正常退出
   }
 
  1. exit函数:
    • exit函数可以在程序的任何地方调用,用于立即终止进程的执行。
    • exit函数接受一个整数参数作为进程的退出状态码。
    • 例如:
 
   #include <stdlib.h>

   void someFunction() {
       // 一些操作
       exit(0); // 0 表示正常退出
   }
 

二、异常退出

 
  1. 发生严重错误:

    • 当程序遇到严重错误,如内存访问错误、除零错误等,可能会导致进程异常退出。
    • 这种情况下,操作系统通常会生成错误信息并终止进程。
  2. 接收到信号:

    • 进程可以接收到来自操作系统或其他进程发送的信号。某些信号会导致进程异常退出,例如SIGKILL(强制终止)和SIGSEGV(段错误)。
    • 可以通过信号处理函数来捕获某些信号并进行适当的处理,但对于一些强制终止的信号,进程无法阻止退出。
    • 例如:
 
   #include <signal.h>

   void signalHandler(int signum) {
       // 处理信号
       printf("Received signal %d\n", signum);
       exit(signum); // 根据信号决定退出状态码
   }

   int main() {
       signal(SIGINT, signalHandler); // 捕获中断信号(Ctrl+C)
       while (1) {
           // 程序的主要逻辑
       }
       return 0;
   }

4,进程的的等待

一、使用waitwaitpid函数

 
  1. wait函数:
    • wait函数用于等待任意一个子进程结束。如果调用wait的进程没有子进程,那么它会立即返回 -1。
    • wait函数会阻塞当前进程,直到有一个子进程结束。当一个子进程结束时,wait函数会收集子进程的退出状态,并返回结束的子进程的进程 ID。
    • 函数原型:pid_t wait(int *status);
    • 参数status是一个整数指针,用于接收子进程的退出状态。如果不需要获取退出状态,可以将其设置为NULL
    • 例如:
 
   #include <stdio.h>
   #include <sys/wait.h>

   int main() {
       pid_t pid = fork();
       if (pid == 0) {
           // 子进程
           printf("Child process.\n");
           _exit(0);
       } else if (pid > 0) {
           // 父进程
           int status;
           pid_t terminated_pid = wait(&status);
           if (terminated_pid == -1) {
               perror("wait");
               return 1;
           }
           if (WIFEXITED(status)) {
               printf("Child process %d exited with status %d.\n", terminated_pid, WEXITSTATUS(status));
           }
       } else {
           perror("fork");
           return 1;
       }
       return 0;
   }
 
  1. waitpid函数:
    • waitpid函数比wait函数更加灵活,可以等待特定的子进程,并且可以设置一些选项来控制等待的行为。
    • 函数原型:pid_t waitpid(pid_t pid, int *status, int options);
    • 参数pid指定要等待的子进程的进程 ID。如果pid为 -1,则等待任意一个子进程。
    • 参数options可以是 0 或者由一些常量组合而成,用于指定等待的选项。例如,WNOHANG表示非阻塞等待,如果没有子进程结束,立即返回 0。
    • 例如:
 
   #include <stdio.h>
   #include <sys/wait.h>

   int main() {
       pid_t pid1 = fork();
       if (pid1 == 0) {
           // 第一个子进程
           printf("First child process.\n");
           _exit(1);
       }
       pid_t pid2 = fork();
       if (pid2 == 0) {
           // 第二个子进程
           printf("Second child process.\n");
           _exit(2);
       }
       int status;
       pid_t terminated_pid = waitpid(pid2, &status, 0);
       if (terminated_pid == -1) {
           perror("waitpid");
           return 1;
       }
       if (WIFEXITED(status)) {
           printf("Child process %d exited with status %d.\n", terminated_pid, WEXITSTATUS(status));
       }
       return 0;
   }
 

二、等待的意义

 
  1. 资源回收:
    • 当子进程结束时,它可能占用一些系统资源,如内存、文件描述符等。通过等待子进程,父进程可以确保这些资源被正确回收,避免资源泄漏。
  2. 获取子进程的退出状态:
    • 父进程可以通过等待获取子进程的退出状态,从而了解子进程的执行结果。这对于错误处理和程序的逻辑控制非常重要。
  3. 同步:
    • 等待子进程可以实现父进程和子进程之间的同步。例如,父进程可以在子进程完成某些任务后再继续执行。

5,进程的替换

一、exec系列函数介绍

  1. exec函数的作用:

    • exec系列函数用于在当前进程的地址空间中执行一个新的程序,从而替换当前正在运行的进程。
    • 新程序的代码、数据和栈将替换原进程的相应部分,而进程的 ID 保持不变。
  2. 常见的exec函数:

    • execlexeclpexecle:这些函数以列表的形式接收命令行参数。
    • execvexecvpexecve:这些函数以数组的形式接收命令行参数。

二、函数原型及参数说明

  1. execl函数原型:int execl(const char *path, const char *arg0,..., (char *)0);

    • path:新程序的路径名。
    • arg0arg1等:新程序的命令行参数,最后一个参数必须是NULL
  2. execv函数原型:int execv(const char *path, char *const argv[]);

    • path:新程序的路径名。
    • argv:一个以NULL结尾的字符串数组,包含新程序的命令行参数。

三、示例代码

使用execl函数的示例:

#include <stdio.h>
#include <unistd.h>

int main() {
    printf("Before exec.\n");
    execl("/bin/ls", "ls", "-l", NULL);
    perror("exec failed");
    return 0;
}

使用execv函数的示例:

#include <stdio.h>
#include <unistd.h>

int main() {
    printf("Before exec.\n");
    char *argv[] = {"ls", "-l", NULL};
    execv("/bin/ls", argv);
    perror("exec failed");
    return 0;
}

四、注意事项

  1. 错误处理:
    • 如果exec函数调用成功,新程序将替换当前进程,并且不会返回。如果调用失败,会返回 -1,并设置errno来指示错误原因。可以使用perror函数来打印错误信息。
  2. 环境变量:
    • execleexecve函数可以接收一个额外的参数来设置新程序的环境变量。
  3. 进程替换的效果:
    • 进程替换后,原进程的代码、数据和栈被新程序的相应部分替换,但进程的 ID、打开的文件描述符等资源通常会保持不变。

进程替换在实际应用中非常有用,例如在 shell 脚本中执行外部命令、启动新的应用程序等场景。

六使用进程的注意事项

一、资源管理

 
  1. 内存管理:

    • 进程在运行过程中可能会动态分配内存。确保在不再需要时及时释放内存,以避免内存泄漏。可以使用malloccalloc等函数分配内存,并使用free函数释放。
    • 注意内存访问越界的问题,避免访问未分配或已释放的内存区域,这可能导致程序崩溃或出现不可预测的行为。
  2. 文件描述符:

    • 进程可能会打开文件、网络连接等资源,这些资源通常由文件描述符表示。在进程结束前,确保关闭不再需要的文件描述符,以释放系统资源。
    • 注意文件描述符的正确使用和管理,避免出现文件描述符泄漏或错误的文件操作。

二、错误处理

 
  1. 系统调用错误:

    • 当使用系统调用函数(如forkexecwait等)时,要检查返回值以确定是否发生错误。如果发生错误,系统调用通常会返回 -1,并设置errno变量来指示错误原因。
    • 使用perror函数可以方便地打印错误信息,帮助调试问题。
  2. 异常情况处理:

    • 考虑进程可能遇到的各种异常情况,如被其他进程发送信号中断、资源不足等。可以使用信号处理函数来处理特定的信号,以确保进程在异常情况下能够正确地退出或进行适当的恢复操作。

三、进程间通信

 
  1. 同步与互斥:

    • 如果多个进程需要共享资源或进行协作,需要考虑同步和互斥问题。可以使用信号量、互斥锁等机制来确保对共享资源的正确访问,避免出现竞争条件和数据不一致的情况。
    • 注意同步机制的正确使用和避免死锁的发生。
  2. 通信方式选择:

    • 根据实际需求选择合适的进程间通信方式,如管道、消息队列、共享内存等。不同的通信方式有不同的特点和适用场景,要根据具体情况进行选择。
    • 确保通信的可靠性和安全性,避免数据丢失或被篡改。

四、性能考虑

 
  1. 进程创建和销毁开销:

    • 创建和销毁进程会带来一定的系统开销,包括内存分配、资源初始化等。如果需要频繁地创建和销毁进程,可能会影响系统性能。
    • 考虑是否可以使用其他方式(如线程)来减少进程创建的开销,或者优化进程的生命周期管理。
  2. 进程调度:

    • 操作系统的进程调度算法会影响进程的执行效率。了解操作系统的调度策略,合理设置进程的优先级和资源需求,以提高进程的响应时间和系统的整体性能。

五、可移植性

 
  1. 不同操作系统的差异:

    • C 语言在不同的操作系统上可能有不同的行为和实现。在编写涉及进程的代码时,要考虑到不同操作系统之间的差异,尽量使用可移植的代码和函数。
    • 例如,在 Unix/Linux 和 Windows 系统上,进程创建、等待和通信的函数可能不同,需要进行适当的条件编译或使用跨平台的库。
  2. 编译器差异:

    • 不同的编译器可能对 C 语言的标准实现有一些差异。在使用特定的编译器时,要注意其对进程相关功能的支持和行为。
 

总之,在 C 语言中使用进程需要仔细考虑资源管理、错误处理、进程间通信、性能和可移植性等方面的问题,以确保程序的正确性、可靠性和高效性。


http://www.niftyadmin.cn/n/5679912.html

相关文章

组播基础-1

文章目录 组播的应用场景组播解决方案组播服务模型组播地址组播协议域内组播路由协议域间组播路由协议 组播的应用场景 多媒体、流媒体的应用&#xff1a;网络电视、网络电台、实时视频会议培训、联合作业&#xff0c;远程医疗、远程教育等点到多点的数据发布应用 组播解决方案…

Python | Leetcode Python题解之第430题扁平化多级双向链表

题目&#xff1a; 题解&#xff1a; class Solution:def flatten(self, head: "Node") -> "Node":def dfs(node: "Node") -> "Node":cur node# 记录链表的最后一个节点last Nonewhile cur:nxt cur.next# 如果有子节点&#…

脚手架是什么?详细版+通俗易懂版!!!!!!

脚手架&#xff08;Scaffolding&#xff09;在软件开发领域&#xff0c;特别是在前端开发和全栈开发环境中&#xff0c;是一个术语&#xff0c;用来描述一个辅助工具或框架&#xff0c;它旨在帮助开发者快速搭建项目的基础结构和开发环境。这些基础结构可能包括项目的目录结构、…

【ARM】解决ArmDS Fast Models 中部分内核无法上电的问题

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 解决ArmDS Fast Models 中部分内核无法上电的问题。 2、 问题场景 在调用ArmDS的Fast Models中的Cortex-A55的模型&#xff0c;只有Core 0是上电状态&#xff0c;而Core 1处于掉电状态&#xff0c;如图2-1所示&…

【锁住精华】MySQL锁机制全攻略:从行锁到表锁,共享锁到排他锁,悲观锁到乐观锁

MySQL有哪些锁 1、按照锁的粒度划分 行锁 是最低粒度的的锁&#xff0c;锁住指定行的数据&#xff0c;加锁的开销较大&#xff0c;加锁较慢&#xff0c;可能会出现死锁的情况&#xff0c;锁的竞争度会较低&#xff0c;并发度相对较高。但是如果where条件里的字段没有加索引&…

优化 Go 语言数据打包:性能基准测试与分析

场景&#xff1a;在局域网内&#xff0c;需要将多个机器网卡上抓到的数据包同步到一个机器上。 原有方案&#xff1a;tcpdump -w 写入文件&#xff0c;然后定时调用 rsync 进行同步。 改造方案&#xff1a;使用 Go 重写这个抓包逻辑及同步逻辑&#xff0c;直接将抓到的包通过网…

通义千问:让我的编程工作效率翻倍的秘密武器

在日益繁忙的工作环境中&#xff0c;选择合适的编程工具已成为提升开发者工作效率的关键。不同的工具能够帮助我们简化代码编写、自动化任务、提升调试速度&#xff0c;甚至让团队协作更加顺畅。在这篇博客中&#xff0c;我将分享一个让我工作效率翻倍的编程工具——通义千问大…

C#和数据库高级:虚方法

文章目录 一、抽象方法和抽象类中的思考1.1、回顾抽象方法的特点1.2、针对抽象方法问题的引出 二、虚方法的使用步骤2.1、虚方法重写方法的调用2.2、系统自带的虚方法2.3、重写Equals方法2.4、虚方法和抽象方法的比较 三、虚方法和抽象方法的联系3.1、ToString()方法的应用 一、…