C多進程

来源:https://www.cnblogs.com/Jack-artical/archive/2022/12/01/16938014.html
-Advertisement-
Play Games

這篇文章主要是想針對多進程的創建和一些通信手段來進行一下記錄 創建子進程 關於創建子進程的原型一般都是用的這個,直接fork,這個函數在父進程中調用,在父子進程中各有一個pid_t類型的返回值,父進程中得到的是子進程的ID,子進程中得到的是0值。當然調用失敗就是-1。 //創建進程,然後複製出另一份 ...


這篇文章主要是想針對多進程的創建和一些通信手段來進行一下記錄

創建子進程

關於創建子進程的原型一般都是用的這個,直接fork,這個函數在父進程中調用,在父子進程中各有一個pid_t類型的返回值,父進程中得到的是子進程的ID,子進程中得到的是0值。當然調用失敗就是-1。

//創建進程,然後複製出另一份進程
#include <unistd.h>
pid_t fork();

根據不同的fork返回值,父子進程可以分出自己專屬的代碼區域段。例子如下:

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

int i = 10;
int main() {
    pid_t pid;
    pid = fork();
    if (pid == 0) {
        i++;
        printf("I' m the subprocess.The i:%d\n", i);
    } else {
        i--;
        printf("I' m the parent process.The i:%d\n", i);
    }
    return 0;
}

一般來說,寫代碼的理想狀態是最後的程式正常跑,更理想的就是完全不出錯,不過那個太理想了。比如多進程程式中,當父進程結束了,子進程沒有被父進程獲取狀態信息,從而使得進程號依然保留在系統中,占用系統定數的進程號;又比如父進程都結束運行了,子進程還在繼續跑,由init進程來接管。這兩種情況,前者被叫僵屍進程,後者被稱為孤兒進程(這個概念其實我挺犯迷糊,如果有衝突那就是你對,記得提點一聲)。所以,父進程在結束之前,要對子進程負責,要查詢子進程的結束狀態,並確保子進程跑完了才跑路。

wait一下

簡單的方案,就是父進程一直等,實現這個功能的函數原型如下:

#include <sys/wait.h>
pid_t wait(int *statloc);

//配合使用的巨集
WIFEXITED(statloc);						//子進程正常終止,返回非0值
WEXITSTATUS(statloc);						//子進程正常終止,返回退出碼
WIFSIGNALED(statloc);						//因為未捕獲信號而終止,返回非0值
WTERMSIG(statloc);						//配合前一個巨集,返回信號值
WIFSTOPPED(statloc);						//子進程意外終止,返回非0
WSTOPSIG(statloc);						//子進程意外終止,返回信號值

上面函數的通用解讀就是,wait函數的調用會阻塞父進程,一直等著子進程跑完返回狀態信息到statloc才對父進程放行。而對於子進程的結束信息的解讀,就是上面對應的巨集來進行。不過wait的阻塞讓很多人不滿,所以他們實現了另一種wait:

#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *statloc, int options);

使用waitpid處理僵屍進程:

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    pid_t pid;
    int status, i=0;
    pid = fork();
    if (pid == 0) {
        i--;
        printf("subprocess: %d\n", i);
        sleep(5);
        return 6;
    } else {
        //因為只有一個子進程,就不明確指定了
        while (!waitpid(-1, &status, WNOHANG)) {
            i++;
            printf("parent process, %d sec\n", i);
            sleep(1);
        }
        if (WIFEXITED(status))
            printf("Subprocess was ended and return a value :%d\n", WEXITSTATUS(status));
    }
    return 0;
}

進程間通信

比較簡單的通信方式,是創建管道,管道和socket套接字同屬系統資源,創建了管道,就是使得兩個管道在系統提供的記憶體進行通信。實現的原型如下:

#include <unistd.h>
int pipe(int filedes[2]);

所謂管道,是有著兩個口子的,這裡的管道也一樣,filedes就是一個包含了兩個文件描述符的數組,一般傳入的這個參數是空的,函數調用結束後就成了新創建的管道的入口和出口。
嗯,所以這個管道的使用,其實就是這兩個描述符的使用,filedes數組中,第一個是管道入口,第二個是管道出口,這個要註意。

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

