Linux 下多線程和多進程程序的優缺點,各自適合什麼樣的業務場景?
什麼樣的場景下適合多線程,什麼樣的適合多進程?怎樣提高多線程的效率?
判斷的原則非常簡單:
在能使用多進程解決一個問題的時候不要使用多線程。RCU的發明者,Paul McKenny 在《Is Parallel Programming Hard, And, If So, What Can You Do About It?》一書中也有這樣的看法。
多線程的優點:- 方便高效的內存共享 - 多進程下內存共享比較不便,且會抵消掉多進程編程的好處
- 較輕的上下文切換開銷 - 不用切換地址空間,不用更改CR3寄存器,不用清空TLB。
- 更強的容錯性 - 一個進程crash不會導致整個系統崩潰
- 更好的多核可伸縮性 - 進程的使用將許多內核資源(如地址空間,頁表,打開的文件)隔離,在多核系統上的可伸縮性強於多線程程序
綜上,當你的不同任務間需要大量共享數據或頻繁通信時,使用多線程,其他情況下盡量使用多進程。
提高多線程程序效率的一般方法:- 不要頻繁創建,銷毀線程,使用線程池
- 減少線程間同步和通信(最為關鍵)
- 避免需要頻繁共享寫的數據
- 合理安排共享數據結構,避免偽共享(false sharing)
- 使用非阻塞數據結構/演算法
- 避免可能產生可伸縮性問題的系統調用(比如mmap)
- 避免產生大量缺頁異常,盡量使用Huge Page
- 可以的話使用用戶態輕量級線程代替內核線程
進程相對於線程最大的區別是有內核保證的隔離:數據和錯誤隔離。對於使用如C/C++這些語言編寫的本地代碼,錯誤隔離是非常有用的:採用多進程架構的程序一般可以做到一定程度的自恢復
不過,託管編程語言大大降低甚至消滅了進程seg fault這樣的「突然死亡」的可能性。當一切例外情況都可以用異常、返回值等託管機制來處理時,進程提供的錯誤隔離的意義就越來越小,而數據隔離則被視作影響性能的因素,這時只使用線程編程有時更合理樓上極為同學的答案雖好,但都不是針對題目的,樓主問題是基於Linux環境下,事實上,Linux系統中,進程和線程的唯一區別就是對資源共享的問題,在Linux下面,線程和其他操作系統的概念是有區別的,實質上Linux中創建線程的系統調用都和創建進程的一致,具體可參見<手機碼字沒法詳細查閱資料,明天補充>,因此,在Linux環境下,創建線程,代價和創建進程差別不大,而進行上下文切換(這個當然我也還需研究,不過記憶中進程和線程的上下文切換主要的開銷還是在內核態到用戶態的轉換上面),而且線程之間共享資源更加方便,線程間通信也更為簡單,唯一的問題是,線程的使用api相較於新手更難一點對於最後一個問題,提高多線程工作效率,線程池,減少互斥,避免死鎖。多開幾個線程搶佔cpu資源
首
先要明確的是,按照LKD 2裡面的說法,LINUX和其他OS 比如WINDOWS,
SOLARIS之間一個很大的不同是沒有嚴格定義的線程(thread)。那麼你也許會問,如果LINUX中沒有線程,那麼如何來表示類似WINDOWS
線程的那種執行觀念呢?答案是LINUX中,PROCESS(進程)可以當作線程。
LINUX中是怎樣表示的呢?具體來說,LINUX中的PROCESS有2種。一種是獨立的PROCESS。自己有自己的地址空間,資源列表,代碼等。另
外一種PROCESS是和其他PROCESS共享一個地址空間,資源列表的。這種PROCESS就類似於WINDOWS中的線程。在看LINUX內核代碼的時候,你會同時看到process, task, thread這3個名字。下面簡要介紹下他們之間的區別:1、task 可以理解為一個LINUX PROCESS。定
義TASK的數據結構叫做struct task_struct,
在linuxsched.h中。我覺得這個名字起得不好。因為大家都已經對PROCESS,
THREAD之類得觀念很熟悉了。現在又冒出來個TASK,很容易讓人搞混。不過也許是歷史原因吧。這個TASK一直保留著。在
task_struct 中有一堆的成員。其中有PID 和TGID. PID實際上類似於WINDOWS中的THREAD ID。而TGID
(thead group id) 對應於WINDOWS中的PID。PID對於獨立的PROCESS來說,就是它的PID。這時PID ==
TGID。
對於和其他PROCESS共享地址空間的PROCESS來說,每個都有獨立的PID,但是他們的TGID是一樣的。
2、thead雖然說LINUX不支持THREAD. 但是在內核代碼里又可以看到THREAD這個名字。這時可以把他們和WINDOWS中的THREAD對應起來。一個比較著名的是thread_info 結構。3、kernel thread在LINUX中,kernel thread是一個專門的名詞。它的特點是沒有獨立的地址空間(MM結構為NULL). 他們只運行在KERNEL SPACE.不能切換到USER SPACE。最後,總結下,在LINUX中,一個PROCESS即可能是一個WINDOWS PROCESS類似的觀念,也可能是一個與WINDOWS THREAD類似的觀念。而且有時還被叫做TASK(感覺有點亂)。Linux中的task,process, thread 簡介-qbeyxbr-搜狐博客
@劉然 在linux環境下的線程和其他地方的線程還是不太一樣吧。。**************************************************************************************************************再沾一段博文。。只是說明下我上文提出的在linux環境下,線程和進程創建用的是一個系統調用。Process Creation
Linux通過clone()系統調用來實現的fork(), clone() 在內核中調用是的do_fork()函數, 而do_fork()又調用了copy_process()來forking process,copy_process()的流程如下:
- call dup_task_struct,這個創建一個kernel stack/thread_info/task_struct給新的process;
- 檢測是否超過了當前user的資源限制;
- 將child和parent區分開,將process descriptor的成員清空或設置初始值;
- child的state被置為TASK_UNINTERRUPTIBLE,來保證child還不被運行;
- 設置child的task_struct的flags成員,標示是超級用戶許可權的PF_SUPERPRIV被清空,標示還未調用exec()的PF_FORKNOEXEC被置上;
- 通過get_pid()給pid成員賦值;
- 根據傳給clone()的參數來決定是否賦值open files/file system info/signal handlers/process address space/name space
- 分割parent的剩餘時間片
- 最後copy_process返回一個指向新child的指針給caller
The Linux Implementation of Threads
Linux實現thread的方法比較特別,在linux內核中並沒有thread這麼一個概念,所有的線程在Linux內核中被看做是標
准進程,Linux
Kernel並不提供針對線程的調度,取而代之的是,在Linux中線程僅僅是一個與其他進程共享某些特定資源的進程,每個線程有獨立的
task_struct。
Linux的線程是由帶有如下參數的clone()系統調用創建的:
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);
參數的含義:
CLONE_VM: Parent and child share address space.
CLONE_FS: Parent and child share filesystem information
CLONE_FILES: Parent and child share open files.
CLONE_SIGHAND: Parent and child share signal handlers and blockedsignals.我覺得從以下幾個方面考慮:
1:通信,進程與線程的通信機制不一樣,進程擁有獨立的內存棧與堆,在不使用共享內存的基礎上需要操作系統的消息機制,網路等進行通信。內存共享需要考慮資源的訪問的競爭,例如鎖機制等,當然,線程也是一樣需要的,線程共享堆空間。2:效率,線程是CPU的最小工作單位,而進程更多的被理解為程序的一個執行實例。相比進程而言,線程在新建和回收的效率上很高,但它還是一個耗時的操作,所以需要線程池等來加快這一類操作。3:資源:進程與線程獲取到的資源是不一樣的,具體我不會細說了,我也是半桶水。有人把進程比喻為工廠,線程是工廠的流水線。雖然我覺得不是非常合適,但這個比喻還是很生動的。具體該怎麼選擇還真得看具體需求了,我懂的不多,為樓主開個頭,期待更好的回答吧推薦閱讀:
※如何使用 Go 語言寫遊戲伺服器?
※對於很多發燒級PC硬體玩家來說,伺服器cpu、recc內存和RAID陣列等等伺服器上的硬體或者技術真的有必要麼?
※為何多個 http 的 80 埠的站點可以共用同一個伺服器(IP),而 https 卻不行?
※如何寫一個web伺服器?
※Web 伺服器與應用伺服器的區別是什麼?