linux下多進程與多線程編程

(一) 理解Linux下進程的結構 linux

   Linux下一個進程在內存裏有三部份的數據,就是「數據段」,「堆棧段」和「代碼段」,其實學過彙編語言的人必定知道,通常的CPUI386,都有上述三種段寄存器,以方便操做系統的運行。「代碼段」,顧名思義,就是存放了程序代碼的數據,假如機器中有數個進程運行相同的一個程序,那麼它們就可使用同一個代碼段。 程序員


堆棧段存放的就是子程序的返回地址、子程序的參數以及程序的局部變量。而數據段則存放程序的全局變量,常數以及動態數據分配的數據空間(好比用malloc之類的函數取得的空間)。這其中有許多細節問題,這裏限於篇幅就很少介紹了。系統若是同時運行數個相同的程序,它們之間就不能使用同一個堆棧段和數據段。 編程

(二) 如何使用fork 數組


Linux下產生新的進程的系統調用就是fork函數,這個函數名是英文中「分叉」的意思。爲何取這個名字呢?由於一個進程在運行中,若是使用了fork,就產生了另外一個進程,因而進程就「分叉」了,因此這個名字取得很形象。下面就看看如何具體使用fork,這段程序演示了使用fork的基本框架: 安全

void main(){ 數據結構

int i; 多線程

if ( fork() == 0 ) { 併發

/* 子進程程序 */ app

for ( i = 1; i " ); 框架

fgets( command, 256, stdin );

command[strlen(command)-1] = 0;

if ( fork() == 0 ) {

/* 子進程執行此命令 */

execlp( command, command );

/* 若是exec函數返回,代表沒有正常執行命令,打印錯誤信息*/

perror( command );

exit( errorno );

}

else {

/* 父進程, 等待子進程結束,並打印子進程的返回值 */

wait ( &rtn );

printf( " child process return %d\n",. rtn );

}

}

}


此程序從終端讀入命令並執行之,執行完成後,父進程繼續等待從終端讀入命令。熟悉DOSWINDOWS系統調用的朋友必定知道DOS/WINDOWS也有exec類函數,其使用方法是相似的,但DOS/WINDOWS還有spawn類函數,由於DOS是單任務的系統,它只能將「父進程」駐留在機器內再執行「子進程」,這就是spawn類的函數。WIN32已是多任務的系統了,但還保留了spawn類函數,WIN32中實現spawn函數的方法同前述UNIX中的方法差很少,開設子進程後父進程等待子進程結束後才繼續運行。UNIX在其一開始就是多任務的系統,因此從核心角度上講不須要spawn類函數。另外,有一個更簡單的執行其它程序的函數system,它是一個較高層的函數,實際上至關於在SHELL環境下執行一條命令,而exec類函數則是低層的系統調用。

 

(四) Linux的進程與Win32的進程/線程有何區別

   熟悉WIN32編程的人必定知道,WIN32的進程管理方式與UNIX上有着很大區別,在UNIX裏,只有進程的概念,但在WIN32裏卻還有一個「線程」的概念,那麼

UNIXWIN32在這裏究竟有着什麼區別呢?


UNIX裏的fork是七十年代UNIX早期的開發者通過長期在理論和實踐上的艱苦探索後取得的成果,一方面,它使操做系統在進程管理上付出了最小的代價,另外一方面,又爲程序員提供了一個簡潔明瞭的多進程方法。

   WIN32裏的進程/線程是繼承自OS/2的。在WIN32裏,「進程」是指一個程序,而「線程」是一個「進程」裏的一個執行「線索」。從核心上講,WIN32的多進程與

UNIX並沒有多大的區別,在WIN32裏的線程才至關於UNIX的進程,是一個實際正在執行的代碼。可是,WIN32裏同一個進程裏各個線程之間是共享數據段的。這纔是與UNIX的進程最大的不一樣。

   下面這段程序顯示了WIN32下一個進程如何啓動一個線程:(請注意,這是個終端方式程序,沒有圖形界面)

int g;

DWORD WINAPI ChildProcess( LPVOID lpParameter ){

int i;

for ( i = 1; i

 

pthread 解讀

 

 

Posix線程編程指南(1)

內容:

1、 線程建立

2、線程取消

線程建立與取消

楊沙洲(pubb@163.net)

2001 年 10 

這是一個關於Posix線程編程的專欄。做者在闡明概念的基礎上,將向您詳細講述Posix線程庫API。本文是第一篇將向您講述線程的建立與取消。

1、 線程建立

1線程與進程

相對進程而言,線程是一個更加接近於執行體的概念,它能夠與同進程中的其餘線程共享數據,但擁有本身的棧空間,擁有獨立的執行序列。在串行程序基礎上引入

線程和進程是爲了提升程序的併發度,從而提升程序運行效率和響應時間。

線程和進程在使用上各有優缺點:線程執行開銷小,但不利於資源的管理和保護;而進程正相反。同時,線程適合於在SMP機器上運行,而進程則能夠跨機器遷移。

1建立線程

POSIX經過pthread_create()函數建立線程,API定義以下:

int pthread_create(pthread_t * thread, pthread_attr_t * attr,

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

fork()調用建立一個進程的方法不一樣,pthread_create()建立的線程並不具有與主線程(即調用pthread_create()的線程)一樣的執行序列,而是使其運行start_routine(arg)函數。thread返回建立的線程ID,而attr是建立線程時設置的線程屬性(見下)。pthread_create()的返回值表示線程建立是否成功。儘管argvoid*類型的變量,但它一樣能夠做爲任意類型的參數傳給start_routine()函數;同時,start_routine()能夠返回一個void*類型的返回值,而這個返回值也能夠是其餘類型,並由pthread_join()獲取。

1線程建立屬性

pthread_create()中的attr參數是一個結構指針,結構中的元素分別對應着新線程的運行屬性,主要包括如下幾項:

__detachstate,表示新線程是否與進程中其餘線程脫離同步,若是置位則新線程不能用pthread_join()來同步,且在退出時自行釋放所佔用的資源。缺省爲PTHREAD_CREATE_JOINABLE狀態。這個屬性也能夠在線程建立並運行之後用pthread_detach()來設置,而一旦設置爲PTHREAD_CREATE_DETACH狀態(不管是建立時設置仍是運行時設置)則不能再恢復到PTHREAD_CREATE_JOINABLE狀態。

__schedpolicy,表示新線程的調度策略,主要包括SCHED_OTHER(正常、非實時)、SCHED_RR(實時、輪轉法)和SCHED_FIFO(實時、先入先出)三種,缺省爲SCHED_OTHER,後兩種調度策略僅對超級用戶有效。運行時能夠用過pthread_setschedparam()來改變。

__schedparam,一個struct sched_param結構,目前僅有一個sched_priority整型變量表示線程的運行優先級。這個參數僅當調度策略爲實時(即SCHED_RRSCHED_FIFO)時纔有效,並能夠在運行時經過pthread_setschedparam()函數來改變,缺省爲0

__inheritsched,有兩種值可供選擇:PTHREAD_EXPLICIT_SCHEDPTHREAD_INHERIT_SCHED,前者表示新線程使用顯式指定調度策略和調度參數(即attr中的值),

然後者表示繼承調用者線程的值。缺省爲PTHREAD_EXPLICIT_SCHED

__scope,表示線程間競爭CPU的範圍,也就是說線程優先級的有效範圍。POSIX的標準中定義了兩個值:PTHREAD_SCOPE_SYSTEMPTHREAD_SCOPE_PROCESS,前者表示與系統中全部線程一塊兒競爭CPU時間,後者表示僅與同進程中的線程競爭CPU。目前LinuxThreads僅實現了PTHREAD_SCOPE_SYSTEM一值。

pthread_attr_t結構中還有一些值,但不使用pthread_create()來設置。

爲了設置這些屬性,POSIX定義了一系列屬性設置函數,包括pthread_attr_init()pthread_attr_destroy()和與各個屬性相關的pthread_attr_get---/pthread_at

tr_set---函數。

1線程建立的Linux實現

咱們知道,Linux的線程實現是在覈外進行的,核內提供的是建立進程的接口do_fork()。內核提供了兩個系統調用__clone()fork(),最終都用不一樣的參數調用do_fork()核內API。固然,要想實現線程,沒有核心對多進程(實際上是輕量級進程)共享數據段的支持是不行的,所以,do_fork()提供了不少參數,包括CLONE_VM(共享內存空間)、CLONE_FS(共享文件系統信息)、CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信號句柄表)和CLONE_PID(共享進程ID,僅對核內進程,即0號進程有效)。當使用fork系統調用時,內核調用do_fork()不使用任何共享屬性,進程擁有獨立的運行環境,而使用pthread_create()來建立線程時,則最終設置了全部這些屬性來調用__clone(),而這些參數又所有傳給核內的do_fork(),從而建立的"進程"擁有共享的運行環境,只有棧是獨立的,由__clone()傳入。

Linux線程在覈內是以輕量級進程的形式存在的,擁有獨立的進程表項,而全部的建立、同步、刪除等操做都在覈外pthread庫中進行。pthread庫使用一個管理線程(__pthread_manager(),每一個進程獨立且惟一)來管理線程的建立和終止,爲線程分配線程ID,發送線程相關的信號(好比Cancel),而主線程(pthread_create())的調用者則經過管道將請求信息傳給管理線程。

2、線程取消

2線程取消的定義

通常狀況下,線程在其主體函數退出的時候會自動終止,但同時也能夠由於接收到另外一個線程發來的終止(取消)請求而強制終止。

2線程取消的語義

線程取消的方法是向目標線程發Cancel信號,但如何處理Cancel信號則由目標線程本身決定,或者忽略、或者當即終止、或者繼續運行至Cancelation-point(取消點),由不一樣的Cancelation狀態決定。

線程接收到CANCEL信號的缺省處理(即pthread_create()建立線程的缺省狀態)是繼續運行至取消點,也就是說設置一個CANCELED狀態,線程繼續運行,只有運行至Cancelation-point的時候纔會退出。

2取消點

根據POSIX標準,pthread_join()pthread_testcancel()pthread_cond_wait()pthread_cond_timedwait()sem_wait()sigwait()等函數以及read()write()等會引發阻塞的系統調用都是Cancelation-point,而其餘pthread函數都不會引發Cancelation動做。可是pthread_cancel的手冊頁聲稱,因爲LinuxThread庫與C庫結合得很差,於是目前C庫函數都不是Cancelation-point;但CANCEL信號會使線程從阻塞的系統調用中退出,並置EINTR錯誤碼,所以能夠在須要做爲Cancelation-point的系統調用先後調用pthread_testcancel(),從而達到POSIX標準所要求的目標,即以下代碼段:

pthread_testcancel();

retcode = read(fd, buffer, length);

pthread_testcancel();

2程序設計方面的考慮

若是線程處於無限循環中,且循環體內沒有執行至取消點的必然路徑,則線程沒法由外部其餘線程的取消請求而終止。所以在這樣的循環體的必經路徑上應該加入pthread_testcancel()調用。

2與線程取消相關的pthread函數

int pthread_cancel(pthread_t thread)

發送終止信號給thread線程,若是成功則返回0,不然爲非0值。發送成功並不意味着thread會終止。

int pthread_setcancelstate(int state, int *oldstate)

設置本線程對Cancel信號的反應,state有兩種值:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE,分別表示收到信號後設爲CANCLED狀態和忽略CANCEL信號繼續運行;old_state若是不爲NULL則存入原來的Cancel狀態以便恢復。

int pthread_setcanceltype(int type, int *oldtype)

設置本線程取消動做的執行時機,type由兩種取值:PTHREAD_CANCEL_DEFFEREDPTHREAD_CANCEL_ASYCHRONOUS,僅當Cancel狀態爲Enable時有效,分別表示收到信號後繼續運行至下一個取消點再退出和當即執行取消動做(退出);oldtype若是不爲NULL則存入運來的取消動做類型值。

void pthread_testcancel(void)

檢查本線程是否處於Canceld狀態,若是是,則進行取消動做,不然直接返回。

 

posix線程編程指南(2)

內容:

一. 概念及做用

二. 建立和註銷

三. 訪問

四. 使用範例

關於做者

相關內容:

(1) 線程建立與取消

線程私有數據

楊沙洲(pubb@163.net)

2001 年 10 

這是一個關於Posix線程編程的專欄。做者在闡明概念的基礎上,將向您詳細講述Posix線程庫API。本文是第二篇將向您講述線程的私有數據。

一. 概念及做用

在單線程程序中,咱們常常要用到"全局變量"以實現多個函數間共享數據。在多線程環境下,因爲數據空間是共享的,所以全局變量也爲全部線程所共有。但有時應用程序設計中有必要提供線程私有的全局變量,僅在某個線程中有效,但卻能夠跨多個函數訪問,好比程序可能須要每一個線程維護一個鏈表,而使用相同的函數操做,最簡單的辦法就是使用同名而不一樣變量地址的線程相關數據結構。這樣的數據結構能夠由Posix線程庫維護,稱爲線程私有數據(Thread-specificData,或TSD)。

二. 建立和註銷

Posix定義了兩個API分別用來建立和註銷TSD

int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *))

該函數從TSD池中分配一項,將其值賦給key供之後訪問使用。若是destr_function不爲空,在線程退出(pthread_exit())時將以key所關聯的數據爲參數調用destr_function(),以釋放分配的緩衝區。

不論哪一個線程調用pthread_key_create(),所建立的key都是全部線程可訪問的,但各個線程可根據本身的須要往key中填入不一樣的值,這就至關於提供了一個同名而不一樣值的全局變量。在LinuxThreads的實現中,TSD池用一個結構數組表示:

static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] = { { 0, NULL } };

