Linux系統下的多線程編程入門【上】

來源:互聯網
上載者:User

引言

  線程(thread)技術早在60年代就被提出,但真正應用多線程到作業系統中去,是在80年代中期,solaris是這方面的佼佼者。傳統的Unix也支援線程的概念,但是在一個進程(process)中只允許有一個線程,這樣多線程就意味著多進程。現在,多線程技術已經被許多作業系統所支援,包括Windows/NT,當然,也包括Linux。

  為什麼有了進程的概念後,還要再引入線程呢?使用多線程到底有哪些好處?什麼的系統應該選用多線程?我們首先必須回答這些問題。

  使用多線程的理由之一是和進程相比,它是一種非常"節儉"的多任務操作方式。我們知道,在Linux系統下,啟動一個新的進程必須分配給它獨立的地址空間,建立眾多的資料表來維護它的程式碼片段、堆棧段和資料區段,這是一種"昂貴"的多任務工作方式。而運行於一個進程中的多個線程,它們彼此之間使用相同的地址空間,共用大部分資料,啟動一個線程所花費的空間遠遠小於啟動一個進程所花費的空間,而且,線程間彼此切換所需的時間也遠遠小於進程間切換所需要的時間。據統計,總的說來桓黿痰目笤際且桓魷叱炭?0倍左右,當然,在具體的系統上,這個資料可能會有較大的區別。

  使用多線程的理由之二是線程間方便的通訊機制。對不同進程來說,它們具有獨立的資料空間,要進行資料的傳遞只能通過通訊的方式進行,這種方式不僅費時,而且很不方便。線程則不然,由於同一進程下的線程之間共用資料空間,所以一個線程的資料可以直接為其它線程所用,這不僅快捷,而且方便。當然,資料的共用也帶來其他一些問題,有的變數不能同時被兩個線程所修改,有的子程式中聲明為static的資料更有可能給多線程程式帶來災難性的打擊,這些正是編寫多線程程式時最需要注意的地方。

  除了以上所說的優點外,不和進程比較,多線程程式作為一種多任務、並發的工作方式,當然有以下的優點:

  1) 提高應用程式響應。這對圖形介面的程式尤其有意義,當一個操作耗時很長時,整個系統都會等待這個操作,此時程式不會響應鍵盤、滑鼠、菜單的操作,而使用多線程技術,將耗時間長度的操作(time consuming)置於一個新的線程,可以避免這種尷尬的情況。

  2) 使多CPU系統更加有效。作業系統會保證當線程數不大於CPU數目時,不同的線程運行於不同的CPU上。

  3) 改善程式結構。一個既長又複雜的進程可以考慮分為多個線程,成為幾個獨立或半獨立的運行部分,這樣的程式會利於理解和修改。

  下面我們先來嘗試編寫一個簡單的多線程程式。

  簡單的多線程編程

  Linux系統下的多線程遵循POSIX線程介面,稱為pthread。編寫Linux下的多線程程式,需要使用標頭檔pthread.h,串連時需要使用庫libpthread.a。順便說一下,Linux下pthread的實現是通過系統調用clone()來實現的。clone()是Linux所特有的系統調用,它的使用方式類似fork,關於clone()的詳細情況,有興趣的讀者可以去查看有關文檔說明。下面我們展示一個最簡單的多線程程式example1.c。
/* example.c*/<br />#include <stdio.h><br />#include <pthread.h><br />void thread(void)<br />{<br /> int i;<br /> for(i=0;i<3;i++)<br />  printf("This is a pthread.n");<br />}</p><p>int main(void)<br />{<br /> pthread_t id;<br /> int i,ret;<br /> ret=pthread_create(&id,NULL,(void *) thread,NULL);<br /> if(ret!=0){<br />  printf ("Create pthread error!n");<br />  exit (1);<br /> }<br /> for(i=0;i<3;i++)<br />  printf("This is the main process.n");<br /> pthread_join(id,NULL);<br /> return (0);<br />}

我們編譯此程式:
gcc example1.c -lpthread -o example1

運行example1,我們得到如下結果:

This is the main process.<br />This is a pthread.<br />This is the main process.<br />This is the main process.<br />This is a pthread.<br />This is a pthread.

再次運行,我們可能得到如下結果:

This is a pthread.<br />This is the main process.<br />This is a pthread.<br />This is the main process.<br />This is a pthread.<br />This is the main process.

      前後兩次結果不一樣,這是兩個線程爭奪CPU資源的結果。上面的樣本中,我們使用到了兩個函數,pthread_create和pthread_join,並聲明了一個pthread_t型的變數。

  pthread_t在標頭檔/usr/include/bits/pthreadtypes.h中定義:

  typedef unsigned long int pthread_t;

  它是一個線程的標識符。函數pthread_create用來建立一個線程,它的原型為:

extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,void *(*__start_routine) (void *), void *__arg));

  第一個參數為指向線程標識符的指標,第二個參數用來設定線程屬性,第三個參數是線程運行函數的起始地址,最後一個參數是運行函數的參數。這裡,我們的函數thread不需要參數,所以最後一個參數設為空白指標。第二個參數我們也設為空白指標,這樣將產生預設屬性的線程。對線程屬性的設定和修改我們將在下一節闡述。當建立線程成功時,函數返回0,若不為0則說明建立線程失敗,常見的錯誤傳回碼為EAGAIN和EINVAL。前者表示系統限制建立新的線程,例如線程數目過多了;後者表示第二個參數代表的線程屬性值非法。建立線程成功後,新建立的線程則運行參數三和參數四確定的函數,原來的線程則繼續運行下一行代碼。

  函數pthread_join用來等待一個線程的結束。函數原型為:

  extern int pthread_join __P ((pthread_t __th, void **__thread_return));

  第一個參數為被等待的線程標識符,第二個參數為一個使用者定義的指標,它可以用來儲存被等待線程的傳回值。這個函數是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束為止,當函數返回時,被等待線程的資源被收回。一個線程的結束有兩種途徑,一種是象我們上面的例子一樣,函數結束了,調用它的線程也就結束了;另一種方式是通過函數pthread_exit來實現。它的函數原型為:

  extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));

  唯一的參數是函數的傳回碼,只要pthread_join中的第二個參數thread_return不是NULL,這個值將被傳遞給thread_return。最後要說明的是,一個線程不能被多個線程等待,否則第一個接收到訊號的線程成功返回,其餘調用pthread_join的線程則返回錯誤碼ESRCH。

  在這一節裡,我們編寫了一個最簡單的線程,並掌握了最常用的三個函數pthread_create,pthread_join和pthread_exit。下面,我們來瞭解線程的一些常用屬性以及如何設定這些屬性。

  修改線程的屬性

  在上一節的例子裡,我們用pthread_create函數建立了一個線程,在這個線程中,我們使用了預設參數,即將該函數的第二個參數設為NULL。的確,對大多數程式來說,使用預設屬性就夠了,但我們還是有必要來瞭解一下線程的有關屬性。

  屬性結構為pthread_attr_t,它同樣在標頭檔/usr/include/pthread.h中定義,喜歡追根問底的人可以自己去查看。屬性值不能直接設定,須使用相關函數進行操作,初始化的函數為pthread_attr_init,這個函數必須在pthread_create函數之前調用。屬性對象主要包括是否綁定、是否分離、堆棧地址、堆棧大小、優先順序。預設的屬性為非綁定、非分離、預設1M的堆棧、與父進程同樣層級的優先順序。

  關於線程的綁定,牽涉到另外一個概念:輕進程(LWP:Light Weight Process)。輕進程可以理解為核心線程,它位於使用者層和系統層之間。系統對線程資源的分配、對線程的控制是通過輕進程來實現的,一個輕進程可以控制一個或多個線程。預設狀況下,啟動多少輕進程、哪些輕進程來控制哪些線程是由系統來控制的,這種狀況即稱為非綁定的。綁定狀況下,則顧名思義,即某個線程固定的"綁"在一個輕進程之上。被綁定的線程具有較高的響應速度,這是因為CPU時間片的調度是面向輕進程的,綁定的線程可以保證在需要的時候它總有一個輕進程可用。通過設定被綁定的輕進程的優先順序和調度級可以使得綁定的線程滿足諸如即時反應之類的要求。

  設定線程綁定狀態的函數為pthread_attr_setscope,它有兩個參數,第一個是指向屬性結構的指標,第二個是綁定類型,它有兩個取值:PTHREAD_SCOPE_SYSTEM(綁定的)和PTHREAD_SCOPE_PROCESS(非綁定的)。下面的代碼即建立了一個綁定的線程。

#include <pthread.h><br />pthread_attr_t attr;<br />pthread_t tid;</p><p>/*初始化屬性值,均設為預設值*/<br />pthread_attr_init(&attr);<br />pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);</p><p>pthread_create(&tid, &attr, (void *) my_function, NULL);

線程的分離狀態決定一個線程以什麼樣的方式來終止自己。在上面的例子中,我們採用了線程的預設屬性,即為非分離狀態,這種情況下,原有的線程等待建立的線程結束。只有當pthread_join()函數返回時,建立的線程才算終止,才能釋放自己佔用的系統資源。而分離線程不是這樣子的,它沒有被其他的線程所等待,自己運行結束了,線程也就終止了,馬上釋放系統資源。程式員應該根據自己的需要,選擇適當的分離狀態。設定線程分離狀態的函數為pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二個參數可選為PTHREAD_CREATE_DETACHED(分離線程)和 PTHREAD _CREATE_JOINABLE(非分離線程)。這裡要注意的一點是,如果設定一個線程為分離線程,而這個線程運行又非常快,它很可能在pthread_create函數返回之前就終止了,它終止以後就可能將線程號和系統資源移交給其他的線程使用,這樣調用pthread_create的線程就得到了錯誤的線程號。要避免這種情況可以採取一定的同步措施,最簡單的方法之一是可以在被建立的線程裡調用pthread_cond_timewait函數,讓這個線程等待一會兒,留出足夠的時間讓函數pthread_create返回。設定一段等待時間,是在多線程編程裡常用的方法。但是注意不要使用諸如wait()之類的函數,它們是使整個進程睡眠,並不能解決線程同步的問題。

  另外一個可能常用的屬性是線程的優先順序,它存放在結構sched_param中。用函數pthread_attr_getschedparam和函數pthread_attr_setschedparam進行存放,一般說來,我們總是先取優先順序,對取得的值修改後再存放回去。下面即是一段簡單的例子。

#include <pthread.h><br />#include <sched.h><br />pthread_attr_t attr;<br />pthread_t tid;<br />sched_param param;<br />int newprio=20;</p><p>pthread_attr_init(&attr);<br />pthread_attr_getschedparam(&attr, ¶m);<br />param.sched_priority=newprio;<br />pthread_attr_setschedparam(&attr, ¶m);<br />pthread_create(&tid, &attr, (void *)myfunction, myarg);

 

轉自天極網路:http://dev.yesky.com/331/2276331_1.shtml

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.