Linux 进程间通信(IPC)详解

进程间通信(IPC)深入解析

一、进程间通信概述

在操作系统里,不同进程间常常需要进行数据交换、同步协调等操作,进程间通信(Inter - Process Communication,IPC)机制应运而生。在Linux系统中,常见的IPC机制有管道、信号量、共享内存、消息队列和套接字。这些机制各自具备独特的特性,适用于不同的应用场景,并且在各类系统和应用程序开发中得到广泛应用,也是技术面试中的重点考查内容。

思考问题1:为什么需要进程间通信?

在多进程的环境下,各个进程通常是独立运行的,但有些情况下,它们需要协同工作。比如,一个进程负责收集用户输入,另一个进程负责对这些输入进行处理,这就需要两个进程之间进行数据传递。另外,当多个进程需要访问同一资源时,为了避免冲突,就需要通过进程间通信来进行同步和协调。

思考问题2:不同的IPC机制适用于哪些场景?

不同的IPC机制有不同的特点和适用场景。例如,管道适合简单的父子进程间的数据传递;信号量主要用于进程间的同步和互斥;共享内存适合大量数据的快速传输;消息队列适用于需要按消息类型分类处理数据的场景;套接字则常用于网络环境下的进程间通信。

二、管道

2.1 管道分类

管道分为有名(命名)管道和无名管道。

有名管道

有名管道也叫命名管道,它以文件的形式存在于文件系统中,不同进程可以通过这个文件进行通信,即便这些进程没有亲缘关系。

mkfifo fifo		# 创建一个叫做fifo的管道

创建完成后,使用ls -l命令查看文件类型,会看到文件类型为p,这代表该文件是一个管道文件。

无名管道

无名管道是通过系统调用pipe创建的,它没有对应的文件系统实体,只能用于具有亲缘关系的进程(如父子进程)之间的通信。

2.2 管道的特点和阻塞行为

数据存储

管道大小在文件系统层面显示为零,但数据实际上是存储在内存中的。管道本质上是内核中的一块缓冲区,用于临时存储要传输的数据。

打开条件

读打开和写打开的进程必须同时打开管道。若只有读进程打开管道,读操作会阻塞,直到有写进程打开并写入数据;若只有写进程打开管道,写操作也会阻塞,直到有读进程打开管道读取数据。

读阻塞

读打开的进程在管道没有数据时会阻塞,直到有数据被写入管道。当所有写端关闭且管道中的数据都被读完后,读操作会返回0,表示已到达文件末尾。

写关闭处理

写打开的进程关闭管道时,读打开的进程会返回零。此时读进程知道写进程已经停止写入数据,可以结束读取操作。

2.3 如何使用管道在两个进程之间传递数据?

第一步,创建一个管道文件

使用mkfifo命令创建一个命名管道文件,例如:

mkfifo fifo
第二步,创建两个进程,a.cb.c
//a.c用来往管道里面写数据:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>int main()
{int fd = open("./fifo", O_WRONLY);if (fd == -1){perror("open");exit(1);}printf("fd=%d\n", fd);while (1){printf("input:\n");char buff[128] = {0};fgets(buff, 128, stdin);if (strncmp(buff, "end", 3) == 0){break;}write(fd, buff, strlen(buff));}close(fd);exit(0);
}
//b.c用来在管道里面收数据:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>int main()
{int fd = open("./fifo", O_RDONLY);if (fd == -1){perror("open");exit(1);}printf("fd=%d\n", fd);while (1){char buff[128] = {0};int n = read(fd, buff, 127);if (n == 0){break;}printf("buff = %s\n", buff);}close(fd);exit(0);
}
第三步,同时打开两个文件,进行通信

当使用管道进行数据传输时,必须有两个进程同时打开这个管道文件,一个负责读,一个负责写,否则不能正常打开。在打开文件后,当写进程没有进行写操作前,读进程将会阻塞。

