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.pswp.cn/pingmian/79339.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/79339.shtml
英文地址,請注明出處:http://en.pswp.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.程序代碼段 ?…