int main() {

    pid_t pid;
    int fds[2];
    char str[20];
    pipe(fds);
    pid = fork();

    if (pid != 0) {
        write(fds[1], "balabala", sizeof("balabala"));
        printf("parent process.\n");
        sleep(3);
    } else {
        read(fds[0], str, 20);
        printf("subprocess, get mes: %s\n", str);
    }

    return 0;
}

例子是父進程發送信息,子進程接收信息,實際上反過來也可以,不限定。但信息放進管道,父子進程其實都可以讀取,就像寫了信息在文本,誰都可以讀取。管道的單向只體現在它的信息是從fds[1]進,fds[0]出。為了保證
信息的受眾是對端從而實現雙方通信,往往實現兩個管道,然後一個管道負責發,一個負責收,這樣就不需要預測運行流程。

管道是很便利,但它往往適用於關聯進程(像父子進程),想要無關聯的通信還需要其他機制,比如下麵的3種System V IPC。

System V IPC

針對共用資源的多進程訪問,這種獨占式的訪問會引發大問題,誰先誰後無法控制,這種引發競爭的代碼段,被稱為臨界區。對進程的同步,就是確保進入臨界區只有一個進程。

信號量

它是一個特殊的整數值變數,只支持兩種操作,一個是取,一個是放,分別是P原語和V原語的解讀。因為針對多進程同步和多線程同步都有信號量的概念,雖然語義一致,但實現不一樣,姑且把多進程間信號量稱為信號量,多線程間信號量稱為POSIX信號量。對於信號量的初始化決定了其行為,但最常用的就是二進位信號量,用0和1來代表空置和占用的意義。linux中的實現,往往在sys/sem.h頭文件中,三個系統調用設計成操作一組信號量而不是單個信號量,三個系統調用分別是semget、semop和semctl;而POSIX信號量的實現都在semaphore.h頭文件中。

信號量的創建

#include <sys/sem.h>

//申請信號集,申請成功就返回信號量標記值,失敗返回-1
int semget(key_t key, int num_sems, int sem_flags);

semget的參數key具有唯一性,num_sems則是申請的system V信號量集的信號量數,sem_flags制定了信號量的讀寫許可權。在semget創建信號量成功後,相關聯的內核數據結構體semid_ds也會被創建且初始化,具體存儲的信息就是創建信號量集的進程的用戶ID和組ID,以及信號量集的信號量數還有信號量的讀寫許可權等。

信號量賦初值

具體操作需要依賴semctl函數:

#include <sys/sem.h>
int semctl(int sem_id, int sem_num, int command, ...)

sem_id,當然就是信號量集的標識符了,sem_num於信號量集的意義就像下標之於數組,是標記某某某信號量,command則是執行的命令了。因為這裡要用它來賦初值,所以調用起來就是 semctl(sem_id, 0, SETVAL, sem_union),這個調用其實就是執行SETVAL指示的賦值操作,而sem_union就是攜帶著想要賦值給信號量的初值。不過先不對這個結合體做過多闡述。系統瞭解一下semop先:

#include <sys/sem.h>
int semop(int sem_id, struct sembuf *semops, size_t num_sem_ops);

semop函數是對信號量進行PV操作的關鍵,但具體如何改變要看傳參semops,也就是sembuf這種結構體

struct sembuf {
    unsigned short int sem_num;                  //對應信號量在信號量集中的索引
    short int sem_op;                            //指定操作類型
    short int sem_flag;                          //標誌位
};

可選值為正整型、0和負整型的sem_op以及可選值為IPC_NOWAIT和SEM_UNDO的sem_flag配合起來就決定了semop函數的調用結果。

共用記憶體

很容易理解的一個機制,就是一塊記憶體,進程間可以共用,它的實現都在sys/shm.h中,使用的函數包括shemget、shmat、shmdt、shmctl:

#include <sys/shm.h>

//創建共用記憶體或者獲取已存在的共用記憶體
int shmget(key_t key, size_t size, int shmflag);
//size,位元組為單位,指定記憶體的大小,獲取已存在的共用記憶體可以設置為0;
//shmflag,支持SHM_HUGETLB和SHM_NORESERVE,前者表示用“大頁面”來分配空間給共用記憶體,後者表示不為共用記憶體保留交換分區,這樣記憶體不足的時候繼續寫入就會發起SIGSEGV信號

