国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

ios多線程和進(jìn)程的區(qū)別(轉(zhuǎn)載)

2019-11-14 20:17:05
字體:
供稿:網(wǎng)友

很想寫點(diǎn)關(guān)于多進(jìn)程和多線程的東西,我確實(shí)很愛他們。但是每每想動(dòng)手寫點(diǎn)關(guān)于他們的東西,卻總是求全心理作祟,始終動(dòng)不了手。

今天終于下了決心,寫點(diǎn)東西,以后可以再修修補(bǔ)補(bǔ)也無妨。

 

一.為何需要多進(jìn)程(或者多線程),為何需要并發(fā)?

這個(gè)問題或許本身都不是個(gè)問題。但是對(duì)于沒有接觸過多進(jìn)程編程的朋友來說,他們確實(shí)無法感受到并發(fā)的魅力以及必要性。

我想,只要你不是整天都寫那種int main()到底的代碼的人,那么或多或少你會(huì)遇到代碼響應(yīng)不夠用的情況,也應(yīng)該有嘗過并發(fā)編程的甜頭。就像一個(gè)快餐點(diǎn)的服務(wù)員,既要在前臺(tái)接待客戶點(diǎn)餐,又要接電話送外賣,沒有分身術(shù)肯定會(huì)忙得你焦頭爛額的。幸運(yùn)的是確實(shí)有這么一種技術(shù),讓你可以像孫悟空一樣分身,靈魂出竅,樂哉樂哉地輕松應(yīng)付一切狀況,這就是多進(jìn)程/線程技術(shù)。

并發(fā)技術(shù),就是可以讓你在同一時(shí)間同時(shí)執(zhí)行多條任務(wù)的技術(shù)。你的代碼將不僅僅是從上到下,從左到右這樣規(guī)規(guī)矩矩的一條線執(zhí)行。你可以一條線在main函數(shù)里跟你的客戶交流,另一條線,你早就把你外賣送到了其他客戶的手里。

 

所以,為何需要并發(fā)?因?yàn)槲覀冃枰鼜?qiáng)大的功能,提供更多的服務(wù),所以并發(fā),必不可少。

 

二.多進(jìn)程

什么是進(jìn)程。最直觀的就是一個(gè)個(gè)pid,官方的說法就:進(jìn)程是程序在計(jì)算機(jī)上的一次執(zhí)行活動(dòng)。

說得簡(jiǎn)單點(diǎn),下面這段代碼執(zhí)行的時(shí)候

  1. int main()
  2. {
  3. PRintf(”pid is %d/n”,getpid() );
  4. return 0;
  5. }
  1. int main()
  2. {
  3. printf(”pid is %d/n”,getpid() );
  4. return 0;
  5. }

 

進(jìn)入main函數(shù),這就是一個(gè)進(jìn)程,進(jìn)程pid會(huì)打印出來,然后運(yùn)行到return,該函數(shù)就退出,然后由于該函數(shù)是該進(jìn)程的唯一的一次執(zhí)行,所以return后,該進(jìn)程也會(huì)退出。

 

看看多進(jìn)程。linux下創(chuàng)建子進(jìn)程的調(diào)用是fork();

 

  1. #include <unistd.h>
  2. #include <sys/types.h>
  3. #include <stdio.h>
  4. void print_exit()
  5. {
  6. printf("the exit pid:%d/n",getpid() );
  7. }
  8. main ()
  9. {
  10. pid_t pid;
  11. atexit( print_exit ); //注冊(cè)該進(jìn)程退出時(shí)的回調(diào)函數(shù)
  12. pid=fork();
  13. if (pid < 0)
  14. printf("error in fork!");
  15. else if (pid == 0)
  16. printf("i am the child process, my process id is %d/n",getpid());
  17. else
  18. {
  19. printf("i am the parent process, my process id is %d/n",getpid());
  20. sleep(2);
  21. wait();
  22. }
  23. }
  1. #include <unistd.h>
  2. #include <sys/types.h>
  3. #include <stdio.h>
  4. void print_exit()
  5. {
  6. printf("the exit pid:%d/n",getpid() );
  7. }
  8. main ()
  9. {
  10. pid_t pid;
  11. atexit( print_exit ); //注冊(cè)該進(jìn)程退出時(shí)的回調(diào)函數(shù)
  12. pid=fork();
  13. if (pid < 0)
  14. printf("error in fork!");
  15. else if (pid == 0)
  16. printf("i am the child process, my process id is %d/n",getpid());
  17. else
  18. {
  19. printf("i am the parent process, my process id is %d/n",getpid());
  20. sleep(2);
  21. wait();
  22. }
  23. }

 