建立一個TSD就至關於將結構數組中的某一項設置爲"in_use",並將其索引返回給*key,而後設置destructor函數爲destr_function

註銷一個TSD採用以下API

int pthread_key_delete(pthread_key_t key)

這個函數並不檢查當前是否有線程正使用該TSD,也不會調用清理函數(destr_function),而只是將TSD釋放以供下一次調用pthread_key_create()使用。在LinuxThreads中,它還會將與之相關的線程數據項設爲NULL(見"訪問")。

三. 訪問

TSD的讀寫都經過專門的Posix Thread函數進行,其API定義以下:

int pthread_setspecific(pthread_key_t key, const void *pointer)

void * pthread_getspecific(pthread_key_t key)

寫入(pthread_setspecific())時,將pointer的值(不是所指的內容)與key相關聯,而相應的讀出函數則將與key相關聯的數據讀出來。數據類型都設爲void

*,所以能夠指向任何類型的數據。

LinuxThreads中,使用了一個位於線程描述結構(_pthread_descr_struct)中的二維void *指針數組來存放與key關聯的數據,數組大小由如下幾個宏來講明:

#define PTHREAD_KEY_2NDLEVEL_SIZE 32

#define PTHREAD_KEY_1STLEVEL_SIZE \

((PTHREAD_KEYS_MAX + PTHREAD_KEY_2NDLEVEL_SIZE - 1)

/ PTHREAD_KEY_2NDLEVEL_SIZE)

