【操作系統(Linux)】——通過案例學習父子進程的線程異步性

本篇旨在通過幾個案例來學習父子進程的線程異步性

一、父進程與子進程

我們將要做的: 創建父子進程,觀察父子進程執行的順序,了解進程執行的異步行為

源代碼:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h> // 定義了 POSIX 操作系統 API(Unix/Linux 下的系統調用函數)
#include <stdlib.h>int main()
{pid_t pid; // 進程idchar*msg;  // 信息緩沖區int k;     // 變量,后面用于控制執行打印的次數printf("觀察父子進程執行的先后順序,了解調度算法的特征\n");pid=fork(); // 創建子進程switch(pid){case 0:msg="子進程在運行";k=3;break;case -1:msg="進程創建失敗";break;    default:msg="父進程在運行";k=5;break;}while(k>0){puts(msg);	sleep(1);	k--;		}exit(0);
}

🔧1. 頭文件講解

  1. #include <sys/types.h>
  • 作用:定義數據類型,如 pid_t
  • pid_t 是一個整型,用于表示進程 ID,確保跨平臺一致性。
  • 一般與 fork()wait() 等系統調用一起使用。
  1. #include <unistd.h>
  • 作用:定義了 POSIX 操作系統 API(Unix/Linux 下的系統調用函數)。
  • 提供本程序中用到的:
    • fork():創建子進程
    • sleep():讓進程休眠若干秒
    • 還包括 getpid()(獲取進程ID)、exec族函數(程序替換)等。
  1. #include <stdlib.h>
  • 作用:標準庫函數,如內存分配、程序控制等。
  • 本程序中使用了:
    • exit(0):正常退出當前進程(0 表示正常退出)

🧠 2. 核心函數講解

fork()

  • 函數原型:pid_t fork(void);
  • 作用:創建一個新的子進程,該子進程是調用它的進程的副本。
  • 返回值:
    • 父進程中fork() 返回子進程的 PID(大于 0)
    • 子進程中fork() 返回 0
    • 創建失敗返回 -1

switch(pid)

  • 根據 fork() 的返回值來判斷當前是:
    • 子進程pid == 0
    • 父進程pid > 0
    • 創建失敗pid == -1

puts(msg)

  • 輸出字符串 msg 并自動換行,功能類似于 printf("%s\\n", msg);,但更簡單。

sleep(1)

  • 暫停當前線程執行 1 秒鐘,模擬處理過程,也便于觀察進程輸出順序。

exit(0)

  • 正常終止當前進程。系統看到返回值 0,認為程序成功執行。

📌 3. 程序運行邏輯總結

  1. 調用 fork() 創建子進程,得到兩個并發執行的進程。
  2. 每個進程根據 fork() 的返回值設定自己的輸出內容(msg)和輸出次數(k)。
  3. 每個進程都進入 while(k>0) 循環,每秒輸出一次 msg,共輸出 k 次。
  4. 最終執行 exit(0) 正常退出。

🧪 4. 運行效果說明

實際運行時,輸出類似:

觀察父子進程執行的先后順序,了解調度算法的特征
父進程在運行
子進程在運行
子進程在運行
父進程在運行
...

由于父子進程是并發執行的,它們輸出的先后順序會隨著調度器算法系統負載等因素而變化。


二、主進程與子進程

我們將做的: 創建主線程和子線程,觀察多線程執行的順序,了解線程執行的異步行為

源代碼:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h> // POSIX 線程庫函數static int run=1;    // 子線程循環判斷條件,主線程設置為 0 后子線程結束
static int retvalue; // 子線程退出時返回的值,供主線程獲取void *threadfunc(void*arg)
{int*running=arg; // 接受主線程傳入的參數printf("子線程初始化完畢,傳入參數為:%d\n",*running);	while(*running)	//子線程通過 *running 控制循環是否繼續{printf("子線程正在運行\n");usleep(1); // 微秒級休眠}printf("子線程退出\n");retvalue=8;pthread_exit((void*)&retvalue); // 返回 retvalue 的地址給主線程
}
int main()
{pthread_t tid; // 線程idint ret=-1;int times=3;int i=0;int *ret_join=NULL;// 創建一個線程,線程函數為threadfunc,傳入參數為&runret=pthread_create(&tid,NULL,(void*)threadfunc,&run);	if(ret!=0){printf("建立線程失敗\n");return 1;}printf("主線程創建子線程后在運行...\n");usleep(1);	// 主線程短暫休眠,為了讓子線程有機會先運行printf("主線程調用usleep(1)...\n");for(;i<times;i++){printf("主線程打印i=%d\n",i);usleep(1);}run=0; // 子進程控制參數設置為0,通知子進程結束pthread_join(tid,(void*)&ret_join);	printf("線程返回值為:%d\n",*ret_join);return 0;
}