i am the child process, my process id is 15806 the exit pid:15806 i am the parent process, my process id is 15805 the exit pid:15805

這是gcc測(cè)試下的運(yùn)行結(jié)果。

 

關(guān)于fork函數(shù),功能就是產(chǎn)生子進(jìn)程,由于前面說過,進(jìn)程就是執(zhí)行的流程活動(dòng)。

那么fork產(chǎn)生子進(jìn)程的表現(xiàn)就是它會(huì)返回2次,一次返回0,順序執(zhí)行下面的代碼。這是子進(jìn)程。

一次返回子進(jìn)程的pid,也順序執(zhí)行下面的代碼,這是父進(jìn)程。

(為何父進(jìn)程需要獲取子進(jìn)程的pid呢?這個(gè)有很多原因,其中一個(gè)原因:看最后的wait,就知道父進(jìn)程等待子進(jìn)程的終結(jié)后,處理其task_struct結(jié)構(gòu),否則會(huì)產(chǎn)生僵尸進(jìn)程,扯遠(yuǎn)了,有興趣可以自己google)。

如果fork失敗,會(huì)返回-1.

額外說下atexit( print_exit ); 需要的參數(shù)肯定是函數(shù)的調(diào)用地址。

這里的print_exit 是函數(shù)名還是函數(shù)指針呢?答案是函數(shù)指針,函數(shù)名永遠(yuǎn)都只是一串無用的字符串。

某本書上的規(guī)則:函數(shù)名在用于非函數(shù)調(diào)用的時(shí)候,都等效于函數(shù)指針。

 

說到子進(jìn)程只是一個(gè)額外的流程,那他跟父進(jìn)程的聯(lián)系和區(qū)別是什么呢?

我很想建議你看看linux內(nèi)核的注解(有興趣可以看看,那里才有本質(zhì)上的了解),總之,fork后,子進(jìn)程會(huì)復(fù)制父進(jìn)程的task_struct結(jié)構(gòu),并為子進(jìn)程的堆棧分配物理頁。理論上來說,子進(jìn)程應(yīng)該完整地復(fù)制父進(jìn)程的堆,棧以及數(shù)據(jù)空間,但是2者共享正文段。

關(guān)于寫時(shí)復(fù)制:由于一般 fork后面都接著exec,所以,現(xiàn)在的 fork都在用寫時(shí)復(fù)制的技術(shù),顧名思意,就是,數(shù)據(jù)段,堆,棧,一開始并不復(fù)制,由父,子進(jìn)程共享,并將這些內(nèi)存設(shè)置為只讀。直到父,子進(jìn)程一方嘗試寫這些區(qū)域,則內(nèi)核才為需要修改的那片內(nèi)存拷貝副本。這樣做可以提高 fork的效率。

 

三.多線程

線程是可執(zhí)行代碼的可分派單元。這個(gè)名稱來源于“執(zhí)行的線索”的概念。在基于線程的多任務(wù)的環(huán)境中,所有進(jìn)程有至少一個(gè)線程,但是它們可以具有多個(gè)任務(wù)。這意味著單個(gè)程序可以并發(fā)執(zhí)行兩個(gè)或者多個(gè)任務(wù)。

 

簡(jiǎn)而言之,線程就是把一個(gè)進(jìn)程分為很多片,每一片都可以是一個(gè)獨(dú)立的流程。這已經(jīng)明顯不同于多進(jìn)程了,進(jìn)程是一個(gè)拷貝的流程,而線程只是把一條河流截成很多條小溪。它沒有拷貝這些額外的開銷,但是僅僅是現(xiàn)存的一條河流,就被多線程技術(shù)幾乎無開銷地轉(zhuǎn)成很多條小流程,它的偉大就在于它少之又少的系統(tǒng)開銷。(當(dāng)然偉大的后面又引發(fā)了重入性等種種問題,這個(gè)后面慢慢比較)。

還是先看linux提供的多線程的系統(tǒng)調(diào)用:

 

int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void),  void *restrict arg);

Returns: 0 if OK, error number on failure

