多線程和多進程的區別(小結)

 

分類: linux 2009-06-19 09:33 11501人閱讀 評論(15) 收藏 舉報

很想寫點關于多進程和多線程的東西,我確實很愛他們。但是每每想動手寫點關于他們的東西,卻總是求全心理作祟,始終動不了手。

今天終于下了決心,寫點東西,以后可以再修修補補也無妨。

?

一.為何需要多進程(或者多線程),為何需要并發?

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

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

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

?

所以,為何需要并發?因為我們需要更強大的功能,提供更多的服務,所以并發,必不可少。

?

二.多進程

什么是進程。最直觀的就是一個個pid,官方的說法就:進程是程序在計算機上的一次執行活動。

說得簡單點,下面這段代碼執行的時候

view plain
  1. int?main()??
  2. ??
  3. {??
  4. ??
  5. printf(”pid?is?%d/n”,getpid()?);??
  6. ??
  7. return?0;??
  8. ??
  9. }??

?

進入main函數,這就是一個進程,進程pid會打印出來,然后運行到return,該函數就退出,然后由于該函數是該進程的唯一的一次執行,所以return后,該進程也會退出。

?

看看多進程。linux下創建子進程的調用是fork();

??

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


?

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測試下的運行結果。

?

關于fork函數,功能就是產生子進程,由于前面說過,進程就是執行的流程活動。

那么fork產生子進程的表現就是它會返回2次,一次返回0,順序執行下面的代碼。這是子進程。

一次返回子進程的pid,也順序執行下面的代碼,這是父進程。

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

如果fork失敗,會返回-1.

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

這里的print_exit是函數名還是函數指針呢?答案是函數指針,函數名永遠都只是一串無用的字符串。

某本書上的規則:函數名在用于非函數調用的時候,都等效于函數指針。

?

說到子進程只是一個額外的流程,那他跟父進程的聯系和區別是什么呢?

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

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

?

三.多線程

線程是可執行代碼的可分派單元。這個名稱來源于“執行的線索”的概念。在基于線程的多任務的環境中,所有進程有至少一個線程,但是它們可以具有多個任務。這意味著單個程序可以并發執行兩個或者多個任務。

?

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

還是先看linux提供的多線程的系統調用:

?

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

第一個參數為指向線程標識符的指針。
第二個參數用來設置線程屬性。
第三個參數是線程運行函數的起始地址。
最后一個參數是運行函數的參數。

???

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


?

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

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

?gcc -o pthrea -pthread? pthrea.c

?

四.比較以及注意事項

?

1.看完前面,應該對多進程和多線程有個直觀的認識。如果總結多進程和多線程的區別,你肯定能說,前者開銷大,后者開銷較小。確實,這就是最基本的區別。

2.線程函數的可重入性:

說到函數的可重入,和線程安全,我偷懶了,引用網上的一些總結。

?

線程安全:概念比較直觀。一般說來,一個函數被稱為線程安全的,當且僅當被多個并發線程反復調用時,它會一直產生正確的結果。

??

?

?

?

?

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

線程安全的條件:

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

可重入的判斷條件:

要確保函數可重入,需滿足一下幾個條件:

1、不在函數內部使用靜態或全局數據
2、不返回靜態或全局數據,所有數據都由函數的調用者提供。
3、使用本地數據,或者通過制作全局數據的本地拷貝來保護全局數據。
4、不調用不可重入函數。

?

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

?

?

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

?

如果我們的線程函數不是線程安全的,那在多線程調用的情況下,可能導致的后果是顯而易見的——共享變量的值由于不同線程的訪問,可能發生不可預料的變化,進而導致程序的錯誤,甚至崩潰。

?

3.關于IPC(進程間通信)

由于多進程要并發協調工作,進程間的同步,通信是在所難免的。

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

linux下進程間通信的幾種主要手段簡介:

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

或許你會有疑問,那多線程間要通信,應該怎么做?前面已經說了,多數的多線程都是在同一個進程下的,它們共享該進程的全局變量,我們可以通過全局變量來實現線程間通信。如果是不同的進程下的2個線程間通信,直接參考進程間通信。

?

4.關于線程的堆棧

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

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

?

?

5.在子線程里fork

?

看過好幾次有人問,在子線程函數里調用system或者 fork為何出錯,或者fork產生的子進程是完全復制父進程的嗎?