其中在/usr/include/bits/local_lim.h中定義了PTHREAD_KEYS_MAX1024,所以一維數組大小爲32。而具體存放的位置由key值通過如下計算獲得:

idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE

idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE

也就是說,數據存放與一個32×32的稀疏矩陣中。一樣,訪問的時候也由key值通過相似計算獲得數據所在位置索引,再取出其中內容返回。

四. 使用範例

如下這個例子沒有什麼實際意義,只是說明如何使用,以及可以使用這一機制達到存儲線程私有數據的目的。

#include 

#include 

pthread_key_t key;

void echomsg(int t)

{

printf("destructor excuted in thread %d,param=%d\n",pthread_self(),t);

}

void * child1(void *arg)

{

int tid=pthread_self();

printf("thread %d enter\n",tid);

pthread_setspecific(key,(void *)tid);

sleep(2);

printf("thread %d returns %d\n",tid,pthread_getspecific(key));

sleep(5);

}

void * child2(void *arg)

{

int tid=pthread_self();

printf("thread %d enter\n",tid);

pthread_setspecific(key,(void *)tid);

sleep(1);

printf("thread %d returns %d\n",tid,pthread_getspecific(key));

sleep(5);

}

int main(void)

{

int tid1,tid2;

printf("hello\n");

pthread_key_create(&key,echomsg);

pthread_create(&tid1,NULL,child1,NULL);

pthread_create(&tid2,NULL,child2,NULL);

sleep(10);

pthread_key_delete(key);

printf("main thread exit\n");

return 0;

}