第一個(gè)參數(shù)為指向線程標(biāo)識(shí)符的指針。 第二個(gè)參數(shù)用來設(shè)置線程屬性。 第三個(gè)參數(shù)是線程運(yùn)行函數(shù)的起始地址。 最后一個(gè)參數(shù)是運(yùn)行函數(shù)的參數(shù)。

 

  1. #include<stdio.h>
  2. #include<string.h>
  3. #include<stdlib.h>
  4. #include<unistd.h>
  5. #include<pthread.h>
  6. void* task1(void*);
  7. void* task2(void*);
  8. void usr();
  9. int p1,p2;
  10. int main()
  11. {
  12. usr();
  13. getchar();
  14. return 1;
  15. }
  16. void usr()
  17. {
  18. pthread_t pid1, pid2;
  19. pthread_attr_t attr;
  20. void *p;
  21. int ret=0;
  22. pthread_attr_init(&attr); //初始化線程屬性結(jié)構(gòu)
  23. pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //設(shè)置attr結(jié)構(gòu)為分離
  24. pthread_create(&pid1, &attr, task1, NULL); //創(chuàng)建線程,返回線程號(hào)給pid1,線程屬性設(shè)置為attr的屬性,線程函數(shù)入口為task1,參數(shù)為NULL
  25. pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
  26. pthread_create(&pid2, &attr, task2, NULL);
  27. //前臺(tái)工作
  28. ret=pthread_join(pid2, &p); //等待pid2返回,返回值賦給p
  29. printf("after pthread2:ret=%d,p=%d/n", ret,(int)p);
  30. }
  31. void* task1(void *arg1)
  32. {
  33. printf("task1/n");
  34. //艱苦而無法預(yù)料的工作,設(shè)置為分離線程,任其自生自滅
  35. pthread_exit( (void *)1);
  36. }
  37. void* task2(void *arg2)
  38. {
  39. int i=0;
  40. printf("thread2 begin./n");
  41. //繼續(xù)送外賣的工作
  42. pthread_exit((void *)2);
  43. }
  1. #include<stdio.h>
  2. #include<string.h>
  3. #include<stdlib.h>
  4. #include<unistd.h>
  5. #include<pthread.h>
  6. void* task1(void*);
  7. void* task2(void*);
  8. void usr();
  9. int p1,p2;
  10. int main()
  11. {
  12. usr();
  13. getchar();
  14. return 1;
  15. }
  16. void usr()
  17. {
  18. pthread_t pid1, pid2;
  19. pthread_attr_t attr;
  20. void *p;
  21. int ret=0;
  22. pthread_attr_init(&attr); //初始化線程屬性結(jié)構(gòu)
  23. pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //設(shè)置attr結(jié)構(gòu)為分離
  24. pthread_create(&pid1, &attr, task1, NULL); //創(chuàng)建線程,返回線程號(hào)給pid1,線程屬性設(shè)置為attr的屬性,線程函數(shù)入口為task1,參數(shù)為NULL
  25. pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
  26. pthread_create(&pid2, &attr, task2, NULL);
  27. //前臺(tái)工作
  28. ret=pthread_join(pid2, &p); //等待pid2返回,返回值賦給p
  29. printf("after pthread2:ret=%d,p=%d/n", ret,(int)p);
  30. }
  31. void* task1(void *arg1)
  32. {
  33. printf("task1/n");
  34. //艱苦而無法預(yù)料的工作,設(shè)置為分離線程,任其自生自滅
  35. pthread_exit( (void *)1);
  36. }
  37. void* task2(void *arg2)
  38. {
  39. int i=0;
  40. printf("thread2 begin./n");
  41. //繼續(xù)送外賣的工作
  42. pthread_exit((void *)2);
  43. }

 

這個(gè)多線程的例子應(yīng)該很明了了,主線程做自己的事情,生成2個(gè)子線程,task1為分離,任其自生自滅,而task2還是繼續(xù)送外賣,需要等待返回。(因該還記得前面說過僵尸進(jìn)程吧,線程也是需要等待的。如果不想等待,就設(shè)置線程為分離線程)

額外的說下,linux下要編譯使用線程的代碼,一定要記得調(diào)用pthread庫。如下編譯:

gcc -o pthrea -pthread pthrea.c

 

四.比較以及注意事項(xiàng)

 

1.看完前面,應(yīng)該對(duì)多進(jìn)程和多線程有個(gè)直觀的認(rèn)識(shí)。如果總結(jié)多進(jìn)程和多線程的區(qū)別,你肯定能說,前者開銷大,后者開銷較小。確實(shí),這就是最基本的區(qū)別。

2.線程函數(shù)的可重入性:

說到函數(shù)的可重入,和線程安全,我偷懶了,引用網(wǎng)上的一些總結(jié)。

 

線程安全:概念比較直觀。一般說來,一個(gè)函數(shù)被稱為線程安全的,當(dāng)且僅當(dāng)被多個(gè)并發(fā)線程反復(fù)調(diào)用時(shí),它會(huì)一直產(chǎn)生正確的結(jié)果。

 

 

 

 

 