管道的特点是读打开和写打开的进程可以循环读写数据。当管道文件被关闭后,读操作返回值为0,可以作为读进程结束的条件。

2.4 无名管道

无名管道通过pipe来创建。其实现原理是需要提供一个整型数组,数组的两个元素分别作为读端和写端的文件描述符。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main()
{int fd[2];if (pipe(fd) == -1){perror("pipe");exit(1);}// 父进程写,子进程读pid_t pid = fork();if (pid == -1){perror("fork");exit(1);}if (pid == 0){close(fd[1]);while (1){char buff[128] = {0};if (read(fd[0], buff, 127) == 0){break;}printf("child read:%s\n", buff);}close(fd[0]);}else{close(fd[0]);while (1){printf("input:\n");char buff[128] = {0};fgets(buff, 128, stdin);if (strncmp(buff, "end", 3) == 0){break;}write(fd[1], buff, strlen(buff));}close(fd[1]);}exit(0);
}

2.5 有名管道和无名管道的区别

  • 通信范围:无名管道只能在父子进程之间通信,而有名管道可以在任意两个进程之间通信。
  • 存在形式:无名管道没有对应的文件系统实体,而有名管道以文件的形式存在于文件系统中。

2.6 管道的通信方式

管道的通信方式是半双工,即能发送和接收数据,但不能同时进行发送和接收操作。

2.7 写入管道的数据位置

写入管道的数据存储在内存中。不使用文件进行数据传递是因为使用文件进行传递涉及到I/O操作,效率较低,而管道直接在内存中操作,数据传输速度更快。

2.8 管道的实现原理

假设管道有一个分配的内存空间,将其划分为一个字节一个字节的单元,有两个操作这块空间的指针。用size来表示管道的总大小,设一个头指针和一个尾指针指向管道的起始位置。写入数据时,头指针往后移动,指向待写入的下一个位置;读数据时,读掉尾指针所在位置的数据,然后尾指针往后移动。只要尾指针赶上头指针,说明管道中的数据已读完。等到头指针指到内存最末尾时,会循环到起始地址。管道的内存是有限的,在没读掉的位置不能写入数据,就像一个循环队列一样。

思考问题3:管道的缓冲区大小有限制吗?如果数据量超过缓冲区大小会怎样?

管道的缓冲区大小是有限制的,不同的系统可能有不同的默认值,通常为几KB到几十KB不等。当数据量超过缓冲区大小时,写操作会阻塞,直到有足够的空间可以继续写入数据。这是为了防止数据溢出,保证数据的有序传输。

思考问题4:管道在多进程环境下可能会出现哪些问题?如何解决?

在多进程环境下,管道可能会出现数据竞争、死锁等问题。例如,多个写进程同时向管道写入数据可能会导致数据混乱;如果读进程和写进程的操作不协调,可能会出现死锁。解决这些问题可以使用同步机制,如信号量、互斥锁等,来协调进程对管道的访问。

2.9 写端关闭和读端关闭的处理

当写端关闭时,读端read()会返回0;当读关闭时,写端write()会异常终止(触发SIGPIPE信号)。

三、信号量

3.1 PV操作

信号量通常是一个正数值,一般代表可用资源的数目。对信号量的操作主要有PV操作。

  • P操作:获取资源,对信号量的值减一。如果减一后信号量的值小于0,进程会进入阻塞状态,等待其他进程释放资源。
  • V操作:释放资源,对信号量的值加一。如果加一后信号量的值小于等于0,说明有进程在等待该资源,会唤醒一个等待的进程。

3.2 相关概念

  • 临界资源:一次仅允许一个进程使用的共享资源,如打印机、共享内存区域等。
  • 临界区:访问临界资源的代码段。为了保证临界资源的正确使用,需要对临界区进行保护,防止多个进程同时访问。