給例程建立兩個線程分別設置同一個線程私有數據爲本身的線程ID,爲了檢驗其私有性,程序錯開了兩個線程私有數據的寫入和讀出的時間,從程序運行結果能夠看

出,兩個線程對TSD的修改互不干擾。同時,當線程退出時,清理函數會自動執行,參數爲tid

Posix線程編程指南(3)

內容:

一. 互斥鎖

二. 條件變量

三. 信號燈

四. 異步信號

五. 其餘同步方式 

相關內容:

(1) 線程建立與取消

(2) 線程私有數據

線程同步

這是一個關於Posix線程編程的專欄。做者在闡明概念的基礎上,將向您詳細講述Posix線程庫API。本文是第三篇將向您講述線程同步。

一. 互斥鎖

儘管在Posix Thread中一樣可使用IPC的信號量機制來實現互斥鎖mutex功能,但顯然semphore的功能過於強大了,在Posix

Thread中定義了另一套專門用於線程同步的mutex函數。

1. 建立和銷燬

有兩種方法建立互斥鎖,靜態方式和動態方式。POSIX定義了一個宏PTHREAD_MUTEX_INITIALIZER來靜態初始化互斥鎖,方法以下:

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;

LinuxThreads實現中,pthread_mutex_t是一個結構,而PTHREAD_MUTEX_INITIALIZER則是一個結構常量。

動態方式是採用pthread_mutex_init()函數來初始化互斥鎖,API定義以下:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)

其中mutexattr用於指定互斥鎖屬性(見下),若是爲NULL則使用缺省屬性。

pthread_mutex_destroy()用於註銷一個互斥鎖,API定義以下:

int pthread_mutex_destroy(pthread_mutex_t *mutex)

銷燬一個互斥鎖即意味着釋放它所佔用的資源,且要求鎖當前處於開放狀態。因爲在Linux中,互斥鎖並不佔用任何資源,所以LinuxThreads中的pthread_mutex_des

troy()除了檢查鎖狀態之外(鎖定狀態則返回EBUSY)沒有其餘動做。

2. 互斥鎖屬性

互斥鎖的屬性在建立鎖的時候指定,在LinuxThreads實現中僅有一個鎖類型屬性,不一樣的鎖類型在試圖對一個已經被鎖定的互斥鎖加鎖時表現不一樣。當前(glibc2.2

.3,linuxthreads0.9)有四個值可供選擇:

PTHREAD_MUTEX_TIMED_NP,這是缺省值,也就是普通鎖。當一個線程加鎖之後,其他請求鎖的線程將造成一個等待隊列,並在解鎖後按優先級得到鎖。這種鎖策略保

證了資源分配的公平性。

PTHREAD_MUTEX_RECURSIVE_NP,嵌套鎖,容許同一個線程對同一個鎖成功得到屢次,並經過屢次unlock解鎖。若是是不一樣線程請求,則在加鎖線程解鎖時從新競爭。

PTHREAD_MUTEX_ERRORCHECK_NP,檢錯鎖,若是同一個線程請求同一個鎖,則返回EDEADLK,不然與PTHREAD_MUTEX_TIMED_NP類型動做相同。這樣就保證當不容許屢次

加鎖時不會出現最簡單狀況下的死鎖。

PTHREAD_MUTEX_ADAPTIVE_NP,適應鎖,動做最簡單的鎖類型,僅等待解鎖後從新競爭。

3. 鎖操做

鎖操做主要包括加鎖pthread_mutex_lock()、解鎖pthread_mutex_unlock()和測試加鎖pthread_mutex_trylock()三個,不論哪一種類型的鎖,都不可能被兩個不一樣的

線程同時獲得,而必須等待解鎖。對於普通鎖和適應鎖類型,解鎖者能夠是同進程內任何線程;而檢錯鎖則必須由加鎖者解鎖纔有效,不然返回EPERM;對於嵌套鎖

,文檔和實現要求必須由加鎖者解鎖,但實驗結果代表並無這種限制,這個不一樣目前尚未獲得解釋。在同一進程中的線程,若是加鎖後沒有解鎖,則任何其餘線

程都沒法再得到鎖。

int pthread_mutex_lock(pthread_mutex_t *mutex)

int pthread_mutex_unlock(pthread_mutex_t *mutex)

int pthread_mutex_trylock(pthread_mutex_t *mutex)

pthread_mutex_trylock()語義與pthread_mutex_lock()相似,不一樣的是在鎖已經被佔據時返回EBUSY而不是掛起等待。

4. 其餘

POSIX線程鎖機制的Linux實現都不是取消點,所以,延遲取消類型的線程不會因收到取消信號而離開加鎖等待。值得注意的是,若是線程在加鎖後解鎖前被取消,鎖

