自己的理解,不一定對,僅供參考,勿copy哦!雷同就一起掛了
實驗1
【實驗名稱】:並發程式設計(實驗1)
【實驗目的】:掌握在程式中建立新進程的方法, 觀察並理解多道程式並發執行的現象。
【實驗原理】:fork():建立子進程。子進程得到父進程地址空間的一個複製。
傳回值:成功時,該函數被調用一次,但返回兩次,fork()對子進程返回0,對父進程返回子進程標識符(非0值)。不成功時對父進程返回-1,沒有子進程。
【實驗內容】:首先分析一下程式運行時其輸出結果有哪幾種可能性,然後實際調試該程式觀察其實際輸出情況,比較兩者的差異,分析其中的原因。
void main (void)
{
int x=5;
if( fork( ) )
{
x+=30;
printf (“%d\n”,x);
}
else
printf(“%d\n”,x);
printf((“%d\n”,x);
}
【實驗要求】:每個同學必須獨立完成本實驗、提交實驗報告、來源程式和可執行程式。實驗報告中必須包含預計的實驗結果,關鍵代碼的分析,調試記錄,實際的實驗結果,實驗結果分析等內容。
一.關鍵程式碼分析
一個現有進程可以調用fork函數建立一個新進程。由fork建立的新進程被稱為子進程(child process)。fork函數被調用一次但返回兩次。兩次返回的唯一區別是子進程中返回0值而父進程中返回子進程ID。
子進程是父進程的副本,它將獲得父進程資料空間、堆、棧等資源的副本。注意,子進程持有的是上述儲存空間的“副本”,這意味著父子進程間不共用這些儲存空間。
也就是說,當程式運行到if條件判斷運行fork()函數後,父進程啟動並執行是條件成立成立的代碼,即
x+=30;
printf (“%d\n”,x);
子進程啟動並執行是條件不成立的代碼,即
printf(“%d\n”,x);
根據並發執行的工作方式及特徵,我們預計可能的實驗結果如下:
二.預計實驗結果
猜測有6種情況(其實就是這4個數的全排列)
35 |
35 |
35 |
5 |
5 |
5 |
35 |
5 |
5 |
5 |
35 |
35 |
5 |
35 |
5 |
35 |
5 |
35 |
5 |
5 |
35 |
35 |
35 |
5 |
三.調試記錄
1. 先建立一個.c為尾碼的文檔,如test.c
2. 編輯來源程式:
#include<stdio.h>
#include<unistd.h>
void main (void)
{
int x=5;
if( fork( ) )
{
x+=30;
printf (“%d\n”,x);
}
else
printf(“%d\n”,x);
printf((“%d\n”,x);
}
3. 在命令列下編輯 gcc –o test.out test.c
然後編輯 ./test.out
運行程式
4. 實際實驗結果:
35
35
(一串字元命令,省略)5
5
(PS:那段字元命令,父進程運行完就會出現,所以都是在輸出兩個35後出現)
重複步驟3,N次後發現還是這個結果。
四.實驗結果分析
老師說其它結果的出現是小機率事件。我們不敢苟同,這都基本上可以當不可能事件處理了。
於是我們猜測,這個跟傳說中的時間片有關。
因為這個程式運行所需的時間實在是太短,預設先啟動並執行父進程完全可以在系統給定的時間片內運行完,如果沒有出現什麼其他情況而掛起,子進程就只能等到父進程運行完後才能運行。
所以呢,當然就只有一種情況了。
五.實驗結果處理
不過,我們可以利用時間片的特性製造出6種結果。
主角登場了,sleep(1) 延時一秒函數
可以把延時函數理解為一段要運行1秒的代碼。下面的提到的進程,都是因在這段延時代碼中運行時進程時間片用完掛起(假設在這個延時代碼中運行了0.6秒),再次調度回來時,只要再運行0.4秒就可以了跳出這個延時函數了。
第一種:
因為預設先啟動並執行是父進程(我用的系統是這樣),我們感覺對子進程太不公平了,所以我們製造的第一種情況的源碼是:
#include<stdio.h>
#include<unistd.h>
void main (void)
{ int x=5;
if( fork( ) )
{
x+=30;
sleep(1);
printf (“%d\n”,x);
}
else
printf(“%d\n”,x);
printf((“%d\n”,x);
}
我們猜測時間片是0.6秒,不管對不對,重點是分析。
父進程運行0.6秒後,時間片用完掛起,調度運行子進程,子進程運行完後調度回父進程接著運行。所以我們猜測結果是
5
5
35
35
(一串字元命令,省略)
哇塞,運行完也是!
第二種:
這裡我們突然想到如果在子進程前也加個延時函數會怎樣,
#include<stdio.h>
#include<unistd.h>
void main (void)
{ int x=5;
if( fork( ) )
{
x+=30;
sleep(1);
printf (“%d\n”,x);
}
else {
sleep(1);
printf(“%d\n”,x);
}
printf((“%d\n”,x);
}
不過,這個只能調試完再根據答案去分析,因為我們也不知道真正的時間片是多少,只是猜測。運行結果為
35
35
(一串字元命令,省略)5
5
我們繼續假設時間片為0.6秒,父進程運行0.6秒後掛起,輪到子進程運行0.6秒後也掛起,調度回父進程繼續運行,因為之前已經運行了0.6秒,所以這次調度時間片就夠用了。假設成立
所以,以下都是假設時間片為0.6秒,這個時間是很合理的。
第三種:
#include<stdio.h>
#include<unistd.h>
void main (void)
{
int x=5;
if( fork( ) )
{
x+=30;
sleep(1);
printf (“%d\n”,x);
}
else {
printf(“%d\n”,x);
sleep(1);
}
printf((“%d\n”,x);
}
與前面的分析類似,父進程運行0.6秒後掛起,調度子進程會先運行第一條指令輸出x為5,然後進入延時函數運行0.6秒(前面運行1條指令的時間可以忽略不計)後掛起,調度回父進程運行完,再調度回子進程。所以輸出為
5
35
35
(一串字元應該是提示輸入的命令,省略)5
第四種:
#include<stdio.h>
#include<unistd.h>
void main (void)
{ int x=5;
if( fork( ) )
{
x+=30;
printf (“%d\n”,x);
sleep(1);
}
else {
printf(“%d\n”,x);
sleep(1);
}
printf((“%d\n”,x);
}
分析同上,輸出結果為
35
5
35
(一串字元命令,省略)5
第五種:
#include<stdio.h>
#include<unistd.h>
void main (void)
{ int x=5;
if( fork( ) )
{
x+=30;
printf (“%d\n”,x);
sleep(1);
}
else {
printf(“%d\n”,x);
}
printf((“%d\n”,x);
}
運行結果為,
35
5
5
35
(一串字元命令,省略)
第六種:
仔細看下上面5種情況,會發現還有種比較奇葩的沒有出現。
5
35
5
35
(一串字元命令,省略)
需要從父進程(延時掛起)->子進程(輸出然後延時掛起)->父進程(延時然後輸出然後再延時掛起)->子進程(輸出結束進程)->父進程(延時然後輸出結束進程),代碼為
#include<stdio.h>
#include<unistd.h>
void main (void)
{ int x=5;
if( fork( ) )
{
x+=30;
sleep(1);
printf (“%d\n”,x);
sleep(1);
}
else {
printf(“%d\n”,x);
sleep(1);
}
printf((“%d\n”,x);
}