可重入:概念基本沒有比較正式的完整解釋,但是它比線程安全要求更嚴(yán)格。根據(jù)經(jīng)驗(yàn),所謂“重入”,常見的情況是,程序執(zhí)行到某個(gè)函數(shù)foo()時(shí),收到信號(hào),于是暫停目前正在執(zhí)行的函數(shù),轉(zhuǎn)到信號(hào)處理函數(shù),而這個(gè)信號(hào)處理函數(shù)的執(zhí)行過程中,又恰恰也會(huì)進(jìn)入到剛剛執(zhí)行的函數(shù)foo(),這樣便發(fā)生了所謂的重入。此時(shí)如果foo()能夠正確的運(yùn)行,而且處理完成后,之前暫停的foo()也能夠正確運(yùn)行,則說明它是可重入的。

線程安全的條件:

要確保函數(shù)線程安全,主要需要考慮的是線程之間的共享變量。屬于同一進(jìn)程的不同線程會(huì)共享進(jìn)程內(nèi)存空間中的全局區(qū)和堆,而私有的線程空間則主要包括棧和寄存器。因此,對(duì)于同一進(jìn)程的不同線程來說,每個(gè)線程的局部變量都是私有的,而全局變量、局部靜態(tài)變量、分配于堆的變量都是共享的。在對(duì)這些共享變量進(jìn)行訪問時(shí),如果要保證線程安全,則必須通過加鎖的方式。

可重入的判斷條件:

要確保函數(shù)可重入,需滿足一下幾個(gè)條件:

1、不在函數(shù)內(nèi)部使用靜態(tài)或全局?jǐn)?shù)據(jù)  2、不返回靜態(tài)或全局?jǐn)?shù)據(jù),所有數(shù)據(jù)都由函數(shù)的調(diào)用者提供。  3、使用本地?cái)?shù)據(jù),或者通過制作全局?jǐn)?shù)據(jù)的本地拷貝來保護(hù)全局?jǐn)?shù)據(jù)。 4、不調(diào)用不可重入函數(shù)。

 

可重入與線程安全并不等同,一般說來,可重入的函數(shù)一定是線程安全的,但反過來不一定成立。它們的關(guān)系可用下圖來表示:

 

 

比如:strtok函數(shù)是既不可重入的,也不是線程安全的;加鎖的strtok不是可重入的,但線程安全;而strtok_r既是可重入的,也是線程安全的。

 

如果我們的線程函數(shù)不是線程安全的,那在多線程調(diào)用的情況下,可能導(dǎo)致的后果是顯而易見的——共享變量的值由于不同線程的訪問,可能發(fā)生不可預(yù)料的變化,進(jìn)而導(dǎo)致程序的錯(cuò)誤,甚至崩潰。

 

3.關(guān)于ipC(進(jìn)程間通信)

由于多進(jìn)程要并發(fā)協(xié)調(diào)工作,進(jìn)程間的同步,通信是在所難免的。

稍微列舉一下linux常見的IPC.

linux下進(jìn)程間通信的幾種主要手段簡(jiǎn)介:

  1. 管道(Pipe)及有名管道(named pipe):管道可用于具有親緣關(guān)系進(jìn)程間的通信,有名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關(guān)系進(jìn)程間的通信;
  2. 信號(hào)(Signal):信號(hào)是比較復(fù)雜的通信方式,用于通知接受進(jìn)程有某種事件發(fā)生,除了用于進(jìn)程間通信外,進(jìn)程還可以發(fā)送信號(hào)給進(jìn)程本身;linux除了支持Unix早期信號(hào)語義函數(shù)sigal外,還支持語義符合Posix.1標(biāo)準(zhǔn)的信號(hào)函數(shù)sigaction(實(shí)際上,該函數(shù)是基于BSD的,BSD為了實(shí)現(xiàn)可靠信號(hào)機(jī)制,又能夠統(tǒng)一對(duì)外接口,用sigaction函數(shù)重新實(shí)現(xiàn)了signal函數(shù));
  3. 報(bào)文(Message)隊(duì)列(消息隊(duì)列):消息隊(duì)列是消息的鏈接表,包括Posix消息隊(duì)列system V消息隊(duì)列。有足夠權(quán)限的進(jìn)程可以向隊(duì)列中添加消息,被賦予讀權(quán)限的進(jìn)程則可以讀走隊(duì)列中的消息。消息隊(duì)列克服了信號(hào)承載信息量少,管道只能承載無格式字節(jié)流以及緩沖區(qū)大小受限等缺點(diǎn)。
  4. 共享內(nèi)存:使得多個(gè)進(jìn)程可以訪問同一塊內(nèi)存空間,是最快的可用IPC形式。是針對(duì)其他通信機(jī)制運(yùn)行效率較低而設(shè)計(jì)的。往往與其它通信機(jī)制,如信號(hào)量結(jié)合使用,來達(dá)到進(jìn)程間的同步及互斥。
  5. 信號(hào)量(semaphore):主要作為進(jìn)程間以及同一進(jìn)程不同線程之間的同步手段。
  6. 套接口(Socket):更為一般的進(jìn)程間通信機(jī)制,可用于不同機(jī)器之間的進(jìn)程間通信。起初是由Unix系統(tǒng)的BSD分支開發(fā)出來的,但現(xiàn)在一般可以移植到其它類Unix系統(tǒng)上:Linux和System V的變種都支持套接字。