將永遠保持鎖定狀態,所以若是在關鍵區段內有取消點存在,或者設置了異步取消類型,則必須在退出回調函數中解鎖。

這個鎖機制同時也不是異步信號安全的,也就是說,不該該在信號處理過程當中使用互斥鎖,不然容易形成死鎖。

二. 條件變量

條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動做:一個線程等待"條件變量的條件成立"而掛起;另外一個線程使"條件成立"(給出條件

成立信號)。爲了防止競爭,條件變量的使用老是和一個互斥鎖結合在一塊兒。

1. 建立和註銷

條件變量和互斥鎖同樣,都有靜態動態兩種建立方式,靜態方式使用PTHREAD_COND_INITIALIZER常量,以下:

pthread_cond_t cond=PTHREAD_COND_INITIALIZER

動態方式調用pthread_cond_init()函數,API定義以下:

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)

儘管POSIX標準中爲條件變量定義了屬性,但在LinuxThreads中沒有實現,所以cond_attr值一般爲NULL,且被忽略。

註銷一個條件變量須要調用pthread_cond_destroy(),只有在沒有線程在該條件變量上等待的時候才能註銷這個條件變量,不然返回EBUSY。由於Linux實現的條件變

量沒有分配什麼資源,因此註銷動做只包括檢查是否有等待線程。API定義以下:

int pthread_cond_destroy(pthread_cond_t *cond)

2. 等待和激發

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)

等待條件有兩種方式:無條件等待pthread_cond_wait()和計時等待pthread_cond_timedwait(),其中計時等待方式若是在給定時刻前條件沒有知足,則返回ETIMEOU

T,結束等待,其中abstime以與time()系統調用相贊成義的絕對時間形式出現,0表示格林尼治時間197011000秒。

不管哪一種等待方式,都必須和一個互斥鎖配合,以防止多個線程同時請求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的競爭條件(Race

Condition)。mutex互斥鎖必須是普通鎖(PTHREAD_MUTEX_TIMED_NP)或者適應鎖(PTHREAD_MUTEX_ADAPTIVE_NP),且在調用pthread_cond_wait()前必須由本線程

加鎖(pthread_mutex_lock()),而在更新條件等待隊列之前,mutex保持鎖定狀態,並在線程掛起進入等待前解鎖。在條件知足從而離開pthread_cond_wait()以前

mutex將被從新加鎖,以與進入pthread_cond_wait()前的加鎖動做對應。

激發條件有兩種形式,pthread_cond_signal()激活一個等待該條件的線程,存在多個等待線程時按入隊順序激活其中一個;而pthread_cond_broadcast()則激活所

有等待線程。

3. 其餘

pthread_cond_wait()pthread_cond_timedwait()都被實現爲取消點,所以,在該處等待的線程將當即從新運行,在從新鎖定mutex後離開pthread_cond_wait()

而後執行取消動做。也就是說若是pthread_cond_wait()被取消,mutex是保持鎖定狀態的,於是須要定義退出回調函數來爲其解鎖。

如下示例集中演示了互斥鎖和條件變量的結合使用,以及取消對於條件等待動做的影響。在例子中,有兩個線程被啓動,並等待同一個條件變量,若是不使用退出回

調函數(見範例中的註釋部分),則tid2將在pthread_mutex_lock()處永久等待。若是使用回調函數,則tid2的條件等待及主線程的條件激發都能正常工做。

#include 

#include 

#include 

pthread_mutex_t mutex;

pthread_cond_t cond;

void * child1(void *arg)

{

pthread_cleanup_push(pthread_mutex_unlock,&mutex); /* comment 1 */

while(1){

printf("thread 1 get running \n");

printf("thread 1 pthread_mutex_lock returns %d\n",

pthread_mutex_lock(&mutex));

pthread_cond_wait(&cond,&mutex);

printf("thread 1 condition applied\n");

pthread_mutex_unlock(&mutex);

sleep(5);

}

pthread_cleanup_pop(0); /* comment 2 */

}

void *child2(void *arg)

{

while(1){

sleep(3); /* comment 3 */

printf("thread 2 get running.\n");

printf("thread 2 pthread_mutex_lock returns %d\n",

pthread_mutex_lock(&mutex));

pthread_cond_wait(&cond,&mutex);

printf("thread 2 condition applied\n");

pthread_mutex_unlock(&mutex);

sleep(1);

}

}

int main(void)

{

int tid1,tid2;

printf("hello, condition variable test\n");

pthread_mutex_init(&mutex,NULL);

pthread_cond_init(&cond,NULL);

pthread_create(&tid1,NULL,child1,NULL);

pthread_create(&tid2,NULL,child2,NULL);

do{

sleep(2); /* comment 4 */

pthread_cancel(tid1); /* comment 5 */

sleep(2); /* comment 6 */

pthread_cond_signal(&cond);

}while(1);

sleep(100);

pthread_exit(0);

}

若是不作註釋5pthread_cancel()動做,即便沒有那些sleep()延時操做,child1child2都能正常工做。註釋3和註釋4的延遲使得child1有時間完成取消動做,從

而使child2能在child1退出以後進入請求鎖操做。若是沒有註釋1和註釋2的回調函數定義,系統將掛起在child2請求鎖的地方;而若是同時也不作註釋3和註釋4的延

時,child2能在child1完成取消動做之前獲得控制,從而順利執行申請鎖的操做,但卻可能掛起在pthread_cond_wait()中,由於其中也有申請mutex的操做。child1

