全局變量的異步I/O問題

全局變量的異步I/O問題同樣屬于時序競態問題,其本質就是多個進程或者同一個進程中的多個時序(如主控程序和信號捕捉時的用戶處理函數)對同一個變量進行修改時,它們的執行順序不一樣就會導致該變量最終的值不一樣,從而產生不一樣的結果。

多個進程或者同一個進程中的多個時序對同一個變量進行操作時,應該盡量避免使用這種變量。在編程時也應當盡量避免使用全局變量。如果非用不可,則必須考慮該全局變量的使用順序問題,可以采用加鎖的方法對全局變量進行訪問。如果加鎖的方式無法解決,則直接就不訪問該變量,直到等待其它進程或時序訪問完之后才進行訪問,總之確保變量正確的訪問順序。

//分析如下父子進程交替數數程序,重點分析程序中3sleep函數的作用,如果取消掉用戶處理函數中的兩個sleep函數會發生什么問題

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>int n = 0, flag = 0;  //定義兩個全局變量(注意了)void sys_err(char *str)
{perror(str);exit(1);
}void do_sig_child(int num)  //子進程的用戶處理函數
{printf("I am child  %d\t%d\n", getpid(), n);n += 2;flag = 1;  //對全局變量的修改sleep(1);
}void do_sig_parent(int num)   //父進程的用戶處理函數
{printf("I am parent %d\t%d\n", getpid(), n);n += 2;flag = 1;  //對全局變量的修改sleep(1);
}int main(void)
{pid_t pid;struct sigaction act;if ((pid = fork()) < 0)sys_err("fork");else if (pid > 0) {n = 1;          //父進程從1開始數sleep(1);       //父進程睡眠1s確保在父進程向子進程發信號之前,子進程完成了對信號的注冊act.sa_handler = do_sig_parent;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGUSR2, &act, NULL);             //注冊自己的信號捕捉函數,父進程使用SIGUSR2信號do_sig_parent(0);   //父進程先進行數數,從1開始while(1) {/* wait for signal */;if (flag == 1) {                         //父進程數數完成kill(pid, SIGUSR1);flag = 0;                        //標志已經給子進程發送完信號}}} else if (pid == 0){n = 2;      //子進程從2開始數act.sa_handler = do_sig_child;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGUSR1, &act, NULL);while(1) {/* wait for signal */;if (flag == 1) {kill(getppid(), SIGUSR2);flag = 0;}}}return 0;
}

SIGUSR1SIGUSR2信號。用戶自定義信號,程序員可以在程序中定義并使用該信號。默認動作為終止進程。

上述函數的正常執行結果本應該是:父進程數1、3、5、7、······;子進程數2、4、6、8、·······,且它們之間交替數數。

父進程中的第一個sleep函數確保在父進程向子進程發送信號前,子進程已經完成了對信號的注冊(因為子進程有可能失去CPU時間太長而未完成對信號的注冊),否則會導致子進程收到信號被終結。

在父進程中的全局變量flag在用戶處理函數中和主控程序(while循環中)都會被修改(子進程也一樣),但是正確的執行順序必須是:父進程完成數數→用戶處理函數置flag為1→父進程發信號→主控程序置flag為0。flag為1確保向進程發送信號,flag為0確保信號只是發送一次,不重復發送。但是,如果其中某一個進程(父進程或子進程)在while循環中剛發送完信號就失去了CPU,還未對flag進行修改,此時另一個進程處理完信號后,再次向該進程發送信號,此時該進程接收到信號不會接著執行flag=0的操作了,會馬上去處理信號,信號處理完后,才會回到主控程序執行flag=0的操作,此時顯然順序發生了顛倒,導致最終flag錯誤置為0。因此,該進程處理完信號后再也不會發送信號了,另一個進程也再也不會收到信號,從而更不會再發信號。兩個進程都在while循環中重復判斷條件,但是條件永遠不滿足。因此,用戶捕捉函數中的兩個sleep函數的作用就是確保,一個進程在向兩一個進程發送信號前,另一個進程主控程序中的flag=0的操作已經執行了,確保變量值修改的正確性。

如何解決該問題呢?可以使用后續章節講到的“鎖”機制。當操作全局變量的時候,通過加鎖、解鎖來解決該問題(互斥訪問,進程同步)。

現階段,我們在編程期間如若使用全局變量,應在主觀上注意全局變量的異步IO可能造成的問題。

上述問題雖然可以通過sleep函數來解決,但是sleep函數會導致數數效率太低,可以取消全局變量flag,讓發送信號的操作在用戶處理函數中完成即可。

//程序的修改和優化

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>int n = 0;
pid_t pid;void sys_err(char *str)
{perror(str);exit(1);
}void do_sig_child(int num)
{printf("I am child  %d\t%d\n", getpid(), n);n += 2;kill(getppid( ) , SIGUSR2);
}void do_sig_parent(int num)
{printf("I am parent %d\t%d\n", getpid(), n);n += 2;kill(pid , SIGUSR1);
}int main(void)
{struct sigaction act;if ((pid = fork()) < 0)sys_err("fork");else if (pid > 0) {n = 1;sleep(1);act.sa_handler = do_sig_parent;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGUSR2, &act, NULL);            do_sig_parent(0);while(1) {;}} else if (pid == 0){n = 2;act.sa_handler = do_sig_child;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGUSR1, &act, NULL);while(1) {;}}return 0;
}