函數調用成功就返回共用記憶體的標識符,失敗返回-1,然後同樣地,內核中有個相關的數據結構shmid_ds會被創建且初始化。在共用記憶體創建成功後,需要把它關聯到進程的地址空間中,用完了需要進行分離:

//關聯操作,返回共用記憶體被關聯到進程中的具體地址,失敗會返回(void*)-1
void *shmat(int shm_id, const void *shm_addr, int shmflag);
//分離原本關聯好的共用記憶體,成功就回0,失敗回-1
int shmdt(const void *shm_addr);

shmget成功調用返回的標識符就可用於shm_id,shm_addr則是進程內指針,具體函數調用效果還是要看shmflag

  • shm_addr為NULL,關聯地址由系統選擇,這樣更加相容
  • shm_addr非空,shmflag沒有設置SHM_RND,共用記憶體關聯到shm_addr指向地址
  • shm_addr非空,shmflag設置了SHM_RND
    嗯,shmflag標誌位還可以設置SHM_RDONLY,表示進程只讀該共用記憶體,沒設置就讀寫都可(共用記憶體創建時就會設置讀寫許可權);SHM_REMAP,已經關聯呢,就重新關聯;SHM_EXEC,指定可讀

關於關聯成功和取消關聯關係,都會使得shmid_ds的內核數據發生變動,比如關聯成功:
shm_nattach加一、shm_lpid設置為調用進程的PID、shm_atime設置為當前時間
取消關聯成功,就:
shm_nattach減一、shm_lpid設為調用進程的PID、shm_dtime會設置成當前時間
這麼來看,其實關聯和非關聯都是一個記錄,看看什麼時候發生變動,變動的操作者是誰,至於區分開兩者就是前面的shm_nattach了。

嗯,和信號量一樣,共用記憶體的關聯也是準備工作,要用還是要有個函數來進行調用,共用記憶體的就是shmctl,這個函數重點關註command參數,這個是具體如何用的關鍵:

int shmctl(int shm_id, int command, struct shmid_ds *buf);

關於command參數參見下表:

參數 意思 函數調用成功的返回值
IPC_STAT 共用記憶體相關的內核數據結構shmid_ds複製到buf中 0
IPC_SET buf的部分數據複製到共用記憶體相關的內核數據結構shmid_ds中,
刷新shmid_ds.shm_ctime
0
IPC_RMID 標記上刪除,當最後一個進程用完調用shmdt分離後,共用記憶體
就被刪了
0
IPC_INFO 獲取共用記憶體的系統配置,存在轉換成shminfo結構體類型的buf中 內核中共用記憶體信息數組被使用項的最大index值
SHM_INFO 和IPC_INFO類似,但得到的是已分配的共用記憶體占用的資源信息
(嗯,這裡要把buf轉換成shm_info型)
同上
SHM_STAT 類似IPC_STAT,但此時shm_id是用來表示內核中共用記憶體信息數組的 內核共用記憶體信息數組索引為shm_id的標識符
SHM_LOCK 禁止共用記憶體被移動到交換分區 0
SHM_UNLOCK 和上面的相反,允許共用記憶體被移動到交換分區 0

暫時先寫就這麼點吧,後面再來更新

一些相關的內核數據結構:

//system v信號量
#include <sys/sem.h>

//描述IPC對象許可權
struct ipc_perm {
    key_t key;                      //鍵值
    uid_t uid;                      //持有者的有效用戶ID
    gid_t gid;                      //持有者的組ID
    uid_t cuid;                     //創建者的用戶ID
    gid_t cgid;                     //創建者的組ID
    mode_t mode;                    //訪問許可權
    ...
};

//system v信號量的內核數據結構
struct semid_ds {
    struct ipc_perm sem_perm;            //重點關註信號量的操作許可權
    unsigned long int sem_nsems;         //信號量集的信號量數
    time_t sem_otime;                    //最後一次調用semop時間
    time_t sem_ctime;                    //最後一次調用semctl時間
    ...
};