函數給出的是標準的條件變量的使用方式:回調函數保護,等待條件前鎖定,pthread_cond_wait()返回後解鎖。

條件變量機制不是異步信號安全的,也就是說,在信號處理函數中調用pthread_cond_signal()或者pthread_cond_broadcast()極可能引發死鎖。

三. 信號燈

信號燈與互斥鎖和條件變量的主要不一樣在於""的概念,燈亮則意味着資源可用,燈滅則意味着不可用。若是說後兩中同步方式側重於"等待"操做,即資源不可用的

話,信號燈機制則側重於點燈,即告知資源可用;沒有等待線程的解鎖或激發條件都是沒有意義的,而沒有等待燈亮的線程的點燈操做則有效,且能保持燈亮狀態。

固然,這樣的操做原語也意味着更多的開銷。

信號燈的應用除了燈亮/燈滅這種二元燈之外,也能夠採用大於1的燈數,以表示資源數大於1,這時能夠稱之爲多元燈。

1. 建立和註銷

POSIX信號燈標準定義了有名信號燈和無名信號燈兩種,但LinuxThreads的實現僅有無名燈,同時有名燈除了老是可用於多進程之間之外,在使用上與無名燈並無

很大的區別,所以下面僅就無名燈進行討論。

int sem_init(sem_t *sem, int pshared, unsigned int value)

這是建立信號燈的API,其中value爲信號燈的初值,pshared表示是否爲多進程共享而不只僅是用於一個進程。LinuxThreads沒有實現多進程共享信號燈,所以全部

0值的pshared輸入都將使sem_init()返回-1,且置errnoENOSYS。初始化好的信號燈由sem變量表徵,用於如下點燈、滅燈操做。

int sem_destroy(sem_t * sem)

被註銷的信號燈sem要求已沒有線程在等待該信號燈,不然返回-1,且置errnoEBUSY。除此以外,LinuxThreads的信號燈註銷函數不作其餘動做。

2. 點燈和滅燈

int sem_post(sem_t * sem)

點燈操做將信號燈值原子地加1,表示增長一個可訪問的資源。

int sem_wait(sem_t * sem)

int sem_trywait(sem_t * sem)

sem_wait()爲等待燈亮操做,等待燈亮(信號燈值大於0),而後將信號燈原子地減1,並返回。sem_trywait()sem_wait()的非阻塞版,若是信號燈計數大於0,則

原子地減1並返回0,不然當即返回-1errno置爲EAGAIN

3. 獲取燈值

int sem_getvalue(sem_t * sem, int * sval)

讀取sem中的燈計數,存於*sval中,並返回0

4. 其餘

sem_wait()被實現爲取消點,並且在支持原子"比較且交換"指令的體系結構上,sem_post()是惟一能用於異步信號處理函數的POSIX異步信號安全的API

四. 異步信號

因爲LinuxThreads是在覈外使用核內輕量級進程實現的線程,因此基於內核的異步信號操做對於線程也是有效的。但同時,因爲異步信號老是實際發往某個進程,所

以沒法實現POSIX標準所要求的"信號到達某個進程,而後再由該進程將信號分發到全部沒有阻塞該信號的線程中"原語,而是隻能影響到其中一個線程。

POSIX異步信號同時也是一個標準C庫提供的功能,主要包括信號集管理(sigemptyset()sigfillset()sigaddset()sigdelset()sigismember()等)、信號處

理函數安裝(sigaction())、信號阻塞控制(sigprocmask())、被阻塞信號查詢(sigpending())、信號等待(sigsuspend())等,它們與發送信號的kill()等函數

配合就能實現進程間異步信號功能。LinuxThreads圍繞線程封裝了sigaction()raise(),本節集中討論LinuxThreads中擴展的異步信號函數,包括pthread_sigmas

k()pthread_kill()sigwait()三個函數。毫無疑問,全部POSIX異步信號函數對於線程都是可用的。

int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask)

設置線程的信號屏蔽碼,語義與sigprocmask()相同,但對不容許屏蔽的Cancel信號和不容許響應的Restart信號進行了保護。被屏蔽的信號保存在信號隊列中,可由

sigpending()函數取出。

int pthread_kill(pthread_t thread, int signo)

thread號線程發送signo信號。實現中在經過thread線程號定位到對應進程號之後使用kill()系統調用完成發送。

int sigwait(const sigset_t *set, int *sig)

掛起線程,等待set中指定的信號之一到達,並將到達的信號存入*sig中。POSIX標準建議在調用sigwait()等待信號之前,進程中全部線程都應屏蔽該信號,以保證

僅有sigwait()的調用者得到該信號,所以,對於須要等待同步的異步信號,老是應該在建立任何線程之前調用pthread_sigmask()屏蔽該信號的處理。並且,調用si

gwait()期間,原來附接在該信號上的信號處理函數不會被調用。

若是在等待期間接收到Cancel信號,則當即退出等待,也就是說sigwait()被實現爲取消點。

五. 其餘同步方式

除了上述討論的同步方式之外,其餘不少進程間通訊手段對於LinuxThreads也是可用的,好比基於文件系統的IPC(管道、UnixSocket等)、消息隊列(Sys.V或者

Posix的)、System

V的信號燈等。只有一點須要注意,LinuxThreads在覈內是做爲共享存儲區、共享文件系統屬性、共享信號處理、共享文件描述符的獨立進程看待的。