我測試過,只要你的線程函數滿足前面的要求,都是正常的。

?

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


?

?

上面這段代碼就可以正常得調用ls指令。

?

不過,在同時調用多進程(子進程里也調用線程函數)和多線程的情況下,函數體內很有可能死鎖。

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

?

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



??????? 函數pthread_join用來等待一個線程的結束。函數原型為:  extern int pthread_join __P (pthread_t __th, void **__thread_return);  第一個參數為被等待的線程標識符,第二個參數為一個用戶定義的指針,它可以用來存儲被等待線程的返回值。這個函數是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束為止,當函數返回時,被等待線程的資源被收回。如果執行成功,將返回0,如果失敗則返回一個錯誤號。


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

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

相關文章

redis-cli使用密碼登錄

redis-cli使用密碼登錄 注意IP地址要寫正確&#xff01; 學習了: https://blog.csdn.net/lsm135/article/details/52932896 https://blog.csdn.net/zyz511919766/article/details/42268219 https://zhidao.baidu.com/question/756651357338691604.html 登錄后 auth pass 或者 r…

FFT快速傅式變換算法halcon算子,持續更新

目錄convol_fftconvol_gaborcorrelation_fftdeserialize_fft_optimization_dataenergy_gaborfft_genericfft_imagefft_image_invgen_bandfiltergen_bandpassgen_derivative_filtergen_filter_maskgen_gaborgen_gauss_filtergen_highpassgen_lowpassgen_mean_filtergen_sin_band…

仿照vue實現簡易的MVVM框架(一)

代碼github地址&#xff1a; https://github.com/susantong/myMVVM 主要的方法有&#xff1a; compile 深度遍歷前端界面的節點&#xff0c;將其復制進一個addQuene隊列中pasers 遍歷所有的節點&#xff0c;并將節點包裝成一個含有本節點、自定義屬性及屬性值的對象。要想實現雙…

tomcat 啟動時內存溢出

在tomcat_home/bin目錄下找到catalina.bat&#xff0c;用文本編輯器打開&#xff0c;加上下面一行&#xff1a; set JAVA_OPTS -Xms1024M -Xmx1024M -XX:PermSize256M -XX:MaxNewSize256M -XX:MaxPermSize256M 解釋一下各個參數&#xff1a; -Xms1024M&#xff1a;初始化堆內存…

@angular/platform-browser-dynamic

/** experimental */ export declare class JitCompilerFactory implements CompilerFactory {createCompiler(options?: CompilerOptions[]): Compiler; }export declare const platformBrowserDynamic: (extraProviders?: StaticProvider[] | undefined) > PlatformRef;…

牛人項目失敗的總結

tom_lt: 遇到的失敗項目比較多&#xff01;讓人郁悶&#xff01;&#xff01; 仔細分析原因&#xff0c;主要在于&#xff1a; 1.項目開始需求不明確。領導決定動手&#xff0c;就開始啟動項目&#xff0c;造成和客戶需要差距太大&#xff0c;導致失敗&#xff1b; 2.需求變更沒…

Android:日常學習筆記(8)———探究UI開發(5)

Android:日常學習筆記(8)———探究UI開發(5) ListView控件的使用 ListView概述 A view that shows items in a vertically scrolling list. The items come from the ListAdapter associated with this view. 1.關于ArrayAdapter&#xff1a; ArrayAdapter<T> 是 ListAd…

Geometric-Transformations圖像幾何變換halcon算子,持續更新

目錄affine_trans_imageaffine_trans_image_sizeconvert_map_typemap_imagemirror_imagepolar_trans_image_extpolar_trans_image_invprojective_trans_imageprojective_trans_image_sizerotate_imagezoom_image_factorzoom_image_sizeaffine_trans_image 功能&#xff1a;把任…

hibernate inverse屬性的作用

hibernate配置文件中有這么一個屬性inverse&#xff0c;它是用來指定關聯的控制方的。inverse屬性默認是false&#xff0c;若為false&#xff0c;則關聯由自己控制&#xff0c;若為true&#xff0c;則關聯由對方控制。見例子&#xff1a; 一個Parent有多個Child,一個Child只能有…

分布式鎖與實現(一)——基于Redis實現

概述 目前幾乎很多大型網站及應用都是分布式部署的&#xff0c;分布式場景中的數據一致性問題一直是一個比較重要的話題。分布式的CAP理論告訴我們“任何一個分布式系統都無法同時滿足一致性&#xff08;Consistency&#xff09;、可用性&#xff08;Availability&#xff09;和…