3.3 信号量的操作步骤

  1. 创建 初始化:使用semget函数创建信号量集,并使用semctl函数进行初始化。
  2. P操作,获取资源:使用semop函数执行P操作,获取对临界资源的访问权限。
  3. V操作,释放资源:使用semop函数执行V操作,释放对临界资源的访问权限。PV操作没有严格的先后顺序,要根据当时的使用需求来决定。
  4. 删除信号量:使用semctl函数删除信号量集。

3.4 信号量的操作和接口

信号量的主要操作函数有semget(创建)、semctl(控制,初始化)和semop(PV操作)。

//sem.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/sem.h>union semun 
{int val;    
};void sem_init();
void sem_p();
void sem_v();
void sem_destroy();
//sem.c
#include "sem.h"
static int semid = -1;void sem_init()
{semid = semget((key_t)1234, 1, IPC_CREAT | IPC_EXCL | 0600);if (semid == -1){semid = semget((key_t)1234, 1, IPC_CREAT | 0600);if (semid == -1){perror("semget");return;}}else{union semun a;a.val = 1;if (semctl(semid, 0, SETVAL, a) == -1) // SETVAL表示初始化值{perror("semctl setval");}}
}void sem_p()
{struct sembuf buf;buf.sem_num = 0; // 信号量的下标buf.sem_op = -1;buf.sem_flg = SEM_UNDO; // 这个标志位表示着当操作发生异常时由内核释放资源,避免资源一直占用if (semop(semid, &buf, 1) == -1){perror("semop p");}
}void sem_v()
{struct sembuf buf;buf.sem_num = 0;buf.sem_op = 1;buf.sem_flg = SEM_UNDO; // 这个标志位表示着当操作发生异常时由内核释放资源,避免资源一直占用if (semop(semid, &buf, 1) == -1){perror("semop v");}
}void sem_destroy()
{if (semctl(semid, 0, IPC_RMID) == -1) // IPC_RMID表示删除{perror("semctl destroy");}    
}

3.5 创建两个进程使用信号量进行资源调用

//a.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "sem.h"int main()
{sem_init();for (int i = 0; i < 5; i++){sem_p();printf("A");fflush(stdout);int n = rand() % 3;sleep(n);printf("A");fflush(stdout);n = rand() % 3;sleep(n);sem_v();}return 0;
}
//b.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "sem.h"int main()
{sem_init();for (int i = 0; i < 5; i++){sem_p();printf("B");fflush(stdout);int n = rand() % 3;sleep(n);printf("B");fflush(stdout);n = rand() % 3;sleep(n);sem_v();}sleep(10);sem_destroy();return 0;
}

思考问题5:信号量的值可以为负数吗?负数代表什么含义?

信号量的值可以为负数。当信号量的值为负数时,其绝对值表示正在等待该资源的进程数量。例如,信号量的值为 -2,表示有两个进程正在等待该资源的释放。

思考问题6:如果在使用信号量时忘记释放资源(即没有执行V操作)会怎样?

如果忘记执行V操作,信号量的值不会增加,其他等待该资源的进程将一直处于阻塞状态,无法获取资源,从而导致死锁或资源饥饿问题。因此,在使用信号量时,必须确保在适当的时候执行V操作,释放资源。

四、共享内存

4.1 共享内存的原理和优势

共享内存是一种高效的进程间通信方式,它允许不同进程直接访问同一块物理内存区域,避免了数据的多次拷贝,从而提高了数据传输的效率。
在这里插入图片描述

//a.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/shm.h>int main()
{// 创建共享内存int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0600);if (shmid == -1){perror("shmget");exit(1);}// 获取共享内存char* s = (char*)shmat(shmid, NULL, 0);if (s == (char*)-1){perror("shmat");exit(1);}while (1){char buff[128] = {0};fgets(buff, 128, stdin);strcpy(s, buff);if (strncmp(buff, "end", 3) == 0){break;}}// 分离共享内存shmdt(s);exit(0);
}
//b.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/shm.h>int main()
{// 创建共享内存int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0600);if (shmid == -1){perror("shmget");exit(1);}// 获取共享内存char* s = (char*)shmat(shmid, NULL, 0);if (s == (char*)-1){perror("shmat");exit(1);}while (1){if (strncmp(s, "end", 3) == 0){break;}printf("s=%s", s);sleep(1);}// 分离共享内存shmdt(s);// 销毁共享内存shmctl(shmid, IPC_RMID, NULL);exit(0);
}

4.2 存在问题及改进方案

上述代码存在问题:当没有输入值的时候,b.c会循环打印当时的共享内存中的值。改进方案是使用信号量,让两个程序不能同时访问临界资源。

4.3 共享内存和信号量的使用

需要使用信号量的两个文件sem.csem.h,跟之前不同的是之前使用了一个信号量,这次需要使用两个。

//sem.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/sem.h>union semun 
{int val;    
};void sem_init();
void sem_p(int index);
void sem_v(int index);
void sem_destroy();
//sem.c
#include "sem.h"
static int semid = -1;void sem_init()
{semid = semget((key_t)1234, 2, IPC_CREAT | IPC_EXCL | 0600);if (semid == -1){semid = semget((key_t)1234, 2, IPC_CREAT | 0600);if (semid == -1){perror("semget");return;}}else{union semun a;int arr[2] = {1, 0};for (int i = 0; i < 2; i++){a.val = arr[i];if (semctl(semid, i, SETVAL, a) == -1) // SETVAL表示初始化值{perror("semctl setval");}            }        }
}void sem_p(int index)
{struct sembuf buf;buf.sem_num = index; // 信号量的下标buf.sem_op = -1;buf.sem_flg = SEM_UNDO; // 这个标志位表示着当操作发生异常时由内核释放资源,避免资源一直占用if (semop(semid, &buf, 1) == -1){perror("semop p");}
}void sem_v(int index)
{struct sembuf buf;buf.sem_num = index;buf.sem_op = 1;buf.sem_flg = SEM_UNDO; // 这个标志位表示着当操作发生异常时由内核释放资源,避免资源一直占用if (semop(semid, &buf, 1) == -1){perror("semop v");}
}void sem_destroy()
{if (semctl(semid, 0, IPC_RMID) == -1) // IPC_RMID表示删除{perror("semctl destroy");}    
}
//a.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/shm.h>
#include "sem.h"int main()
{// 创建共享内存int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0600);if (shmid == -1){perror("shmget");exit(1);}// 映射共享内存char* s = (char*)shmat(shmid, NULL, 0);if (s == (char*)-1){perror("shmat");exit(1);}sem_init();while (1){printf("input:\n");char buff[128] = {0};fgets(buff, 128, stdin);sem_p(0); // s1,第一个信号量,初始值为1strcpy(s, buff);sem_v(1); // s2,第二个信号量,初始值为0if (strncmp(buff, "end", 3) == 0){break;}}// 分离共享内存shmdt(s);exit(0);
}
//b.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/shm.h>
#include "sem.h"int main()
{// 创建共享内存int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0600);if (shmid == -1){perror("shmget");exit(1);}// 获取共享内存char* s = (char*)shmat(shmid, NULL, 0);if (s == (char*)-1){perror("shmat");exit(1);}sem_init();while (1){sem_p(1);if (strncmp(s, "end", 3) == 0){break;}printf("s=%s", s);sem_v(0);}sem_destroy();// 断开共享内存映射shmdt(s);// 销毁shmctl(shmid, IPC_RMID, NULL);exit(0);
}

4.4 共享内存和管道的区别

共享内存对物理内存进行操作,管道对内核缓冲区进行操作,二者存在以下不同:

内存位置与管理方式
  • 共享内存:共享内存是在物理内存中开辟一块特定的区域,通过操作系统的内存管理机制,将其映射到不同进程的虚拟地址空间中。这样,多个进程可以直接访问同一块物理内存,实现数据共享。操作系统负责管理共享内存的分配与回收,进程通过系统调用如shmgetshmat等来请求和使用共享内存。例如,在多个进程需要频繁共享大量数据,如数据库系统中多个查询进程可能需要共享数据缓存时,就可以使用共享内存。
  • 管道:管道的内核缓冲区是由操作系统在内核空间中分配的一块内存区域,用于暂存管道两端进程间传输的数据。它的大小通常有一定限制,并且由操作系统自动管理其数据的进出和空间利用。当进程向管道写入数据时,数据被复制到内核缓冲区;读进程从管道读取数据时,再从内核缓冲区复制到用户空间。例如,在ls | grep这样的命令组合中,ls命令的输出通过管道传输到grep命令,中间的数据就是暂存在管道的内核缓冲区中。
数据访问特性
  • 共享内存:进程可以直接对共享内存进行读写操作,就像访问自己的内存一样,速度非常快,因为不需要进行数据在用户空间和内核空间之间的复制。但是,由于多个进程可以同时访问共享内存,为了保证数据的一致性和完整性,需要使用同步机制,如信号量、互斥锁等,来协调进程对共享内存的访问。否则,可能会出现数据竞争等问题。
  • 管道:管道的读写是有方向的,数据只能从写端流向读端。写进程将数据写入内核缓冲区,读进程从内核缓冲区读取数据。当管道满时,写进程会被阻塞,直到有数据被读走,腾出空间;当管道空时,读进程会被阻塞,直到有数据写入。这种机制保证了数据的有序传输,但也意味着数据的读写是顺序进行的,不能像共享内存那样随机访问。
数据可见性与持续性
  • 共享内存:一旦数据被写入共享内存,只要其他进程有访问权限,就可以立即看到更新后的数据,数据在共享内存中的存在是持续性的,直到被显式修改或删除。即使所有使用共享内存的进程都暂时退出,共享内存中的数据仍然存在于物理内存中,只要没有被操作系统回收或其他进程修改,下次进程再访问时,数据依然保持原来的状态。
  • 管道:管道中的数据具有临时性和一次性的特点。当数据被读进程从内核缓冲区读取后,数据就从管道中消失了,其他进程无法再次读取到相同的数据。而且,当所有与管道相关的文件描述符都被关闭后,管道所占用的内核缓冲区资源会被操作系统自动释放,其中的数据也会被清除。

思考问题7:共享内存可能会带来哪些安全隐患?如何防范?

共享内存可能带来的安全隐患包括数据泄露、数据被恶意篡改等。由于多个进程可以直接访问共享内存,若没有适当的权限控制和加密机制,敏感数据可能会被其他进程获取或修改。防范措施包括设置合理的访问权限,对共享内存中的数据进行加密处理,以及使用同步机制确保数据的一致性和完整性。

思考问题8:如果多个进程同时对共享内存进行写操作会怎样?

如果多个进程同时对共享内存进行写操作,会导致数据竞争问题,可能会使共享内存中的数据变得混乱,出现数据不一致的情况。为了避免这种情况,需要使用同步机制,如信号量、互斥锁等,来保证同一时间只有一个进程可以对共享内存进行写操作。

五、消息队列

5.1 消息队列的特点

添加消息和读取消息是消息队列的基本操作。消息是一个结构体,结构体名字由自己定义,特殊的地方是第一个成员是长整型(代表消息的类型),且该类型的值至少为1。

为什么消息类型必须大于零?

长整型如果是0号,就是不区分消息类型,在函数调用中,要是传入0的话,无论是什么消息类型都能读到。这样可以根据不同的消息类型对消息进行分类处理,提高消息处理的灵活性和效率。

5.2 消息队列的接口函数

  1. msgget():创建或获取消息队列。
int msgget(key_t key, int msgflg);

key是消息队列的键值,用于唯一标识一个消息队列;msgflg是标志位,用于指定创建方式和权限等。

  1. msgrcv():读取消息。
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

msqid是消息队列的标识符;msgp是用于存储接收到的消息的缓冲区;msgsz是缓冲区的大小;msgtyp是期望接收的消息类型;msgflg是标志位,用于指定操作方式。

  1. msgsnd():发送消息。
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

参数含义与msgrcv类似。

5.3 示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/msg.h>struct mess
{long type;char buff[128];
};int main()
{int msgid = msgget((key_t)1234, IPC_CREAT | 0600);if (msgid == -1){perror("msgget");exit(1);}struct mess m;m.type = 1;strcpy(m.buff, "hello");if (msgsnd(msgid, &m, sizeof(m.buff), 0) == -1){perror("msgsnd");exit(1);}exit(0);
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/msg.h>struct mess
{long type;char buff[128];
};int main()
{int msgid = msgget((key_t)1234, IPC_CREAT | 0600);if (msgid == -1){perror("msgget");exit(1);}struct mess m;if (msgrcv(msgid, &m, sizeof(m.buff), 1, 0) == -1){perror("msgrcv");exit(1);}printf("read:%s\n", m.buff);exit(0);
}

思考问题9:消息队列的大小有限制吗?如果消息队列满了会怎样?

消息队列的大小是有限制的,不同的系统可能有不同的默认值。当消息队列满了时,后续的msgsnd操作会阻塞,直到队列中有空间可以容纳新的消息。这是为了防止消息队列溢出,保证消息的有序存储和处理。

思考问题10:消息队列在分布式系统中有哪些应用场景?

在分布式系统中,消息队列可以用于异步通信、任务调度、解耦服务等场景。例如,在一个电商系统中,订单服务在处理订单时可以将订单信息发送到消息队列中,库存服务、物流服务等可以从消息队列中获取订单信息并进行相应的处理,这样可以提高系统的并发处理能力和可扩展性。

六、IPC管理命令

6.1 ipcs

ipcs命令可以查看消息队列、共享内存、信号量的使用情况。例如:

  • ipcs -m:查看共享内存的使用信息。
  • ipcs -q:查看消息队列的使用信息。
  • ipcs -s:查看信号量的使用信息。

6.2 ipcrm

使用ipcrm命令可以进行删除操作。手动移除的命令格式为ipcrm -s/m/q +id,分别用于删除信号量、共享内存、消息队列。例如:

  • ipcrm -m 1234:删除ID为1234的共享内存段。
  • ipcrm -q 5678:删除ID为5678的消息队列。
  • ipcrm -s 9012:删除ID为9012的信号量集。

通过这些命令,系统管理员可以方便地管理系统中的IPC资源,确保系统的稳定运行。

思考问题11:在使用ipcrm命令删除IPC资源时需要注意什么?

在使用ipcrm命令删除IPC资源时,需要确保该资源不再被其他进程使用。如果在其他进程还在使用该资源时删除,可能会导致这些进程出现异常,甚至崩溃。因此,在删除之前,需要先确认相关进程已经停止使用该资源,或者采取适当的同步机制来确保安全删除。

思考问题12:如何定期清理系统中的IPC资源,以避免资源浪费?

可以编写脚本,结合ipcs命令查看IPC资源的使用情况,根据一定的规则(如资源的使用时间、是否有进程关联等)筛选出不再使用的资源,然后使用ipcrm命令进行删除。还可以设置定时任务,定期执行该脚本,以确保系统中的IPC资源得到及时清理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/79339.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度解析ComfyUI的使用

一、ComfyUI 概述 ComfyUI 本质上是一个专为 AI 绘画爱好者和专业人士打造的用户界面工具&#xff0c;它的核心作用是将复杂的 AI 绘画生成过程以直观的方式呈现给用户。与传统的图像生成工具不同&#xff0c;ComfyUI 借助其独特的节点化工作流系统&#xff0c;把深度学习模型…

模型测试报错:有2张显卡但cuda.device_count()显示GPU卡数量只有一张

此贴仅为记录debug过程&#xff0c;为防后续再次遇见 问题 问题情境 复现文章模型&#xff0c;使用GPU跑代码&#xff0c;有两张GPU&#xff0c;设置在 cuda: 1 上跑 问题描述 在模型测试加载最优模型时报错&#xff1a;torch.cuda.device_count()显示GPU卡数量只有一张&…

【计网】认识跨域,及其在go中通过注册CORS中间件解决跨域方案,go-zero、gin

一、跨域&#xff08;CORS&#xff09;是什么&#xff1f; 跨域&#xff0c;指的是浏览器出于安全限制&#xff0c;前端页面在访问不同源&#xff08;协议、域名、端口任一不同&#xff09;的后端接口时&#xff0c;会被浏览器拦截。 比如&#xff1a; 前端地址后端接口地址是…

内存性能测试方法

写于 2022 年 6 月 24 日 内存性能测试方法 - Wesley’s Blog dd方法测试 cat proc/meminfo console:/ # cat proc/meminfo MemTotal: 3858576 kB MemFree: 675328 kB MemAvailable: 1142452 kB Buffers: 65280 kB Cached: 992252 …

AVFormatContext 再分析二

说明 &#xff1a;将 avfromatContext 的变量依次打印分析&#xff0c;根据ffmpeg 给的说明&#xff0c;猜测&#xff0c;结合网上的文章字节写测试代码分析二。 37 AVInputFormat *iformat; /** * The input container format. * * Demuxing only, set by avfo…

深入了解Linux系统—— 进程优先级

前言 我们现在了解了进程是什么&#xff0c;进程状态表示什么 &#xff0c;我们现在继续来了解进程的属性 —— 进程优先级 进程执行者 在了解进程优先级之前&#xff0c;先来思考一个问题&#xff1a;在我们进行文件访问操作时&#xff0c;操作系统是如何直到我们是谁&#x…

Expected SARSA算法详解:python 从零实现

&#x1f9e0; 向所有学习者致敬&#xff01; “学习不是装满一桶水&#xff0c;而是点燃一把火。” —— 叶芝 我的博客主页&#xff1a; https://lizheng.blog.csdn.net &#x1f310; 欢迎点击加入AI人工智能社区&#xff01; &#x1f680; 让我们一起努力&#xff0c;共创…

1penl配置

好的&#xff0c;根据您提供的 1pctl 命令输出信息&#xff0c;我们来重新依次回答您的所有问题&#xff1a; 第一&#xff1a;1Panel 怎么设置 IP 地址&#xff1f; 根据您提供的 user-info 输出&#xff1a; 面板地址: http://$LOCAL_IP:34523/93d8d2d705 这里的 $LOCAL_I…

链表的回文结构题解

首先阅读题目&#xff1a; 1.要保证是回文结构 2.他的时间复杂度为O(n)、空间复杂度为O(1) 给出思路: 1.首先利用一个函数找到中间节点 2.利用一个函数逆置中间节点往后的所有节点 3.现在有两个链表&#xff0c;第一个链表取头节点一直到中间节点、第二个链表取头结点到尾…

【LLaMA-Factory实战】1.3命令行深度操作:YAML配置与多GPU训练全解析

一、引言 在大模型微调场景中&#xff0c;命令行操作是实现自动化、规模化训练的核心手段。LLaMA-Factory通过YAML配置文件和多GPU分布式训练技术&#xff0c;支持开发者高效管理复杂训练参数&#xff0c;突破单机算力限制。本文将结合结构图、实战代码和生产级部署经验&#…

C++负载均衡远程调用学习之 Dns-Route关系构建

目录 1.LARS-DNS-MYSQL环境搭建 2.LARSDNS-系统整体模块的简单说明 3.Lars-Dns-功能说明 4.Lars-Dns-数据表的创建 5.Lars-Dns-整体功能说明 6.Lars-DnsV0.1-Route类的单例实现 7.Lars-DnsV0.1-Route类的链接数据库方法实现 8.Lars-DnsV0.1-定义存放RouteData关系的map数…

fastapi+vue中的用户权限管理设计

数据库设计&#xff1a;RBAC数据模型 这是一个典型的基于SQLAlchemy的RBAC权限系统数据模型实现&#xff0c;各模型分工明确&#xff0c;共同构成完整的权限管理系统。 图解说明&#xff1a; 实体关系&#xff1a; 用户(USER)和角色(ROLE)通过 USER_ROLE 中间表实现多对多关系…

【Python实战】飞机大战

开发一个飞机大战游戏是Python学习的经典实战项目&#xff0c;尤其适合结合面向对象编程和游戏框架&#xff08;如Pygame&#xff09;进行实践。以下是游戏设计的核心考虑因素和模块划分建议&#xff1a; 一、游戏设计核心考虑因素 性能优化 Python游戏需注意帧率控制&#xff…

Flowable7.x学习笔记(十八)拾取我的待办

前言 本文从解读源码到实现功能&#xff0c;完整的学习Flowable的【TaskService】-【claim】方法实现的任务拾取功能。 一、概述 当调用 TaskService.claim(taskId, userId) 时&#xff0c;Flowable 会先加载并校验任务实体&#xff0c;再判断该任务是否已被认领&#xff1b;若…

SQL经典实例

第1章 检索记录 1.1 检索所有行和列 知识点&#xff1a;使用SELECT *快速检索表中所有列&#xff1b;显式列出列名&#xff08;如SELECT col1, col2&#xff09;提高可读性和可控性&#xff0c;尤其在编程场景中更清晰。 1.2 筛选行 知识点&#xff1a;通过WHERE子句过滤符合条…

HTTPcookie与session实现

1.HTTP Cookie 定义 HTTP Cookie &#xff08;也称为 Web Cookie 、浏览器 Cookie 或简称 Cookie &#xff09;是服务器发送到 用户浏览器并保存在浏览器上的一小块数据&#xff0c;它会在浏览器之后向同一服务器再次发 起请求时被携带并发送到服务器上。通常&#xff0…

【算法基础】冒泡排序算法 - JAVA

一、算法基础 1.1 什么是冒泡排序 冒泡排序是一种简单直观的比较排序算法。它重复地走访待排序的数列&#xff0c;依次比较相邻两个元素&#xff0c;如果顺序错误就交换它们&#xff0c;直到没有元素需要交换为止。 1.2 基本思想 比较相邻元素&#xff1a;从头开始&#xf…

0902Redux_状态管理-react-仿低代码平台项目

文章目录 1 Redux 概述1.1 核心概念1.2 基本组成1.3 工作流程1.4 中间件&#xff08;Middleware&#xff09;1.5 适用场景1.6 优缺点1.7 Redux Toolkit&#xff08;现代推荐&#xff09;1.8 与其他工具的对比1.9 总结 2 todoList 待办事项案例3 Redux开发者工具3.1 核心功能3.2…

《ATPL地面培训教材13:飞行原理》——第6章:阻力

翻译&#xff1a;Leweslyh&#xff1b;工具&#xff1a;Cursor & Claude 3.7&#xff1b;过程稿 第6章&#xff1a;阻力 目录 引言寄生阻力诱导阻力减少诱导阻力的方法升力对寄生阻力的影响飞机总阻力飞机总重量对总阻力的影响高度对总阻力的影响构型对总阻力的影响速度稳…

C++总结01-类型相关

一、数据存储 1.程序数据段 • 静态&#xff08;全局&#xff09;数据区&#xff1a;全局变量、静态变量 • 堆内存&#xff1a;程序员手动分配、手动释放 • 栈内存&#xff1a;编译器自动分配、自动释放 • 常量区&#xff1a;编译时大小、值确定不可修改 2.程序代码段 •…