?

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

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

相關文章

【Leetcode | 03】String

字符串目錄序號題號33. 無重復字符的最長子串 151. 翻轉字符串里的單詞

可/不可重入函數

一個函數在被調用執行期間&#xff08;尚未調用結束&#xff09;&#xff0c;由于某種時序&#xff08;遞歸或者處理信號捕捉時等情況&#xff09;又被重復調用&#xff0c;稱之為“重入”。根據函數實現的方法可分為“可重入函數”和“不可重入函數”兩種。看如下程序。 可以看…

【Leetcode | 順序刷題】雜項目錄

序號題號類別1136. 只出現一次的數字位運算2137. 只出現一次的數字 II位運算3 260. 只出現一次的數字 III 位運算4191. 位1的個數位運算5231. 2的冪位運算6342. 4的冪位運算7 338. 比特位計數 位運算8405. 數字轉換為十六進制數位運算9371. 兩整數之和位運算10401. 二進制手表位…

SIGCHLD信號

&#xff08;1&#xff09;SIGCHLD信號產生的條件 1.子進程終止時會向父進程發送SIGCHLD信號&#xff0c;告知父進程回收自己&#xff0c;但該信號的默認處理動作為忽略&#xff0c;因此父進程仍然不會去回收子進程&#xff0c;需要捕捉處理實現子進程的回收&#xff1b; 2.子…

信號傳參

&#xff08;1&#xff09;發送信號傳參 前面已經知道從一個進程向另一個進程發送信號可以使用kill函數&#xff0c;但是kill函數在向進程發送信號的時候不能攜帶除了信號以外的其他信息&#xff0c;這時可以使用與kill相對應的sigqueue函數&#xff0c;該函數也是向一個進程發…

【Leetcode | 52】257. 二叉樹的所有路徑

給定一個二叉樹&#xff0c;返回所有從根節點到葉子節點的路徑。 說明: 葉子節點是指沒有子節點的節點。 示例: 輸入: 1 / \ 2 3 \ 5 輸出: ["1->2->5", "1->3"] 解釋: 所有根節點到葉子節點的路徑為: 1->2->5, 1->3 解法一&a…

623. 在二叉樹中增加一行

給定一個二叉樹&#xff0c;根節點為第1層&#xff0c;深度為 1。在其第 d 層追加一行值為 v 的節點。 添加規則&#xff1a;給定一個深度值 d &#xff08;正整數&#xff09;&#xff0c;針對深度為 d-1 層的每一非空節點 N&#xff0c;為 N 創建兩個值為 v 的左子樹和右子樹…

終端的概念

操作系統接口&#xff1a;用戶接口和程序接口。用戶接口分為聯機用戶接口和脫機用戶接口。脫機用戶接口出現在早期的批處理系統中&#xff08;將作業提前交給操作系統&#xff0c;作業完成的過程中用戶無法交互&#xff09;&#xff1b;聯機用戶接口即為終端&#xff08;所有輸…

終端的啟動流程

在Linux操作系統啟動時&#xff0c;首先加載的進程就是init進程&#xff08;ID為1&#xff09;&#xff0c;其余進程都是init進程產生的&#xff08;fork&#xff0c;然后exec金蟬脫殼&#xff09;&#xff0c;因此系統中所有進程都可以看成是init進程的子孫進程。可以通過ps a…

進程組(作業)

