Linux線程簡說

Linux線程簡說 linux

尹德位 2015 西安 nginx

本文內容臃腫,做者盡力在描述本身對線程的感悟,表達能力有限,敬請讀者去其糟粕。  數據庫

系統環境:  RedHat Enterprise Linux 7.0 x86_64 多線程

內核版本:  linux-4.1.0 併發

Glibc版本:  glibc-2.22 函數

IT名言:進程是資源分配的基本單位。線程是程序調度的基本單位。 性能

目錄: 學習

1.線程概念 spa

2.線程建立 操作系統

3.線程特性

4.線程控制

5.線程同步

6.線程應用

<<正文>>

1.線程概念

衆所周知,進程就是一個關於某數據集合的運行過程。現代操做系統理論體系是創建在進程基礎上的。一切功能性程序,http服務,nginx服務,Oracle數據庫等等都是以進程爲單位運行的。進程的運行除了須要分配內存裝載代碼段(.txt段)外,還須要分配必要的資源才能運行。

有時候咱們須要利用CPU多核的優點實現代碼的並行執行來提升效率,而恰巧這些多份並行執行的代碼是能夠共享資源,這種狀況下采用進程技術來實現多任務程序就顯得臃腫笨拙了。因而線程技術就在千呼萬喚中誕生了。

線程,是輕量級進程。除了必要的棧、上下文等資源外,線程不須要其餘太多額外資源便能運行。其精巧高效的特性是軟件設計中常採用線程來實現多任務的主要緣由。比起進程,線程更便於靈活調度運行。於是現在的操做系統都以線程爲單位來調度,以極大提升系統的運行效率。

儘管線程有許多優點,但並不是能夠取代進程。下面講述兩者的區別,以便讀者理解其應用場景。

區別1:獨立性

進程是徹底獨立的,進程之間互不干擾對方的內存空間(也稱進程空間),親屬進程建立的多個子進程與其父進程之間依然保持獨立性,換言之,若任一進程出現故障,不影響其餘進程運行。而線程與之相比則不一樣,處於同一進程空間中的多個子線程之間共享父線程(父進程)的資源,任一線程若出現故障致使系統信號觸發,則整個進程空間都受牽連。好比某一線程訪問非法內存地址,則SIGSEGV信號將終結整個進程。

區別2:做用範圍

進程是全系統範圍內可見的,經過ps命令能夠看到任何正在運行的進程信息。固然線程也能夠看到,但不一樣的是,線程只能與同一進程空間的其餘線程之間保持聯繫,跨越進程的線程之間沒有創建通訊的必要。由於線程只是共享父進程數據資源,跨越進程的線程由於加工數據資源的對象處於不一樣空間而失去意義。

區別3:正文段區間

子進程的正文段與父進程共享,且擁有所有執行區間,若沒有嚴格邏輯限制,則子進程將從其建立開始,與父進程執行同一代碼副本直到結束。而線程的運行期間相較而言短了許多,只是從線程入口函數開始,到其函數結束線程自動終止。

區別4:項目代碼體系

進程建立的系統調用fork由內核提供;線程庫函數由glibc(運行於內核之上的庫)提供。


2.線程建立

幾乎全部的Linux操做系統都提供線程技術,由於線程的實現由Linux內核提供。

線程建立函數:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,

                          void *(*start_routine) (void *), void *arg);

該函數功能是建立一個新的線程,其函數體在標準glibc庫中的代碼能夠看到:

http://ftp.gnu.org/pub/gnu/glibc/glibc-2.22.tar.gz

nptl/pthread_create.c :

int __pthread_create_2_1 (newthread, attr, start_routine, arg)

      pthread_t *newthread;

      const pthread_attr_t *attr;

      void *(*start_routine) (void *);

從源碼得知,線程真身的建立是調用了create_thread函數:

/* Create the thread.  We always create the thread stopped

   so that it does not get far before we tell the debugger.  */

   retval = create_thread (pd, iattr, true, STACK_VARIABLES_ARGS,      

          &thread_ran);

繼續追蹤create_thread:

sysdeps/unix/sysv/linux/createthread.c

const int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SYSVSEM

        | CLONE_SIGHAND | CLONE_THREAD

        | CLONE_SETTLS | CLONE_PARENT_SETTID

        | CLONE_CHILD_CLEARTID

        | 0);

由此發現,線程的建立實際上仍是clone技術的封裝。換句話說,內核提供的clone系統調用能夠建立線程,而POSIX線程庫也是經過clone技術實現的線程。Linux的NPTL線程技術兼容了POSIX標準,因此在描述上不作區分。

可經過Linux的man手冊頁來查看 : man clone

