Linux操作系統(tǒng)下的多線程編程詳細解析_第1頁
Linux操作系統(tǒng)下的多線程編程詳細解析_第2頁
Linux操作系統(tǒng)下的多線程編程詳細解析_第3頁
Linux操作系統(tǒng)下的多線程編程詳細解析_第4頁
Linux操作系統(tǒng)下的多線程編程詳細解析_第5頁
已閱讀5頁,還剩10頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、Linux操作系統(tǒng)下的多線程編程詳細解析發(fā)表時間:2007-06-07內容來源:站長資訊收集整理作者:佚名線程(thread技術早在60年代就被提出,但真正應用多線程到操作系統(tǒng)中去,是在80年代中期,solaris是這方面的佼佼者。傳統(tǒng)的Unix也支持線程的概念,但是在一個進程(process中只允許有一個線程,這樣多線程就意味著多進程?,F在,多線程技術已經被許多操作系統(tǒng)所支持,包括Windows/NT,當然,也包括Linux。為什么有了進程的概念后,還要再引入線程呢?使用多線程到底有哪些好處?什么的系統(tǒng)應該選用多線程?我們首先必須回答這些問題。使用多線程的理由之一是和進程相比,它是一種非常節(jié)

2、儉的多任務操作方式。我們知道,在Linux系統(tǒng)下,啟動一個新的進程必須分配給它獨立的地址空間,建立眾多的數據表來維護它的代碼段、堆棧段和數據段,這是一種昂貴的多任務工作方式。而運行于一個進程中的多個線程,它們彼此之間使用相同的地址空間,共享大部分數據,啟動一個線程所花費的空間遠遠小于啟動一個進程所花費的空間,而且,線程間彼此切換所需的時間也遠遠小于進程間切換所需要的時間。據統(tǒng)計,總的說來,一個進程的開銷大約是一個線程開銷的30倍左右,當然,在具體的系統(tǒng)上,這個數據可能會有較大的區(qū)別。使用多線程的理由之二是線程間方便的通信機制。對不同進程來說,它們具有獨立的數據空間,要進行數據的傳遞只能通過通信

3、的方式進行,這種方式不僅費時,而且很不方便。線程則不然,由于同一進程下的線程之間共享數據空間,所以一個線程的數據可以直接為其它線程所用,這不僅快捷,而且方便。當然,數據的共享也帶來其他一些問題,有的變量不能同時被兩個線程所修改,有的子程序中聲明為static的數據更有可能給多線程程序帶來災難性的打擊,這些正是編寫多線程程序時最需要注意的地方。除了以上所說的優(yōu)點外,不和進程比較,多線程程序作為一種多任務、并發(fā)的工作方式,當然有以下的優(yōu)點:1 提高應用程序響應。這對圖形界面的程序尤其有意義,當一個操作耗時很長時,整個系統(tǒng)都會等待這個操作,此時程序不會響應鍵盤、鼠標、菜單的操作,而使用多線程技術,將

4、耗時長的操作(time consuming置于一個新的線程,可以避免這種尷尬的情況。 例如線程數目過多了;后者表示第二個參數代表的線程屬性值非法。創(chuàng)建線程成功后,新創(chuàng)建的線程則運行參數三和參數四確定的函數,原來的線程則繼續(xù)運行下一行代碼。函數pthread_join用來等待一個線程的結束。函數原型為:extern int pthread_join _P (pthread_t _th, void *_thread_return;第一個參數為被等待的線程標識符,第二個參數為一個用戶定義的指針,它可以用來存儲被等待線程的返回值。這個函數是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束為

5、止,當函數返回時,被等待線程的資源被收回。一個線程的結束有兩種途徑,一種是象我們上面的例子一樣,函數結束了,調用它的線程也就結束了;另一種方式是通過函數pthread_exit來實現。它的函數原型為:extern void pthread_exit _P (void *_retval _attribute_ (_noreturn_;唯一的參數是函數的返回代碼,只要pthread_join中的第二個參數thread_return不是NULL,這個值將被傳遞給thread_return。最后要說明的是,一個線程不能被多個線程等待,否則第一個接收到信號的線程成功返回,其余調用pthread_join

6、的線程則返回錯誤代碼ESRCH。在這一節(jié)里,我們編寫了一個最簡單的線程,并掌握了最常用的三個函數pthread_create, pthread_join和pthread_exit。下面,我們來了解線程的一些常用屬性以及如何設置這些屬性。修改線程的屬性在上一節(jié)的例子里,我們用pthread_create函數創(chuàng)建了一個線程,在這個線程中,我們使用了默認參數,即將該函數的第二個參數設為NULL。的確,對大多數程序來說,使用默認屬性就夠了,但我們還是有必要來了解一下線程的有關屬性。屬性結構為pthread_attr_t,它同樣在頭文件/usr/include/pthread.h中定義,喜歡追根問底的人

7、可以自己去查看。屬性值不能直接設置,須使用相關函數進行操作,初始化的函數為pthread_attr_init,這個函數必須在pthread_create函數之前調用。屬性對象主要包括是否綁定、是否分離、堆棧地址、堆棧大小、優(yōu)先級。默認的屬性為非綁定、非分離、缺省1M的堆棧、與父進程同樣級別的優(yōu)先級。關于線程的綁定,牽涉到另外一個概念:輕進程(LWP:Light Weight Process。輕進程可以理解為內核線程,它位于用戶層和系統(tǒng)層之間。系統(tǒng)對線程資源的分配、對線程的控制是通過輕進程來實現的,一個輕進程可以控制一個或多個線程。默認狀況下,啟動多少輕進程、哪些輕進程來控制哪些線程是由系統(tǒng)來控

8、制的,這種狀況即稱為非綁定的。綁定狀況下,則顧名思義,即某個線程固定的綁在一個輕進程之上。被綁定的線程具有較高的響應速度,這是因為CPU時間片的調度是面向輕進程的,綁定的線程可以保證在需要的時候它總有一個輕進程可用。通過設置被綁定的輕進程的優(yōu)先級和調度級可以使得綁定的線程滿足諸如實時反應之類的要求。設置線程綁定狀態(tài)的函數為pthread_attr_setscope,它有兩個參數,第一個是指向屬性結構的指針,第二個是綁定類型,它有兩個取值:PTHREAD_SCOPE_SYSTEM(綁定的和PTHREAD_SCOPE_PROCESS(非綁定的。下面的代碼即創(chuàng)建了一個綁定的線程。 pthread_c

9、reate返回。設置一段等待時間,是在多線程編程里常用的方法。但是注意不要使用諸如wait(之類的函數,它們是使整個進程睡眠,并不能解決線程同步的問題。另外一個可能常用的屬性是線程的優(yōu)先級,它存放在結構sched_param中。用函數pthread_attr_getschedparam和函數pthread_attr_setschedparam進行存放,一般說來,我們總是先取優(yōu)先級,對取得的值修改后再存放回去。下面即是一段簡單的例子。線程的數據處理和進程相比,線程的最大優(yōu)點之一是數據的共享性,各個進程共享父進程處沿襲的數據段,可以方便的獲得、修改數據。但這也給多線程編程帶來了許多問題。我們必須當

10、心有多個不同的進程訪問相同的變量。許多函數是不可重入的,即同時不能運行一個函數的多個拷貝(除非使用不同的數據段。在函數中聲明的靜態(tài)變量常常帶來問題,函數的返回值也會有問題。因為如果返回的是函數內部靜態(tài)聲明的空間的地址,則在一個線程調用該函數得到地址后使用該地址指向的數據時,別的線程可能調用此函數并修改了這一段數據。在進程中共享的變量必須用關鍵字volatile來定義,這是為了防止編譯器在優(yōu)化時(如gcc中使用-OX參數改變它們的使用方式。為了保護變量,我們必須使用信號量、互斥等方法來保證我們對變量的正確使用。下面,我們就逐步介紹處理線程數據時的有關知識。1、線程數據在單線程的程序里,有兩種基本

11、的數據:全局變量和局部變量。但在多線程程序里,還有第三種數據類型:線程數據(TSD: Thread-Specific Data。它和全局變量很象,在線程內部,各個函數可以象使用全局變量一樣調用它,但它對線程外部的其它線程是不可見的。這種數據的必要性是顯而易見的。例如我們常見的變量errno,它返回標準的出錯信息。它顯然不能是一個局部變量,幾乎每個函數都應該可以調用它;但它又不能是一個全局變量,否則在A線程里輸出的很可能是B線程的出錯信息。要實現諸如此類的變量,我們就必須使用線程數據。我們?yōu)槊總€線程數據創(chuàng)建一個鍵,它和這個鍵相關聯(lián),在各個線程里,都使用這個鍵來指代線程數據,但在不同的線程里,這個

12、鍵代表的數據是不同的,在同一個線程里,它代表同樣的數據內容。和線程數據相關的函數主要有4個:創(chuàng)建一個鍵;為一個鍵指定線程數據;從一個鍵讀取線程數據;刪除鍵。創(chuàng)建鍵的函數原型為: 第一個參數為指向一個鍵值的指針,第二個參數指明了一個destructor函數,如果這個參數不為空,那么當每個線程結束時,系統(tǒng)將調用這個函數來釋放綁定在這個鍵上的內存塊。這個函數常和函數pthread_once (pthread_once_t*once_control, void (*initroutine (void一起使用,為了讓這個鍵只被創(chuàng)建一次。函數pthread_once聲明一個初始化函數,第一次調用pthre

13、ad_once時它執(zhí)行這個函數,以后的調用將被它忽略。在下面的例子中,我們創(chuàng)建一個鍵,并將它和某個數據相關聯(lián)。我們要定義一個函數createWindow,這個函數定義一個圖形窗口(數據類型為Fl_Window *,這是圖形界面開發(fā)工具FLTK中的數據類型。由于各個線程都會調用這個函數,所以我們使用線程數據。 這樣,在不同的線程中調用函數createMyWin,都可以得到在線程內部均可見的窗口變量,這個變量通過函數pthread_getspecific得到。在上面的例子中,我們已經使用了函數pthread_setspecific來將線程數據和一個鍵綁定在一起。這兩個函數的原型如下: 這兩個函數的

14、參數意義和使用方法是顯而易見的。要注意的是,用pthread_setspecific為一個鍵指定新的線程數據時,必須自己釋放原有的線程數據以回收空間。這個過程函數pthread_key_delete用來刪除一個鍵,這個鍵占用的內存將被釋放,但同樣要注意的是,它只釋放鍵占用的內存,并不釋放該鍵關聯(lián)的線程數據所占用的內存資源,而且它也不會觸發(fā)函數pthread_key_create中定義的destructor函數。線程數據的釋放必須在釋放鍵之前完成。2、互斥鎖互斥鎖用來保證一段時間內只有一個線程在執(zhí)行一段代碼。必要性顯而易見:假設各個線程向同一個文件順序寫入數據,最后得到的結果一定是災難性的。 這

15、里聲明了互斥鎖變量mutex,結構pthread_mutex_t為不公開的數據類型,其中包含一個系統(tǒng)分配的屬性對象。函數pthread_mutex_init用來生成一個互斥鎖。NULL 參數表明使用默認屬性。如果需要聲明特定屬性的互斥鎖,須調用函數pthread_mutexattr_init。函數pthread_mutexattr_setpshared和函數pthread_mutexattr_settype用來設置互斥鎖屬性。前一個函數設置屬性pshared,它有兩個取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用來不同進程中的線程同步

16、,后者用于同步本進程的不同線程。在上面的例子中,我們使用的是默認屬性PTHREAD_PROCESS_ PRIVATE。后者用來設置互斥鎖類型,可選的類型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它們分別定義了不同的上所、解鎖機制,一般情況下,選用最后一個默認屬性。pthread_mutex_lock聲明開始用互斥鎖上鎖,此后的代碼直至調用pthread_mutex_unlock為止,均被上鎖,即同一時間只能被一個線程調用執(zhí)行。當一個線程執(zhí)行到pthr

17、ead_mutex_lock處時,如果該鎖此時被另一個線程使用,那此線程被阻塞,即程序將等待到另一個線程釋放此互斥鎖。在上面的例子中,我們使用了pthread_delay_np函數,讓線程睡眠一段時間,就是為了防止一個線程始終占據此函數。上面的例子非常簡單,就不再介紹了,需要提出的是在使用互斥鎖的過程中很有可能會出現死鎖:兩個線程試圖同時占用兩個資源,并按不同的次序鎖定相應的互斥鎖,例如兩個線程都需要鎖定互斥鎖1和互斥鎖2,a線程先鎖定互斥鎖1,b線程先鎖定互斥鎖2,這時就出現了死鎖。此時我們可以使用函數pthread_mutex_trylock,它是函數pthread_mutex_lock的

18、非阻塞版本,當它發(fā)現死鎖不可避免時,它會返回相應的信息,程序員可以針對死鎖做出相應的處理。另外不同的互斥鎖類型對死鎖的處理不一樣,但最主要的還是要程序員自己在程序設計注意這一點。3、條件變量前一節(jié)中我們講述了如何使用互斥鎖來實現線程間數據的共享和通信,互斥鎖一個明顯的缺點是它只有兩種狀態(tài):鎖定和非鎖定。而條件變量通過允許線程阻塞和等待另一個線程發(fā)送信號的方法彌補了互斥鎖的不足,它常和互斥鎖一起使用。使用時,條件變量被用來阻塞一個線程,當條件不滿足時,線程往往解開相應的互斥鎖并等待條件發(fā)生變化。一旦其它的某個線程改變了條件變量,它將通知相應的條件變量喚醒一個或多個正被此條件變量阻塞的線程。這些線

19、程將重新鎖定互斥鎖并重新測試條件是否滿足。一般說來,條件變量被用來進行線承間的同步。條件變量的結構為pthread_cond_t,函數pthread_cond_init(被用來初始化一個條件變量。它的原型為:extern int pthread_cond_init _P (pthread_cond_t *_cond,_const pthread_condattr_t *_cond_attr;其中cond是一個指向結構pthread_cond_t的指針,cond_attr是一個指向結構pthread_condattr_t的指針。結構pthread_condattr_t是條件變量的屬性結構,和互斥

20、鎖一樣我們可以用它來設置條件變量是進程內可用還是進程間可用,默認值是PTHREAD_ PROCESS_PRIVATE,即此條件變量被同一進程內的各個線程使用。注意初始化條件變量只有未被使用時才能重新初始化或被釋放。釋放一個條件變量的函數為pthread_cond_ destroy(pthread_cond_t cond。函數pthread_cond_wait(使線程阻塞在一個條件變量上。它的函數原型為:extern int pthread_cond_wait _P (pthread_cond_t *_cond,pthread_mutex_t *_mutex;線程解開mutex指向的鎖并被條件變

21、量cond阻塞。線程可以被函數pthread_cond_signal和函數pthread_cond_broadcast喚醒,但是要注意的是,條件變量只是起阻塞和喚醒線程的作用,具體的判斷條件還需用戶給出,例如一個變量是否為0等等,這一點我們從后面的例子中可以看到。線程被喚醒后,它將重新檢查判斷條件是否滿足,如果還不滿足,一般說來線程應該仍阻塞在這里,被等待被下一次喚醒。這個過程一般用while語句實現。另一個用來阻塞線程的函數是pthread_cond_timedwait(,它的原型為: extern int pthread_cond_timedwait _P (pthread_cond_t

22、*_cond,pthread_mutex_t *_mutex, _const struct timespec *_abstime;它比函數 pthread_cond_wait()多了一個時間參數,經歷 abstime 段時間后,即使 條件變量不滿足,阻塞也被解除。 函數 pthread_cond_signal()的原型為: extern int pthread_cond_signal _P (pthread_cond_t *_cond; 它用來釋放被阻塞在條件變量 cond 上的一個線程。多個線程阻塞在此條件變量上時, 哪一個線程被喚醒是由線程的調度策略所決定的。 要注意的是, 必須用保護條件

23、變量的互斥 鎖來保護這個函數,否則條件滿足信號又可能在測試條件和調用 pthread_cond_wait 函 數之間被發(fā)出,從而造成無限制的等待。下面是使用函數 pthread_cond_wait()和函數 pthread_cond_signal()的一個簡單的例子。 pthread_mutex_t count_lock; pthread_cond_t count_nonzero; unsigned count; decrement_count while(count=0 pthread_cond_wait( &count_nonzero, &count_lock; count=count -

24、1; pthread_mutex_unlock (&count_lock; increment_count( pthread_mutex_lock(&count_lock; if(count=0 pthread_cond_signal(&count_nonzero; count=count+1; pthread_mutex_unlock(&count_lock; count 值為 0 時,decrement 函數在 pthread_cond_wait 處被阻塞,并打開互斥鎖 count_lock。此時,當調用到函數 increment_count 時,pthread_cond_signal()

25、函 數改變條件變量,告知 decrement_count()停止阻塞。讀者可以試著讓兩個線程分別運 行這兩個函數,看看會出現什么樣的結果。 函數 pthread_cond_broadcast(pthread_cond_t *cond)用來喚醒所有被阻塞在 條件變量 cond 上的線程。這些線程被喚醒后將再次競爭相應的互斥鎖,所以必須小心使用 這個函數。 ( pthread_mutex_lock (&count_lock; 4、信號量 信號量本質上是一個非負的整數計數器, 它被用來控制對公共資源的訪問。 當公共資源 增加時,調用函數 sem_post()增加信號量。只有當信號量值大于時,才能使用

26、公共 資源,使用后,函數 sem_wait()減少信號量。函數 sem_trywait()和函數 pthread_ mutex_trylock()起同樣的作用,它是函數 sem_wait()的非阻塞版本。下面我們逐 個介紹和信號量有關的一些函數,它們都在頭文件/usr/include/semaphore.h 中定義。 信號量的數據類型為結構 sem_t,它本質上是一個長整型的數。函數 sem_init()用 來初始化一個信號量。它的原型為: extern int sem_init _P (sem_t *_sem, int _pshared, unsigned int _value; sem 為指向信號量結構的一個指針;pshared 不為時此信號量在進程間共享,否則 只能為當前進程的所有線程共享;value 給出了信號量的初始值。 函數 sem_pos

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論