&#xff08;1&#xff09;概念和特性 進程組&#xff0c;也稱之為作業。BSD于1980年前后向Unix中增加的一個新特性。代表一個或多個進程的集合。每個進程都屬于一個進程組。在waitpid函數和kill函數的參數中都曾使用到。操作系統設計的進程組的概念&#xff0c;是為了簡化對多…

437. 路徑總和 III

給定一個二叉樹&#xff0c;它的每個結點都存放著一個整數值。 找出路徑和等于給定數值的路徑總數。 路徑不需要從根節點開始&#xff0c;也不需要在葉子節點結束&#xff0c;但是路徑方向必須是向下的&#xff08;只能從父節點到子節點&#xff09;。 二叉樹不超過1000個節…

會話(session)

一組進程形成一個進程組&#xff0c;一組進程組形成一個會話&#xff0c;即一個會話中可以包括多個進程組。 &#xff08;1&#xff09;創建會話 創建一個會話需要注意以下6點注意事項&#xff1a;1.調用進程不能是進程組組長&#xff08;不能是父進程&#xff09;&#xff0…

508. 出現次數最多的子樹元素和

給出二叉樹的根&#xff0c;找出出現次數最多的子樹元素和。一個結點的子樹元素和定義為以該結點為根的二叉樹上所有結點的元素之和&#xff08;包括結點本身&#xff09;。然后求出出現次數最多的子樹元素和。如果有多個元素出現的次數相同&#xff0c;返回所有出現次數最多的…

1003 我要通過!(20)(20 分)

“答案正確”是自動判題系統給出的最令人歡喜的回復。本題屬于PAT的“答案正確”大派送 —— 只要讀入的字符串滿足下列條件&#xff0c;系統就輸出“答案正確”&#xff0c;否則輸出“答案錯誤”。 得到“答案正確”的條件是&#xff1a; 1. 字符串中必須僅有P, A, T這三種字符…

網絡終端

虛擬終端或串口終端的數目是有限的&#xff0c;虛擬終端&#xff08;字符控制終端&#xff09;一般就是/dev/tty1~/dev/tty6六個&#xff0c;串口終端的數目也不超過串口的數目。然而網絡終端或圖形終端窗口的數目卻是不受限制的&#xff0c;這是通過偽終端&#xff08;Pseudo…

線程的概念

線程&#xff08;LWP&#xff0c;light weight process&#xff09;是輕量級的進程&#xff0c;本質仍是進程&#xff08;在類unix環境下&#xff09;。進程有獨立地址空間&#xff0c;擁有PCB&#xff1b;線程也有PCB&#xff0c;但沒有獨立的地址空間&#xff08;共享&#x…

1001. 害死人不償命的(3n+1)猜想 (15)

卡拉茲(Callatz)猜想&#xff1a; 對任何一個自然數n&#xff0c;如果它是偶數&#xff0c;那么把它砍掉一半&#xff1b;如果它是奇數&#xff0c;那么把(3n1)砍掉一半。這樣一直反復砍下去&#xff0c;最后一定在某一步得到n1。卡拉茲在1950年的世界數學家大會上公布了這個猜…

海量數據處理 (一)

現有海量日志數據保存在一個超級大的文件中&#xff0c;該文件無法直接讀入內存&#xff0c;要求從中提取某天出訪問百度次數最多的那個IP。 從這一天的日志數據中把訪問百度的IP取出來&#xff0c;逐個寫入到一個大文件中;注意到IP是32位的&#xff0c;最多有2^32個IP。同樣可…

線程控制原語之pthread_self和pthread_create函數

注意&#xff1a;使用線程庫函數用gcc編譯時&#xff0c;要加參數&#xff1a;-lpthread&#xff08;libpthread.so&#xff09;&#xff0c;因為線程庫函數屬于第三方c庫函數&#xff0c;不是標準庫函數&#xff08;/lib、/usr/lib或者/usr/local/lib&#xff09;。 &#xf…

1005. 繼續(3n+1)猜想 (25)

卡拉茲(Callatz)猜想已經在1001中給出了描述。在這個題目里&#xff0c;情況稍微有些復雜。 當我們驗證卡拉茲猜想的時候&#xff0c;為了避免重復計算&#xff0c;可以記錄下遞推過程中遇到的每一個數。例如對n3進行驗證的時候&#xff0c;我們需要計算3、5、8、4、2、1&#…