或許你會(huì)有疑問,那多線程間要通信,應(yīng)該怎么做?前面已經(jīng)說了,多數(shù)的多線程都是在同一個(gè)進(jìn)程下的,它們共享該進(jìn)程的全局變量,我們可以通過全局變量來實(shí)現(xiàn)線程間通信。如果是不同的進(jìn)程下的2個(gè)線程間通信,直接參考進(jìn)程間通信。

 

4.關(guān)于線程的堆棧

說一下線程自己的堆棧問題。

是的,生成子線程后,它會(huì)獲取一部分該進(jìn)程的堆棧空間,作為其名義上的獨(dú)立的私有空間。(為何是名義上的呢?)由于,這些線程屬于同一個(gè)進(jìn)程,其他線程只要獲取了你私有堆棧上某些數(shù)據(jù)的指針,其他線程便可以自由訪問你的名義上的私有空間上的數(shù)據(jù)變量。(注:而多進(jìn)程是不可以的,因?yàn)椴煌倪M(jìn)程,相同的虛擬地址,基本不可能映射到相同的物理地址)

 

 

5.在子線程里fork

 

看過好幾次有人問,在子線程函數(shù)里調(diào)用system或者 fork為何出錯(cuò),或者fork產(chǎn)生的子進(jìn)程是完全復(fù)制父進(jìn)程的嗎?

我測(cè)試過,只要你的線程函數(shù)滿足前面的要求,都是正常的。

 

  1. #include<stdio.h>
  2. #include<string.h>
  3. #include<stdlib.h>
  4. #include<unistd.h>
  5. #include<pthread.h>
  6. void* task1(void *arg1)
  7. {
  8. printf("task1/n");
  9. system("ls");
  10. pthread_exit( (void *)1);
  11. }
  12. int main()
  13. {
  14. int ret=0;
  15. void *p;
  16. int p1=0;
  17. pthread_t pid1;
  18. pthread_create(&pid1, NULL, task1, NULL);
  19. ret=pthread_join(pid1, &p);
  20. printf("end main/n");
  21. return 1;
  22. }
  1. #include<stdio.h>
  2. #include<string.h>
  3. #include<stdlib.h>
  4. #include<unistd.h>
  5. #include<pthread.h>
  6. void* task1(void *arg1)
  7. {
  8. printf("task1/n");
  9. system("ls");
  10. pthread_exit( (void *)1);
  11. }
  12. int main()
  13. {
  14. int ret=0;
  15. void *p;
  16. int p1=0;
  17. pthread_t pid1;
  18. pthread_create(&pid1, NULL, task1, NULL);
  19. ret=pthread_join(pid1, &p);
  20. printf("end main/n");
  21. return 1;
  22. }

 

 

上面這段代碼就可以正常得調(diào)用ls指令。

 

不過,在同時(shí)調(diào)用多進(jìn)程(子進(jìn)程里也調(diào)用線程函數(shù))和多線程的情況下,函數(shù)體內(nèi)很有可能死鎖。

具體的例子可以看看這篇文章。

 

http://www.cppblog.com/lymons/archive/2008/06/01/51836.aspx


發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 宁河县| 黄冈市| 洪江市| 屏南县| 张家界市| 杭锦后旗| 含山县| 和硕县| 社旗县| 陇川县| 外汇| 伊通| 台中县| 塔城市| 基隆市| 中方县| 澳门| 富源县| 连州市| 绵竹市| 颍上县| 游戏| 崇仁县| 岳普湖县| 封丘县| 柳州市| 资兴市| 长寿区| 龙川县| 松滋市| 井冈山市| 时尚| 顺昌县| 遂川县| 南召县| 玛曲县| 渑池县| 星座| 青冈县| 宣恩县| 交口县|