int clone(int (*fn)(void *), void *child_stack,

                 int flags, void *arg, ...

                 /* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );

綜上所述,不管是調用clone建立線程,仍是調用POSIX線程庫的pthread_create建立一個線程,其本質都是同樣。


3.線程特性

許多教科書在描述線程和進程的時候都不會刻意區分,由於在執行單位上兩者沒有區別。內核對線程和進程的調度,其實是統一的。咱們知道,每一個進程都有一個進程控制信息結構,維護在內核數據區。該結構爲 struct task_struct ,每個進程都會有一個惟一的結構體與之對應。

該結構體的原型定義在內核代碼的 include/linux/sched.h中。

線程,一樣具備該結構體。也即每一個線程也都有一個徹底獨立的結構體節點。

那麼,問題就出現了:

進程的管理都是以其標識進程ID(即PID)來實現的,莫非線程也具備PID ?

答案是確定的。

接下來咱們就詳細討論下線程的標識ID。

首先,咱們來看看,命令行裏看看多進程和多線程的顯示結果。

經過ps auxH 或者 pstree –ap 來查看(讀者可本身編寫一個建立線程的程序):


進程號35985的進程有一個ID爲35986的子進程。還發現它的名字還有一對「{ }」,其實35986就是一個線程。普通進程身份的子進程的名字不帶大括號。

再經過ps命令咱們再看看:

能夠看出,兩個線程的PID竟然相同。

這就是線程的ID特性,每個線程對外而言都表達其父進程的ID。換句話說,一個進程建立的全部子線程,都是以同一進程身份(即父進程)運行,而其內部依然是併發運行。

ps命令的H參數能夠看到子線程,而且其顯示效果上與子進程並沒有太多區別,但,若用kill命令終止其中任何一個子線程,則整個進程的所有線程(包括父線程/父進程)都將終結。這就是與多進程程序不一樣的地方,若是是用kill終止一個子進程,則其餘進程不受任何影響。從這點來講,多線程程序的獨立性就差一些,若是某個線程資源訪問失誤(訪問空指針)致使SIGSEGV信號觸發的話,牽連甚廣。

好了,上面說了線程的具備進程特性的PID,下面說說線程特有的TID。

每個線程除了擁有被調度控制用的PID外,還有一個pthread_t類型的TID,該ID實際是一個long類型的數據區,存儲着每一個線程的內部標識ID。這個TID只在同一進程空間的內有意義,離開進程空間該ID沒有任何做用。

換言之,TID是父進程用來控制子線程時採用的線程標識。

多線程程序的在線調試也比多進程程序簡單,因其共享同一進程空間。

可經過info thread查看各線程的編號,並經過thread +編號 直接切換到該線程。

(bt命令能夠查看線程的棧數據。)

事實上,從線程的棧能夠看出,線程是clone產生的,以下圖:



4.線程控制

線程控制通常指父線程對子線程的控制,命令行實際不能對子線程直接進行終止操做(會波及整個進程)。因此線程的控制技術此處就再也不贅述,提供幾個關鍵詞供讀者查閱學習:分離屬性,線程取消點,線程回收,線程信號

 

5.線程同步

線程大量被採用的一大緣由是其精巧高效,運行調度很方便。那麼,若多個線程間有數據互斥操縱的場合下,其同步技術是否是同樣優越於進程呢?

答案是確定的。

線程的同步技術大致有這樣幾種(注:線程間獨有,進程間不可採用):

互斥量(即線程鎖),讀寫鎖,自旋鎖,條件變量

線程鎖只用於同一進程空間內的所有子線程中,跨越進程的線程間通常不採用線程鎖來同步。咱們知道,進程間的同步技術有PV操做(信號量技術),那線程鎖是否是就比信號量快呢?

答案仍是確定的。

信號量是一種特殊系統資源,它是全系統可見的。所以要實現互斥,在進程P、V操做時就必須實現原子操做。對於內核而言,原子操做自己是一種負擔較重的處理,而且原子操做會下降內核的併發性能。於是,P、V操做都須要內核提供操做方法,即系統調用。用戶程序若頻繁的採用系統調用,則會下降程序性能,還會增長內核上下文切換開銷,總的來講,既損失了自身效率,還加劇了內核負擔。

與之相比,線程鎖則優越許多。由於線程鎖的做用空間很小,只限於單個進程內,於是線程鎖的實現就進化到了全局變量。即,線程鎖就是一個全局控制變量,這樣在對線程鎖進行操做的時候就無需內核幫助,也避免訪問內核數據區提升了性能。再者,做用域變小了,參與資源競爭的個體(進程/線程)就減小了,使得多任務程序能夠快速獲得互斥資源。

總的一句話,線程的同步技術快於進程。


6.線程應用

說了這麼多的線程優點,那是否是全部的多任務環境均可以採用線程來替代進程設計呢?

答案是不能夠。

進程和線程,兩者各有優點,應在不一樣的場景採用不一樣方式。

線程雖然運行時無需過多資源,但在須要獨立控制的任務裏,它又變成了缺陷。好比當今流行的nginx服務,它採用的是進程池技術,之因此是進程而不是線程,就是考慮了任務獨立性的要求。對於數據獨立性要求低,或者說是專門處理共享數據,只是爲了提升代碼併發執行效率的話,採用線程就是優選之舉了。

限於篇幅,本文就此停筆,宏觀上的線程特性請參考:

http://my.oschina.net/cnyinlinux/blog/367910


<<END>>

相關文章
相關標籤/搜索