這段代碼是一個使用 pthread 實現多線程的基礎示例,展示了如何創建線程、線程間共享數據、線程退出返回值,以及主線程如何等待子線程完成。下面詳細逐行講解:


? 1. 頭文件講解

#include <pthread.h>     // POSIX 線程庫函數
  • pthread.h 是 POSIX 標準線程庫頭文件,提供線程創建、同步、退出等函數定義。

🧠 2. 全局變量定義

static int run = 1;          // 子線程循環判斷條件,主線程設置為 0 后子線程結束
static int retvalue;         // 子線程退出時返回的值,供主線程獲取
  • run 是主線程與子線程共享的控制變量。
  • retvalue 將作為子線程 pthread_exit 返回值的地址,供主線程獲取。

🚀 3. 線程函數 threadfunc

void *threadfunc(void* arg)
{int* running = arg;printf("子線程初始化完畢,傳入參數為:%d\n", *running);	while (*running){printf("子線程正在運行\n");usleep(1); // 微秒級休眠(1 微秒 = 0.001 毫秒)}printf("子線程退出\n");retvalue = 8;pthread_exit((void*)&retvalue); // 返回 retvalue 的地址給主線程
}
? 關鍵點說明:
  • void *threadfunc(void* arg) 是 pthread 要求的線程函數格式。
  • arg 是傳入的參數,實際是主線程傳入 &run
  • 子線程通過 *running 控制循環是否繼續。
  • 使用 pthread_exit() 顯式結束線程,并返回結果指針。

🧵 4. 主線程 main

pthread_t tid; // 聲明線程 id
int ret = -1;  // 初始化返回值
int times = 3; // 打印次數
int i = 0;
int *ret_join = NULL;
ret = pthread_create(&tid, NULL, (void*)threadfunc, &run);
  • 創建一個線程,線程函數為 threadfunc,傳入參數為 &run
  • ret 為返回值,0 表示成功。
if(ret != 0)
{printf("建立線程失敗\n");return 1;
}
printf("主線程創建子線程后在運行...\n");
usleep(1);	
printf("主線程調用usleep(1)...\n");
  • 主線程短暫停頓,為了讓子線程有機會先運行。
for(; i < times; i++)
{printf("主線程打印i=%d\n", i);usleep(1);
}
  • 主線程執行 3 次循環,每次 sleep 1 微秒,并打印當前 i
run = 0;
  • 將共享變量 run 設置為 0,通知子線程退出。
pthread_join(tid, (void*)&ret_join);
  • 等待子線程結束,并獲取返回值。
  • 注意這里 ret_joinint * 類型,用于接收 retvalue 的地址。
printf("線程返回值為:%d\n", *ret_join);
  • 打印子線程返回的值 8

🧪 5. 運行輸出示例(大致)

主線程創建子線程后在運行...
主線程調用usleep(1)...
子線程初始化完畢,傳入參數為:1
子線程正在運行
主線程打印i=0
子線程正在運行
主線程打印i=1
子線程正在運行
主線程打印i=2
子線程正在運行
子線程退出
線程返回值為:8

(線程調度不確定,輸出順序可能變化)


📌 6. 知識點總結

項目內容說明
pthread_create創建新線程
pthread_join等待線程結束,獲取返回值
pthread_exit子線程退出并返回值
usleep(x)微秒級休眠,適合線程示例中短暫等待
共享變量通信主線程修改 run,控制子線程退出

?? 7. 建議與優化

  1. usleep(1) 睡眠時間太短(1 微秒),可以改為 usleep(100000)(即 0.1 秒)方便觀察輸出。
  2. 變量 retvalue 設置為 static 是為了確保其生命周期足夠長,返回地址有效。
  3. 多線程程序應考慮線程安全問題,如資源競爭、內存可見性等,在多核機器中尤為重要。

三、多線程對共享變量的非互斥訪問

我們將要做的: 構造「多線程共享變量競爭」的現象,并分析現象發生的原因,進而思考解決方式。

源代碼:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>int num=30,count=10;void *sub1(void *arg) {int i = 0,tmp;for (; i <count; i++){tmp=num-1;usleep(13);num=tmp;printf("線程1 num減1后值為: %d\n",num);}return ((void *)0);
}
void *sub2(void *arg){int i=0,tmp;for(;i<count;i++){tmp=num-1;usleep(31);num=tmp;printf("線程2 num減1后值為: %d\n",num);}return ((void *)0);
}
int main(int argc, char** argv) {pthread_t tid1,tid2; // 兩個子線程的idint err,i=0,tmp;void *tret; // 線程返回值err=pthread_create(&tid1,NULL,sub1,NULL);if(err!=0){printf("pthread_create error:%s\n",strerror(err));exit(-1);}err=pthread_create(&tid2,NULL,sub2,NULL);if(err!=0){printf("pthread_create error:%s\n",strerror(err));exit(-1);}for(;i<count;i++){tmp=num-1;usleep(5);num=tmp;printf("main num減1后值為: %d\n",num);}printf("兩個線程運行結束\n");err=pthread_join(tid1,&tret);if(err!=0){printf("can not join with thread1:%s\n",strerror(err));exit(-1);}printf("thread 1 exit code %d\n",(int)tret);err=pthread_join(tid2,&tret);if(err!=0){printf("can not join with thread1:%s\n",strerror(err));exit(-1);}printf("thread 2 exit code %d\n",(int)tret);return 0;
}

🧠 1. 程序功能概述

創建了兩個線程 sub1sub2,以及主線程三者共同對一個全局變量 num 執行減 1 操作,共減去 count * 3 = 30 次。

初始值:

int num = 30, count = 10;

所以理論上最終 num == 0,但實際上并不一定!


?? 2. 存在的核心問題:數據競爭(Race Condition)

? 對 num-- 是分三步執行的:
tmp = num - 1;
usleep(x);
num = tmp;

這個過程不是原子操作,多個線程可能“交叉”訪問這個變量,造成競態條件(Race Condition)

中間插入 usleep() 只是為了放大并發寫入帶來的沖突概率,模擬真實環境下的并發問題。

舉例說明:

假設此時 num = 10,兩個線程同時讀到:

線程1:tmp1 = 10 - 1 = 9,睡眠
線程2:tmp2 = 10 - 1 = 9,睡眠

然后:

線程1醒來執行 num = 9
線程2醒來執行 num = 9 (覆蓋了線程1的操作)

🔴 這樣 num 實際只減少了一次,而我們期望它減少兩次(一個線程分別減少一次)!


🔍 3. 運行效果舉例(輸出可能類似):

線程1 num減1后值為: 29
線程2 num減1后值為: 28
main num減1后值為: 27
線程1 num減1后值為: 27  ←? 重復了
main num減1后值為: 26
線程2 num減1后值為: 26  ←? 再次重復

最終 num 的值可能 不是 0,甚至是更高。原因就是上面說的:很多次減法操作失效了。


?4. 如何解決?使用線程同步機制:互斥鎖 pthread_mutex_t

例如,添加全局互斥鎖

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

將每個對 num 的訪問部分用鎖保護:

pthread_mutex_lock(&lock);
tmp = num - 1;
usleep(13); // 保留你原來的模擬處理
num = tmp;
pthread_mutex_unlock(&lock);

🔒 這樣確保每次只有一個線程在訪問和修改 num


🛠? 5. 修改后關鍵片段示例(以 sub1 為例)

void *sub1(void *arg) {int i = 0, tmp;for (; i < count; i++) {pthread_mutex_lock(&lock);tmp = num - 1;usleep(13);num = tmp;printf("線程1 num減1后值為: %d\n", num);pthread_mutex_unlock(&lock);}return ((void *)0);
}

主線程、sub2 中也要加鎖。


🔚 6. 總結

問題說明
數據競爭多線程訪問全局變量未加鎖
后果num 最終值不確定,減法丟失
解決使用 pthread_mutex 互斥鎖
調試建議加 -fsanitize=thread 或使用 valgrind --tool=helgrind 檢查

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/76232.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/76232.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/76232.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

系統性能核心指標:QPS、TPS、RT、并發量詳解

系統性能核心指標&#xff1a;QPS、TPS、RT、并發量詳解 1. 引言 在分布式系統、高并發架構設計中&#xff0c;QPS、TPS、RT、并發量 等指標是衡量系統性能的關鍵。本文深入解析這些術語的定義、計算方法、關聯性及優化策略&#xff0c;幫助開發者更好地進行系統性能評估與調…

PortswiggerLab:Exploiting a mass assignment vulnerability

實驗目標 To solve the lab, find and exploit a mass assignment vulnerability to buy a Lightweight l33t Leather Jacket. You can log in to your own account using the following credentials: wiener:peter. 官方WP In Burps browser, log in to the application using…

卡爾曼濾波器的工作原理

原文: https://www.bzarg.com/p/how-a-kalman-filter-works-in-pictures/ 1 概述 你可以對某個動態系統有不確定信息的任何地方使用卡爾曼濾波器&#xff0c;并且對系統下一步的狀態做出有根據的猜測。即使出現混亂的現實狀態&#xff0c;卡爾曼濾波器都會給出一個合理的結果。…

PDFtk

如果下載的pdf文件有秘鑰的話&#xff0c;使用下面linux命令去掉秘鑰&#xff1a; pdftk 納稅記錄.pdf input_pw 261021 output 納稅記錄_output.pdf將多個單頁pdf合并為一個pdf的linux命令: pdftk 自然人電子稅務局1.pdf 自然人電子稅務局2.pdf 自然人電子稅務局3.pdf 自然人…

Openlayers:海量圖形渲染之WebGL渲染

最近由于在工作中涉及到了海量圖形渲染的問題&#xff0c;因此我開始研究相關的解決方案。我在網絡上尋找相關的解決方案時發現許多的文章都提到利用Openlayers中的WebGLPointsLayer類&#xff0c;可以實現渲染海量的點&#xff0c;之后我又了解到利用WebGLVectorLayer類可以渲…

替換jeecg圖標

替換jeecg圖標 ant-design-vue-jeecg/src/components/tools/Logo.vue <!-- <img v-else src"~/assets/logo.svg" alt"logo">-->

Codeforces Round 970 (Div. 3)題解

題目地址 https://codeforces.com/contest/2008 銳評 本次D3的前四題還是比較簡單的&#xff0c;沒啥難度區分&#xff0c;基本上差不多&#xff0c;屬于手速題。E的碼量比F大一些&#xff0c;實現略顯復雜一些。G的數學思維較明顯&#xff0c;如果很久沒有訓練這個知識點&a…

操作系統:線程間同步之事件集

事件集是線程間同步的機制之一&#xff0c;一個事件集可以包含多個事件&#xff0c;利用事件集可以完成一對多、多對多的線程間同步。 目錄 一、事件集舉例說明 二、事件集工作機制 三、RT-Thread為實例說明 四、事件集的應用場合 一、事件集舉例說明 以坐公交車為例&…

基于springboot鉆孔數據管理系統的設計與實現(源碼+lw+部署文檔+講解),源碼可白嫖!

摘要 本鉆孔數據管理系統采用B/S架構&#xff0c;數據庫是MySQL&#xff0c;網站的搭建與開發采用了先進的Java語言、Hadoop、數據可視化技術進行編寫&#xff0c;使用了Spring Boot框架。該系統從兩個對象&#xff1a;由管理員和用戶來對系統進行設計構建。用戶主要功能包括&…

全雙工分軌語音數據集:讓AI實現無縫對話

清晨&#xff0c;智能音箱根據指令-播放音樂&#xff1b;駕駛途中&#xff0c;車載助手同步處理導航與來電&#xff1b;智能會議工具無縫切換多語種對話……語音交互技術正快速融入生活。然而&#xff0c;用戶對于對話體驗追求更自然、更流暢&#xff0c;實時理解&#xff0c;動…

Python 網絡請求利器:requests 包詳解與實戰

諸神緘默不語-個人技術博文與視頻目錄 文章目錄 一、前言二、安裝方式三、基本使用1. 發起 GET 請求2. 發起 POST 請求 四、requests請求調用常用參數1. URL2. 數據data3. 請求頭 headers4. 參數 params5. 超時時間 timeout6. 文件上傳 file&#xff1a;上傳純文本文件流7. jso…

linux入門四:Linux 編譯器

一、C 語言編譯器 GCC&#xff1a;開啟編程之旅 1.1 GCC 安裝&#xff1a;一站式工具鏈 GCC&#xff08;GNU Compiler Collection&#xff09;是 Linux 下最常用的 C/C 編譯器&#xff0c;支持多種編程語言。安裝命令&#xff08;適用于 Debian/Ubuntu 系統&#xff09;&…

建筑兔零基礎自學記錄69|爬蟲Requests-2

Requests庫初步嘗試 #導入requests庫 import requests #requests.get讀取百度網頁 rrequests.get(http://www.baidu.com) #輸出讀取網頁狀態 print(r.status_code) #輸出網頁源代碼 print(r.text) HTTP 狀態碼是三位數字&#xff0c;用于表示 HTTP 請求的結果。常見的狀態碼有…

Web測試流程及注意點

在Web工程過程中&#xff0c;基于Web系統的測試、確認和驗收是一項重要而富有挑戰性的工作。基于Web的系統測試與傳統的軟件測試不同&#xff0c;它不但需要檢查和驗證是否按照設計的要求運行&#xff0c;而且還要測試系統在不同用戶的瀏覽器端的顯示是否合適。 重要的是&…

基于MATLAB/simulink的信號調制仿真--AM調制

實驗內容&#xff1a; 假設y(t)(20.5*2cos&#xff08;2*pi*1000*t&#xff09;)*5cos&#xff08;2*pi*2*1e4*t&#xff09;調幅系統&#xff0c;請將一個頻率為1000HZ的余弦波信號&#xff0c;通過進行AM調制&#xff0c;載波信號頻率為20kHZ的余弦波&#xff0c;調制度ma0.…

通信協議詳解(十):PSI5 —— 汽車安全傳感器的“抗干擾狙擊手”

一、PSI5是什么&#xff1f; 一句話秒懂 PSI5就像傳感器界的“防彈信使”&#xff1a;在汽車安全系統&#xff08;如氣囊&#xff09;中&#xff0c;用兩根線同時完成供電數據傳輸&#xff0c;即便車禍時線路受損&#xff0c;仍能確保關鍵信號準確送達&#xff01; 基礎概念…

數據結構與算法-圖論-復習1(單源最短路,全源最短路,最小生成樹)

1. 單源最短路 單一邊權 BFS 原理&#xff1a;由于邊權為單一值&#xff0c;可使用廣度優先搜索&#xff08;BFS&#xff09;來求解最短路。BFS 會逐層擴展節點&#xff0c;由于邊權相同&#xff0c;第一次到達某個節點時的路徑長度就是最短路徑長度。 用法&#xff1a;適用…

【WRF理論第十七期】單向/雙向嵌套機制(含namelist.input詳細介紹)

WRF運行的單向/雙向嵌套機制 準備工作&#xff1a;WRF運行的基本流程namelist.input的詳細設置&time_control 設置&domain 嵌套結構&bdy_control 配置部分 namelist 其他注意事項Registry.EM 運行 ARW 嵌套雙向嵌套&#xff08;two-way nesting&#xff09;單向嵌套…

怎么查看蘋果手機和ipad的設備信息和ios udid

你知道嗎&#xff1f;我們每天使用的iPhone和iPad&#xff0c;其實隱藏著大量詳細的硬件與系統信息。除了常見的系統版本和序列號外&#xff0c;甚至連電池序列號、攝像頭序列號、銷售地區、芯片型號等信息&#xff0c;也都可以輕松查到&#xff01; 如果你是開發者、維修工程…

matlab內置的git軟件版本管理功能

1、matlab多人協作開發比普通的嵌入式軟件開發困難很多 用過matlab的人都知道&#xff0c;版本管理對于matlab來說真的很費勁&#xff0c;今天介紹的這個工具也不是說它就解決了這個痛點&#xff0c;只是讓它變得簡單一點。版本管理肯定是不可或缺的&#xff0c;干就完了 2、…