Posix線程編程指南(4)

內容:

1. 線程終止方式

2. 線程終止時的清理

3. 線程終止的同步及其返回值

4. 關於pthread_exit()return

參考資料

相關內容:

(1) 線程建立與取消

(2) 線程私有數據

(3) 線程同步

線程終止

這是一個關於Posix線程編程的專欄。做者在闡明概念的基礎上,將向您詳細講述Posix線程庫API。本文是第四篇將向您講述線程停止。

1. 線程終止方式

通常來講,Posix的線程終止有兩種狀況:正常終止和非正常終止。線程主動調用pthread_exit()或者從線程函數中return都將使線程正常退出,這是可預見的退出

方式;非正常終止是線程在其餘線程的干預下,或者因爲自身運行出錯(好比訪問非法地址)而退出,這種退出方式是不可預見的。

2. 線程終止時的清理

不管是可預見的線程終止仍是異常終止,都會存在資源釋放的問題,在不考慮因運行出錯而退出的前提下,如何保證線程終止時能順利的釋放掉本身所佔用的資源,

特別是鎖資源,就是一個必須考慮解決的問題。

最常常出現的情形是資源獨佔鎖的使用:線程爲了訪問臨界資源而爲其加上鎖,但在訪問過程當中被外界取消,若是線程處於響應取消狀態,且採用異步方式響應,或

者在打開獨佔鎖之前的運行路徑上存在取消點,則該臨界資源將永遠處於鎖定狀態得不到釋放。外界取消操做是不可預見的,所以的確須要一個機制來簡化用於資源

釋放的編程。

POSIX線程API中提供了一個pthread_cleanup_push()/pthread_cleanup_pop()函數對用於自動釋放資源--pthread_cleanup_push()的調用點到pthread_cleanup_

pop()之間的程序段中的終止動做(包括調用pthread_exit()和取消點終止)都將執行pthread_cleanup_push()所指定的清理函數。API定義以下:

void pthread_cleanup_push(void (*routine) (void *), void *arg)

void pthread_cleanup_pop(int execute)

pthread_cleanup_push()/pthread_cleanup_pop()採用先入後出的棧結構管理,void routine(void

*arg)函數在調用pthread_cleanup_push()時壓入清理函數棧,屢次對pthread_cleanup_push()的調用將在清理函數棧中造成一個函數鏈,在執行該函數鏈時按照壓

棧的相反順序彈出。execute參數表示執行到pthread_cleanup_pop()時是否在彈出清理函數的同時執行該函數,爲0表示不執行,非0爲執行;這個參數並不影響異常

終止時清理函數的執行。

pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式實現的,這是pthread.h中的宏定義:

#define pthread_cleanup_push(routine,arg) \

{ struct _pthread_cleanup_buffer _buffer; \

_pthread_cleanup_push (&_buffer, (routine), (arg));

#define pthread_cleanup_pop(execute) \

_pthread_cleanup_pop (&_buffer, (execute)); }

可見,pthread_cleanup_push()帶有一個"{",而pthread_cleanup_pop()帶有一個"}",所以這兩個函數必須成對出現,且必須位於程序的同一級別的代碼段中才能

經過編譯。在下面的例子裏,當線程在"do some work"中終止時,將主動調用pthread_mutex_unlock(mut),以完成解鎖動做。

pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);

pthread_mutex_lock(&mut);

/* do some work */

pthread_mutex_unlock(&mut);

pthread_cleanup_pop(0);

必需要注意的是,若是線程處於PTHREAD_CANCEL_ASYNCHRONOUS狀態,上述代碼段就有可能出錯,由於CANCEL事件有可能在pthread_cleanup_push()pthread_mutex

_lock()之間發生,或者在pthread_mutex_unlock()pthread_cleanup_pop()之間發生,從而致使清理函數unlock一個並無加鎖的mutex變量,形成錯誤。所以,

在使用清理函數的時候,都應該暫時設置成PTHREAD_CANCEL_DEFERRED模式。爲此,POSIXLinux實現中還提供了一對不保證可移植的pthread_cleanup_push_defer_

np()/pthread_cleanup_pop_defer_np()擴展函數,功能與如下代碼段至關:

{ int oldtype;

pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);

pthread_cleanup_push(routine, arg);

...

pthread_cleanup_pop(execute);

pthread_setcanceltype(oldtype, NULL);

}

3. 線程終止的同步及其返回值

通常狀況下,進程中各個線程的運行都是相互獨立的,線程的終止並不會通知,也不會影響其餘線程,終止的線程所佔用的資源也並不會隨着線程的終止而獲得釋放

。正如進程之間能夠用wait()系統調用來同步終止並釋放資源同樣,線程之間也有相似機制,那就是pthread_join()函數。

void pthread_exit(void *retval)

int pthread_join(pthread_t th, void **thread_return)

int pthread_detach(pthread_t th)

pthread_join()的調用者將掛起並等待th線程終止,retvalpthread_exit()調用者線程(線程IDth)的返回值,若是thread_return不爲NULL,則*thread_retur

n=retval。須要注意的是一個線程僅容許惟一的一個線程使用pthread_join()等待它的終止,而且被等待的線程應該處於可join狀態,即非DETACHED狀態。

若是進程中的某個線程執行了pthread_detach(th),則th線程將處於DETACHED狀態,這使得th線程在結束運行時自行釋放所佔用的內存資源,同時也沒法由pthread_