淺析軟件項目管理中十個誤區(來自:http://manager.csdn.net/n/20051213/30907.html)

隨著計算機硬件水平的不斷提高&#xff0c;計算機軟件的規模和復雜度也隨之增加。計算機軟件開發從“個人英雄”時代向團隊時代邁進&#xff0c;計算機軟件項目的管理也從“作坊式”管理向“軟件工廠式”管理邁進。這就要求軟件開發人員特別是軟件項目管理人員更深一步地理解和…

倆孩隨筆

倆孩隨筆 有人給我貼了技術男加奶爸的標簽&#xff0c;不過這兩項都不是我的強項。我深知自己最大的長處在哪&#xff1a;普通&#xff0c;扔人堆里&#xff0c;不是認不出來&#xff0c;而是壓根看不著。想把事情做好&#xff0c;常常會用力過度。工作平平淡淡&#xff0c;需…

Inpainting圖像修復halcon算子,持續更新

目錄harmonic_interpolationinpainting_anisoinpainting_cedinpainting_ctinpainting_mcfinpainting_textureharmonic_interpolation 功能&#xff1a;對一個圖像區域執行諧波插值。 inpainting_aniso 功能&#xff1a;通過各向異性擴散執行圖像修復。 inpainting_ced 功能…

算法(偽代碼)的書寫

q ? Queue&#xff08;隊列&#xff09;, s ? Set&#xff08;集合&#xff09;pq ? PriorityQueue&#xff08;優先隊列&#xff09;d ? distance1. initialization 變量&#xff0c;數據結構的定義&#xff0c;及初始值的賦值&#xff1b;比如著名的 Dijkstra &#xff0…

第八次作業——系統設計與團隊分配(個人)

團隊作業地址&#xff1a;https://www.cnblogs.com/clio-hhhhhhl/p/9079157.html 個人碼云地址&#xff1a;https://gitee.com/Cliohl/events 團隊碼云地址&#xff1a;https://gitee.com/Cliohl/zhuoyue 項目進展&#xff1a; 上禮拜進展&#xff1a;把部分前端頁面實現出來&a…

深入理解Activity啟動流程(二)–Activity啟動相關類的類圖

本文原創作者:Cloud Chou. 歡迎轉載&#xff0c;請注明出處和本文鏈接 本系列博客將詳細闡述Activity的啟動流程&#xff0c;這些博客基于Cm 10.1源碼研究。 在介紹Activity的詳細啟動流程之前&#xff0c;先為大家介紹Activity啟動時涉及到的類&#xff0c;這樣大家可以有大概…

Lines色線halcon算子,持續更新

目錄bandpass_imagelines_colorlines_facetlines_gaussbandpass_image 功能&#xff1a;使用帶通濾波器提取邊緣。 lines_color 功能&#xff1a;檢測色線和它們的寬度。 lines_facet 功能&#xff1a;使用面模型檢測線。 lines_gauss 功能&#xff1a;檢測線和它們的寬…

疑問+軟件

問題 1學好軟件工程需要打好哪些基礎&#xff1f; 2怎樣學好軟件工程&#xff1f; 3軟件工程的發展前景如何&#xff1f; 4軟件工程具體能從事哪些職業&#xff1f; 5怎樣準確的找到自己的定位&#xff1f; 軟件 1支付寶 優點&#xff1a;支付便捷&#xff0c;轉賬方便&#xf…

成功的項目管理

內容提要 第一講 項目與項目管理 1.項目的基本概念 2.項目管理的概念 3.項目管理的重要性 第二講 項目管理的最新發展 1.現代項目管理的創立過程 2.現代項目管理的發展趨勢 3.項目管理流行的原因 第三講 信息時代的項目管理 1.信息時代的特點 2.信息時代項目管理的特點 3.按項目…

5月23日

11.1 LAMP架構介紹一、LAMP架構介紹LAMP是LinuxApache(httpd)MySQLPHP的簡寫&#xff0c;即把Apache、MySQL以及PHP安裝在linux系統上&#xff0c;組成一個運行環境來運行PHP腳本語言&#xff0c;通常是網站。比如Google、淘寶、百度、51cto博客、猿課論壇等就是用PHP語言寫出來…