html怎么添加背景圖片四川整站優(yōu)化關(guān)鍵詞排名
文章目錄
- 實(shí)驗(yàn)內(nèi)容
- 一、進(jìn)程的創(chuàng)建
- 1、編輯源程序
- 2、編輯結(jié)果
- 3、編譯和運(yùn)行程序
- 4、解釋運(yùn)行結(jié)果
- 二、進(jìn)程共享
- 1、運(yùn)行
- 2、解釋運(yùn)行結(jié)果
- 三、進(jìn)程終止
- 1、運(yùn)行
- 2、解釋運(yùn)行結(jié)果
- 四、進(jìn)程同步
- 1、運(yùn)行
- 2、解釋運(yùn)行結(jié)果
- 五、Linux中子進(jìn)程映像的重新裝入
- 1、運(yùn)行
- 2、解釋運(yùn)行結(jié)果
- 六、線程
- 1、運(yùn)行
- 2、解釋運(yùn)行結(jié)果
- 七、共享資源的互斥訪問
- 1、運(yùn)行
- 2、解釋運(yùn)行結(jié)果
實(shí)驗(yàn)內(nèi)容
一、進(jìn)程的創(chuàng)建
編寫一段源程序,使用系統(tǒng)調(diào)用fork()創(chuàng)建子進(jìn)程,當(dāng)此程序運(yùn)行時(shí),在系統(tǒng)中有父進(jìn)程和子進(jìn)程在并發(fā)執(zhí)行。觀察屏幕上的顯示結(jié)果,并分析原因(源代碼:forkpid.c)。
1、編輯源程序
2、編輯結(jié)果
3、編譯和運(yùn)行程序
4、解釋運(yùn)行結(jié)果
假設(shè)命令./forkpid
創(chuàng)建的進(jìn)程稱為A,則A中的fork()
調(diào)用會創(chuàng)建一個(gè)子進(jìn)程,稱為B。
fork()
函數(shù)只在父進(jìn)程A中成功被調(diào)用一次,但是在A和B兩個(gè)進(jìn)程中都會有返回值。成功創(chuàng)建子進(jìn)程后,在父進(jìn)程A中,fork
的返回值為創(chuàng)建的子進(jìn)程的pid;在子進(jìn)程中,fork
的返回值為0。
因此,if(p1==0)
后面的代碼塊會在子進(jìn)程中被執(zhí)行,而else
后面的代碼塊會在父進(jìn)程中被執(zhí)行。由上述運(yùn)行結(jié)果可知,進(jìn)程的創(chuàng)建關(guān)系如下。
pid: 389733 --> 390075(A) --> 390076(B)
那么還有一個(gè)問題,上述運(yùn)行結(jié)果中的My parent is 1
,豈不是說B進(jìn)程的父進(jìn)程pid是1?這不是矛盾了嗎?根據(jù)我所查閱的資料,這是因?yàn)楦高M(jìn)程A比子進(jìn)程B先結(jié)束,B中查找自己的父進(jìn)程時(shí),父進(jìn)程A已經(jīng)不在了,會使用pid為1的進(jìn)程代替。在Linux中,pid號為1的進(jìn)程是所有進(jìn)程的祖先進(jìn)程。
參考:
- https://blog.csdn.net/lein_wang/article/details/81946108 - 為什么父進(jìn)程id是1
- https://www.cnblogs.com/alantu2018/p/8526970.html - linux的 0號進(jìn)程 和 1 號進(jìn)程
二、進(jìn)程共享
父進(jìn)程創(chuàng)建子進(jìn)程后,父子進(jìn)程各自分支中的程序各自私有,其余部分,包括創(chuàng)建前和分支結(jié)束后的程序段,均為父子進(jìn)程共享。(源代碼:forkshare_1.c)
1、運(yùn)行
2、解釋運(yùn)行結(jié)果
根據(jù)運(yùn)行結(jié)果我們很容易看出,進(jìn)程調(diào)度順序和上一節(jié)中一樣,是先調(diào)度父進(jìn)程再調(diào)度字進(jìn)程。其中前三個(gè)字母xay
是父進(jìn)程的輸出,后面三個(gè)字母xby
則是子進(jìn)程的輸出。
可令人困惑不解的是,子進(jìn)程不也應(yīng)該從fork()
返回的地方開始運(yùn)行嗎?那為什么'x'
會被輸出兩次?我一時(shí)間有些蒙圈。
在查找資料后,我嘗試了另一個(gè)示例,其中僅僅是將putchar('x')
換成了printf("x\n")
,而最主要的區(qū)別就是多了一個(gè)\n
。下面是代碼及其運(yùn)行結(jié)果。
子進(jìn)程會從fork()
返回的地方開始執(zhí)行,沒有錯(cuò)。問題出在stdout
緩沖區(qū)的機(jī)制上,在輸出xayxby
的示例中,運(yùn)行putchar('x')
語句并沒有直接將x
寫到屏幕上,而僅僅是將x
放到了緩沖里。隨后運(yùn)行fork()
創(chuàng)建子進(jìn)程,會將父進(jìn)程的stdout
緩沖區(qū)也復(fù)制一份,而復(fù)制的緩沖區(qū)中就包含了剛剛放入的x
。這便是兩個(gè)x
從何而來。
而如果打印的內(nèi)容中包含了\n
,就會馬上將內(nèi)容寫到屏幕上并刷新緩沖區(qū),這便是為什么第二個(gè)示例中x
只有一個(gè)。
參考:
- https://blog.csdn.net/koches/article/details/7787468 - linux中fork–子進(jìn)程是從哪里開始運(yùn)行
三、進(jìn)程終止
如果子進(jìn)程在其分支結(jié)束處使用了進(jìn)程終止exit()系統(tǒng)調(diào)用而終止執(zhí)行,則不會再執(zhí)行分支結(jié)束后的程序段。(源代碼:forkshare_2.c)
1、運(yùn)行
2、解釋運(yùn)行結(jié)果
子進(jìn)程因?yàn)樵诜种е休敵?code>b后就exit()
結(jié)束進(jìn)程了,因此子進(jìn)程不會輸出后面的y
,其它與上一節(jié)情況相同,不作多解釋。
四、進(jìn)程同步
當(dāng)父進(jìn)程有許多任務(wù)要做時(shí),往往會針對每一個(gè)任務(wù)創(chuàng)建一個(gè)子進(jìn)程去完成,然后再等待每一個(gè)子進(jìn)程的終止。其同步關(guān)系是父進(jìn)程等待子進(jìn)程 (源代碼:wait.c)。
實(shí)現(xiàn)的方法是:1)子進(jìn)程終止時(shí)執(zhí)行exit()向父進(jìn)程發(fā)終止信號,2)父進(jìn)程使用wait()等待子進(jìn)程的終止。
1、運(yùn)行
2、解釋運(yùn)行結(jié)果
前面三個(gè)程序都是父進(jìn)程輸出的 va
在前而子進(jìn)程輸出的b
在后,這次ba
換子進(jìn)程的輸出在前了。
父進(jìn)程中掉用wait(0)
函數(shù)會立即阻塞自己,并等待子進(jìn)程的退出。注意阻塞和空循環(huán)等待是有區(qū)別的,進(jìn)程阻塞自己后會交出cpu的控制權(quán)。
不過,wait(0)
和exit(0)
中都有一個(gè)0
,這個(gè)0是什么意思呢?其實(shí)這兩個(gè)零并不是同一個(gè)東西,wait的函數(shù)頭原型是int wait(int *status)
,參數(shù)是一個(gè)int類型的指針,wait(0)
其實(shí)相當(dāng)于wait(NULL)
,這個(gè)參數(shù)為NULL
表示我們不關(guān)心子進(jìn)程是如何退出的,只要退出就行。而如果你寫成wait(1)
,那么編譯時(shí)候就會報(bào)錯(cuò)了,因?yàn)轭愋筒黄ヅ洹?/p>
而exit(0)
的參數(shù)是退出碼,你完全可以使用任性地使用exit(123)
退出子進(jìn)程,并在父進(jìn)程中仍然以wait(0)
接收子進(jìn)程的終止信號。
參考:
- https://zhuanlan.zhihu.com/p/549981187 - 入門篇:進(jìn)程等待函數(shù)wait詳解
- https://blog.csdn.net/qustdjx/article/details/7704323 - wait()以及wait(&status)\ waitpid()
- https://zhuanlan.zhihu.com/p/647776823 - Linux Shell 中的各種退出碼
- https://www.cnblogs.com/shikamaru/p/5359731.html - exit(0)、exit(1)、和return
五、Linux中子進(jìn)程映像的重新裝入
創(chuàng)建一個(gè)子進(jìn)程,并給它加載程序,其功能是顯示“I am a child”。設(shè)被加載的程序路徑名為./child。分析:由于子進(jìn)程需要加載的程序比較簡單,不帶參數(shù),所以可以使用execl()實(shí)現(xiàn)加載。
1、運(yùn)行
./child_parent.c
:
./child.c
:
2、解釋運(yùn)行結(jié)果
我在./child_parent.c
文件的execl()
語句后加了一個(gè)printf()
語句,這樣也行能夠更好地體現(xiàn)execl
的運(yùn)行機(jī)制。
上述源代碼中共有兩個(gè)printf
語句,但最終只有./child.c
中的printf
語句產(chǎn)生了輸出。這是因?yàn)?#xff0c;execl
加載進(jìn)程時(shí)并不會新建一個(gè)進(jìn)程,而是使用傳入路徑中的程序覆蓋當(dāng)前進(jìn)程,并從新進(jìn)程的main
函數(shù)開始執(zhí)行。覆蓋后,使用fork()
創(chuàng)建的那個(gè)程序,當(dāng)然也包括其中的printf("I am child A\n")
,已經(jīng)不存在了。
使用execl
,子進(jìn)程可以更好地干自己的活,而不只是作為父進(jìn)程的拷貝。
參考:
- https://blog.csdn.net/bao_bei/article/details/48287945 - Linux下execl函數(shù)學(xué)習(xí)_linux的execl函數(shù)
六、線程
Linux 系統(tǒng)下的多線程遵循 POSIX 線程接口,稱為 pthread。編寫 Linux 下的多線程程式,需要使用頭文檔 pthread.h,連接時(shí)需要使用庫 libpthread。順便說一下,Linux 下pthread 的實(shí)現(xiàn)是通過系統(tǒng)調(diào)用 clone()來實(shí)現(xiàn)的。clone()是 Linux 所特有的系統(tǒng)調(diào)用,他的使用方式類似 fork,關(guān)于 clone()的周詳情況,有興趣的讀者能夠去查看有關(guān)文檔說明。下面我們展示一個(gè)最簡單的多線程程序 pthread_create.c。
1、運(yùn)行
2、解釋運(yùn)行結(jié)果
// 線程創(chuàng)建函數(shù)的函數(shù)頭
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*strat_routine)(void *), void *arg)
返回值(int):創(chuàng)建成功時(shí)返回0,失敗時(shí)返回錯(cuò)誤碼。
參數(shù):
- thread:前面先創(chuàng)建了空的線程標(biāo)識符
pthread_t id1
,這里傳入&id1
告訴函數(shù)要將新建線程的標(biāo)識符放到哪里??梢詫Ρ?code>scanf("%d", &x)的用法。 - attr:線程的屬性,傳入
NULL
表示默認(rèn)。 - start_routine:線程要執(zhí)行的函數(shù)的指針,上述示例代碼中實(shí)參前的
(void*)
大概是強(qiáng)制類型轉(zhuǎn)換為指針的意思。函數(shù)頭中那一串void *(*strat_routine)(void *)
你可能看著有點(diǎn)暈,感覺自己的C語言功底不太夠用了,抱歉我也是。 - arg:線程要執(zhí)行的函數(shù)所需要的參數(shù),傳入
NULL
意思是不需要參數(shù)。
我在main函數(shù)return前添加了一個(gè)printf
語句。
pthread_join
的作用是讓主線程進(jìn)入阻塞態(tài),等待子線程運(yùn)行結(jié)束后,主線程再接著運(yùn)行。否則,從子線程創(chuàng)建時(shí)開始,主線程與兩個(gè)子線程就開始并發(fā)執(zhí)行,當(dāng)主線程很快運(yùn)行結(jié)束并return后,整個(gè)進(jìn)程也會結(jié)束,即子線程跟著結(jié)束,都來不及在屏幕上打印信息。
可以看到輸出中交替打印兩個(gè)字符串各四次后,最后打印出來的是This is main thread.
。
參考:
- https://blog.csdn.net/sevens_0804/article/details/102823184 - Linux多線程操作pthread_t
- https://www.jb51.net/article/176510.htm - 簡單了解C語言中主線程退出對子線程的影響
七、共享資源的互斥訪問
創(chuàng)建兩個(gè)線程來實(shí)現(xiàn)對一個(gè)數(shù)的遞加 pthread_example.c
1、運(yùn)行
例程1:
代碼太長,截圖不便,我直接貼文本吧。
#include<pthread.h>
#include<stdlib.h>
#include<stdio.h>
#include<sys/time.h>
#include<string.h>
#include<unistd.h>
#define MAX 10pthread_t thread[2];
pthread_mutex_t mut;
int number=0;
int i;void thread1(){printf("thread1: I'm thread 1\n");for(i=0;i<MAX;i++){printf("thread1: number=%d\n",number);pthread_mutex_lock(&mut);number++;pthread_mutex_unlock(&mut);sleep(2);}pthread_exit(NULL);
}
void thread2(){printf("thread2: I'm thread 2\n");for(i=0;i<MAX;i++){printf("thread2: number=%d\n",number);pthread_mutex_lock(&mut);number++;pthread_mutex_unlock(&mut);sleep(3);}pthread_exit(NULL);
}
void thread_create(){int temp;memset(&thread,0,sizeof(thread));if( (temp=pthread_create(&thread[0],NULL,(void*)thread1,NULL)) != 0){printf("create thread1 failed!\n");}else{printf("create thread1 success!\n");}if( (temp=pthread_create(&thread[1],NULL,(void*)thread2,NULL)) != 0){printf("create thread2 failed!\n");}else{printf("create thread2 success!\n");}
}
void thread_wait(){if(thread[0]!=0){pthread_join(thread[0],NULL);printf("thread1 end!\n");}if(thread[1]!=0){pthread_join(thread[1],NULL);printf("thread2 end!\n");}
}
int main(){pthread_mutex_init(&mut,NULL);printf("I am main, I am creating thread!\n");thread_create();printf("I am main, I am waiting thread end!\n");thread_wait();return 0;
}
2、解釋運(yùn)行結(jié)果
進(jìn)程是資源分配的基本單位,線程是調(diào)度的基本單位。上述代碼中,int number
和int i
都是被兩個(gè)線程所共享的變量。利用mutex
可以實(shí)現(xiàn)對共享資源的互斥訪問,這個(gè)比較容易理解,但是上面的運(yùn)行結(jié)果中,可能有些令人疑惑的地方。
1、為什么會打印兩次"number=0",這正常嗎?
是正常的,比如在如下圖所示的執(zhí)行順序(但不唯一)中,就會出現(xiàn)打印兩次"number=0"然后打印"number=2"的情況。注意sleep()
會直接讓當(dāng)前進(jìn)程阻塞,因此下圖中在sleep
處都是拐點(diǎn)。
2、為什么thread1連續(xù)輸出了兩個(gè)數(shù)"number=6"和"number=7"?
對進(jìn)程的同步與互斥有些模糊時(shí),容易產(chǎn)生這樣的疑問。運(yùn)行結(jié)果中,大部分輸出都是thread1和thread2交替的,只有6和7這里是同一個(gè)線程連續(xù)輸出兩次,以至于覺得本應(yīng)該交替輸出、而連續(xù)輸出是異常的。
其實(shí)不是,上述thread1的代碼中是sleep(2)
,而thread2的代碼中是sleep(3)
。如果你將thread1中的sleep(2)
修改成sleep(1)
,可以觀察到大部分時(shí)候都是thread1在連續(xù)輸出??傊?#xff0c;代碼中只是使用mutex實(shí)現(xiàn)了對共享資源的互斥訪問而已,并沒有實(shí)現(xiàn)同步。
3、驗(yàn)證lock與unlock的作用。
到這里,我只知道m(xù)utex可以實(shí)現(xiàn)對共享資源(全局變量number)的互斥訪問,程序也確實(shí)看起來正常運(yùn)行。不過,它只循環(huán)了10次呢。而且不知道你有沒有發(fā)現(xiàn)一個(gè)問題,另一個(gè)全局變量i,不也同樣是thread1和thread2兩個(gè)線程的共享資源嗎?
讓我們將總循環(huán)次數(shù)提到10萬吧:#define MAX=100000
,同時(shí)將阻塞時(shí)間縮到sleep(0.001)
讓它運(yùn)行得快一點(diǎn)。然后重新編譯并運(yùn)行一下我們的代碼:
例程2:
看!最后結(jié)果是number=100018
,而并不是我們所設(shè)置的100000,那么很可能就是由于對于共享變量 i 的訪問發(fā)生了沖突。我將例程2運(yùn)行了5次,記錄每次最后的number值如下:
100018 100017 100011 100024 100009
為了驗(yàn)證確實(shí)是變量 i 導(dǎo)致的問題,而不是其它的什么原因——比如上帝在你的計(jì)算機(jī)里邊擲骰子,我們不妨給 i 也加上互斥。
例程3:
我新增了一個(gè)新的用于互斥的變量mut_i
,并在兩個(gè)線程函數(shù)的循環(huán)中都改用lock
和unlock
來保護(hù)記錄循環(huán)迭代的i++
語句。
我同樣將這個(gè)修改后的例程3運(yùn)行了5次,其中有四次是正確的結(jié)果number=100000
??珊苓z憾,我也不知道在第一次運(yùn)行中為什么會出現(xiàn)連續(xù)兩個(gè)number=99999
,不過我覺得這不一定是資源互斥中所導(dǎo)致的問題。
總之,凡有賦值的操作在多線程環(huán)境都要加鎖,不論是上述的number++
,還是i++
。因?yàn)?#xff0c;它們都不是原子操作,從機(jī)器指令的層面來看,一個(gè)高級語言中的i++
包含多個(gè)步驟,而一條語句還沒執(zhí)行完,可能就已經(jīng)發(fā)生中斷,轉(zhuǎn)而去執(zhí)行另一個(gè)線程了。
4、在例程2和例程3的執(zhí)行結(jié)果中,每次最終的number值都是偏大,為什么不會偏小呢?
例程4:
下面我解除了對于變量 number 的互斥保護(hù),而保持對 i 的互斥保護(hù)。
可以看到我連續(xù)運(yùn)行五次結(jié)果中,number 每次都是比10萬要偏小。這個(gè)具體的細(xì)節(jié),其實(shí)回憶一下以前數(shù)據(jù)庫并發(fā)控制中丟失修改導(dǎo)致的數(shù)據(jù)不一致問題,就明白了,情況如下圖所示。而當(dāng) i 偏小的時(shí)候,i 的遞增次數(shù)就小于實(shí)際迭代次數(shù),于是導(dǎo)致了 number 的結(jié)果偏大。
參考:
- https://blog.csdn.net/JMW1407/article/details/108318960 - int i =1 是原子操作嗎?i++是原子操作嗎?