join()同步,pthread_detach()執行以後,對th請求pthread_join()將返回錯誤。

一個可join的線程所佔用的內存僅當有線程對其執行了pthread_join()後纔會釋放,所以爲了不內存泄漏,全部線程的終止,要麼已設爲DETACHED,要麼就須要使

pthread_join()來回收。

4. 關於pthread_exit()return

理論上說,pthread_exit()和線程宿體函數退出的功能是相同的,函數結束時會在內部自動調用pthread_exit()來清理線程相關的資源。但實際上兩者因爲編譯器的

處理有很大的不一樣。

在進程主函數(main())中調用pthread_exit(),只會使主函數所在的線程(能夠說是進程的主線程)退出;而若是是return,編譯器將使其調用進程退出的代碼(

_exit()),從而致使進程及其全部線程結束運行。

其次,在線程宿主函數中主動調用return,若是return語句包含在pthread_cleanup_push()/pthread_cleanup_pop()對中,則不會引發清理函數的執行,反而會致使

segment fault

 

Posix線程編程指南(5)

內容:

1.得到本線程ID

2.判斷兩個線程是否爲同一線程

3.僅執行一次的操做

4pthread_kill_other_threads_np()

相關內容:

(1) 線程建立與取消

(2) 線程私有數據

(3) 線程同步

(4) 線程終止

這是一個關於Posix線程編程的專欄。做者在闡明概念的基礎上,將向您詳細講述Posix線程庫API。本文是第五篇將向您講述pthread_self()pthread_equal()pt

hread_once()等雜項函數。

Posix線程規範中還有幾個輔助函數難以歸類,暫且稱其爲雜項函數,主要包括pthread_self()pthread_equal()pthread_once()三個,另外還有一個LinuxThr

eads非可移植性擴展函數pthread_kill_other_threads_np()。本文就介紹這幾個函數的定義和使用。

1. 得到本線程ID

pthread_t pthread_self(void)

本函數返回本線程的標識符。

LinuxThreads中,每一個線程都用一個pthread_descr結構來描述,其中包含了線程狀態、線程ID等全部須要的數據結構,此函數的實現就是在線程棧幀中找到本線

程的pthread_descr結構,而後返回其中的p_tid項。

pthread_t類型在LinuxThreads中定義爲無符號長整型。

2. 判斷兩個線程是否爲同一線程

int pthread_equal(pthread_t thread1, pthread_t thread2)

判斷兩個線程描述符是否指向同一線程。在LinuxThreads中,線程ID相同的線程必然是同一個線程,所以,這個函數的實現僅僅判斷thread1thread2是否相等。

3. 僅執行一次的操做

int pthread_once(pthread_once_t *once_control, void (*init_routine) (void))

本函數使用初值爲PTHREAD_ONCE_INITonce_control變量保證init_routine()函數在本進程執行序列中僅執行一次。

#include 

#include 

pthread_once_t once=PTHREAD_ONCE_INIT;

void once_run(void)

{

printf("once_run in thread %d\n",pthread_self());

}

void * child1(void *arg)

{

int tid=pthread_self();

printf("thread %d enter\n",tid);

pthread_once(&once,once_run);

printf("thread %d returns\n",tid);

}

void * child2(void *arg)

{

int tid=pthread_self();

printf("thread %d enter\n",tid);

pthread_once(&once,once_run);

printf("thread %d returns\n",tid);

}

int main(void)

{

int tid1,tid2;

printf("hello\n");

pthread_create(&tid1,NULL,child1,NULL);

pthread_create(&tid2,NULL,child2,NULL);

sleep(10);

printf("main thread exit\n");

return 0;

}

once_run()函數僅執行一次,且究竟在哪一個線程中執行是不定的,儘管pthread_once(&once,once_run)出如今兩個線程中。

LinuxThreads使用互斥鎖和條件變量保證由pthread_once()指定的函數執行且僅執行一次,而once_control則表徵是否執行過。若是once_control的初值不是PTHREA

D_ONCE_INITLinuxThreads定義爲0),pthread_once()的行爲就會不正常。在LinuxThreads中,實際"一次性函數"的執行狀態有三種:NEVER0)、IN_PROGRESS

1)、DONE2),若是once初值設爲1,則因爲全部pthread_once()都必須等待其中一個激發"已執行一次"信號,所以全部pthread_once()都會陷入永久的等待中

;若是設爲2,則表示該函數已執行過一次,從而全部pthread_once()都會當即返回0

4. pthread_kill_other_threads_np()

void pthread_kill_other_threads_np(void)

這個函數是LinuxThreads針對自己沒法實現的POSIX約定而作的擴展。POSIX要求當進程的某一個線程執行exec*系統調用在進程空間中加載另外一個程序時,當前進程

的全部線程都應終止。因爲LinuxThreads的侷限性,該機制沒法在exec中實現,所以要求線程執行exec前手工終止其餘全部線程。pthread_kill_other_threads_np(

)的做用就是這個。

須要注意的是,pthread_kill_other_threads_np()並無經過pthread_cancel()來終止線程,而是直接向管理線程發"進程退出"信號,使全部其餘線程都結束運行

,而不通過Cancel動做,固然也不會執行退出回調函數。儘管LinuxThreads的實驗結果與文檔說明相同,但代碼實現中倒是用的__pthread_sig_cancel信號來kill

程,應該效果與執行pthread_cancel()是同樣的,其中緣由目前還不清楚。

相關文章
相關標籤/搜索