#include <sys/shm.h>
//共用記憶體的內核數據結構
struct shmid_ds {
    struct ipc_perm shm_perm;            //共用記憶體操作許可權
    size_t shm_segsz;                    //共用記憶體大小,以位元組為單位
    __time_t shm_atime;                  //對共用記憶體最後一次調用shmat的時間
    __time_t shm_dtime;                  //對共用記憶體最後一次調用shmdt的時間
    __time_t shm_ctime;                  //對共用記憶體最後一次調用shmctl的時間
    __pid_t shm_cpid;                    //創建者PID
    __pid_t shm_lpid;                    //最後一次執行shmat或者shmdt的進程PID
    ...
};


#include <sys/msg.h>
//消息隊列的內核數據結構
struct msqid_ds {
    struct ipc_perm msg_perm;            //消息隊列操作許可權
    time_t msg_stime;                    //最後一次調用msgsnd時間
    time_t msg_rtime;                    //最後一次調用msgrcv時間
    time_t msg_ctime;                    //最後一次被修改時間
    unsigned long __msg_cbytes;          //消息隊列中已有的位元組數
    msgqnum_t msg_qnum;                  //消息隊列已有消息數
    msglen_t msg_qbytes;                 //消息隊列允許的最大位元組數
    pid_t msg_lspid;                     //最後執行msgsnd的進程PID
    pid_t msg_lrpid;                     //最後執行msgrcv的進程PID
};

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 大家好,我是陶朱公Boy。 前言 上一篇文章《關於狀態機的技術選型,最後一個真心好》我跟大家聊了一下關於”狀態機“的話題。從眾多技術選型中我也推薦了一款阿裡開源的狀態機—“cola-statemachine”。 於是就有小伙伴私信我,自己項目也考慮引入這款狀態機,但網上資料實在太少,能不能系統的介紹 ...
  • 一、Kafka存在哪些方面的優勢 1. 多生產者 可以無縫地支持多個生產者,不管客戶端在使用單個主題還是多個主題。 2. 多消費者 支持多個消費者從一個單獨的消息流上讀取數據,而且消費者之間互不影響。 3. 基於磁碟的數據存儲 支持消費者非實時地讀取消息,由於消息被提交到磁碟,根據設置的規則進行保存 ...
  • 10瓶毒藥其中只有一瓶有毒至少需要幾隻老鼠可以找到有毒的那瓶 身似浮雲,心如飛絮,氣若游絲。 用二分查找和二進位位運算的思想都可以把死亡的老鼠降到最低。 其中,二進位位運算就是每一隻老鼠代表一個二進位0或1,0就代表老鼠存活,1代表老鼠死亡;根據數學運算 23 = 8、24 = 16,那麼至少需要四 ...
  • 1.面向對象 面向對象編程是在面向過程編程的基礎上發展來的,它比面向過程編程具有更強的靈活性和擴展性,所以可以先瞭解下什麼是面向過程編程: 面向過程編程的核心是過程,就是分析出實現需求所需要的步驟,通過函數一步一步實現這些步驟,接著依次調用即可,再簡單理解就是程式 從上到下一步步執行,從頭到尾的解決 ...
  • 來源:https://developer.aliyun.com/article/694020 非同步調用幾乎是處理高併發Web應用性能問題的萬金油,那麼什麼是“非同步調用”? “非同步調用”對應的是“同步調用”,同步調用指程式按照定義順序依次執行,每一行程式都必須等待上一行程式執行完成之後才能執行;非同步調 ...
  • Maven可以使我們在構建項目時需要用到很多第三方類jar包,如下一些常用jar包 而maven的出現可以讓我們避免手動導入jar包出現的某些問題,它可以自動下載那須所需要的jar包 我們只需要在創建的maven項目自動生成的pom.xml中輸入如下代碼 <dependencies> <!--ser ...
  • 很多軟體工程師都認為MD5是一種加密演算法,然而這種觀點是不對的。作為一個 1992 年第一次被公開的演算法,到今天為止已經被髮現了一些致命的漏洞。本文討論MD5在密碼保存方面的一些問題。 ...
  • 經常看到有群友調侃“為什麼搞Java的總在學習JVM調優?那是因為Java爛!我們.NET就不需要搞這些!”真的是這樣嗎?今天我就用一個案例來分析一下。 昨天,一位學生問了我一個問題:他建了一個預設的ASP.NET Core Web API的項目,也就是那個WeatherForecast的預設項目模 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...