亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費注冊 查看新帖 |

Chinaunix

  平臺 論壇 博客 文庫
12下一頁
最近訪問板塊 發(fā)新帖
查看: 9519 | 回復(fù): 10
打印 上一主題 下一主題

[函數(shù)] [轉(zhuǎn)貼]unix下的線程編程 [復(fù)制鏈接]

論壇徽章:
0
跳轉(zhuǎn)到指定樓層
1 [收藏(0)] [報告]
發(fā)表于 2008-06-26 15:18 |只看該作者 |倒序瀏覽
看到有人在問線程編程,我轉(zhuǎn)貼幾篇我以前收集的入門教程,希望能夠有所幫助。
=====================================================================
Linux下的多線程編程
作者: 姚繼鋒 (2001-08-11 09:05:00)
1 引言
  線程(thread)技術(shù)早在60年代就被提出,但真正應(yīng)用多線程到操作系統(tǒng)中去,是在80年代中期,solaris是這方面的佼佼者。傳統(tǒng)的Unix也支持線程的概念,但是在一個進程(process)中只允許有一個線程,這樣多線程就意味著多進程,F(xiàn)在,多線程技術(shù)已經(jīng)被許多操作系統(tǒng)所支持,包括Windows/NT,當(dāng)然,也包括Linux。
  為什么有了進程的概念后,還要再引入線程呢?使用多線程到底有哪些好處?什么的系統(tǒng)應(yīng)該選用多線程?我們首先必須回答這些問題。
  使用多線程的理由之一是和進程相比,它是一種非常"節(jié)儉"的多任務(wù)操作方式。我們知道,在Linux系統(tǒng)下,啟動一個新的進程必須分配給它獨立的地址空間,建立眾多的數(shù)據(jù)表來維護它的代碼段、堆棧段和數(shù)據(jù)段,這是一種"昂貴"的多任務(wù)工作方式。而運行于一個進程中的多個線程,它們彼此之間使用相同的地址空間,共享大部分數(shù)據(jù),啟動一個線程所花費的空間遠遠小于啟動一個進程所花費的空間,而且,線程間彼此切換所需的時間也遠遠小于進程間切換所需要的時間。據(jù)統(tǒng)計,總的說來,一個進程的開銷大約是一個線程開銷的30倍左右,當(dāng)然,在具體的系統(tǒng)上,這個數(shù)據(jù)可能會有較大的區(qū)別。
  使用多線程的理由之二是線程間方便的通信機制。對不同進程來說,它們具有獨立的數(shù)據(jù)空間,要進行數(shù)據(jù)的傳遞只能通過通信的方式進行,這種方式不僅費時,而且很不方便。線程則不然,由于同一進程下的線程之間共享數(shù)據(jù)空間,所以一個線程的數(shù)據(jù)可以直接為其它線程所用,這不僅快捷,而且方便。當(dāng)然,數(shù)據(jù)的共享也帶來其他一些問題,有的變量不能同時被兩個線程所修改,有的子程序中聲明為static的數(shù)據(jù)更有可能給多線程程序帶來災(zāi)難性的打擊,這些正是編寫多線程程序時最需要注意的地方。
  除了以上所說的優(yōu)點外,不和進程比較,多線程程序作為一種多任務(wù)、并發(fā)的工作方式,當(dāng)然有以下的優(yōu)點:
  1) 提高應(yīng)用程序響應(yīng)。這對圖形界面的程序尤其有意義,當(dāng)一個操作耗時很長時,整個系統(tǒng)都會等待這個操作,此時程序不會響應(yīng)鍵盤、鼠標、菜單的操作,而使用多線程技術(shù),將耗時長的操作(time consuming)置于一個新的線程,可以避免這種尷尬的情況。
  2) 使多CPU系統(tǒng)更加有效。操作系統(tǒng)會保證當(dāng)線程數(shù)不大于CPU數(shù)目時,不同的線程運行于不同的CPU上。
  3) 改善程序結(jié)構(gòu)。一個既長又復(fù)雜的進程可以考慮分為多個線程,成為幾個獨立或半獨立的運行部分,這樣的程序會利于理解和修改。
  下面我們先來嘗試編寫一個簡單的多線程程序。

2 簡單的多線程編程
  Linux系統(tǒng)下的多線程遵循POSIX線程接口,稱為pthread。編寫Linux下的多線程程序,需要使用頭文件pthread.h,連接時需要使用庫libpthread.a。順便說一下,Linux下pthread的實現(xiàn)是通過系統(tǒng)調(diào)用clone()來實現(xiàn)的。clone()是Linux所特有的系統(tǒng)調(diào)用,它的使用方式類似fork,關(guān)于clone()的詳細情況,有興趣的讀者可以去查看有關(guān)文檔說明。下面我們展示一個最簡單的多線程程序example1.c。

/* example.c*/
#include <stdio.h>
#include <pthread.h>
void thread(void)
{
int i;
for(i=0;i<3;i++)
printf("This is a pthread.\n");
}

int main(void)
{
pthread_t id;
int i,ret;
ret=pthread_create(&id,NULL,(void *) thread,NULL);
if(ret!=0){
printf ("Create pthread error!\n");
exit (1);
}
for(i=0;i<3;i++)
printf("This is the main process.\n");
pthread_join(id,NULL);
return (0);
}

我們編譯此程序:
gcc example1.c -lpthread -o example1
運行example1,我們得到如下結(jié)果:
This is the main process.
This is a pthread.
This is the main process.
This is the main process.
This is a pthread.
This is a pthread.
再次運行,我們可能得到如下結(jié)果:
This is a pthread.
This is the main process.
This is a pthread.
This is the main process.
This is a pthread.
This is the main process.

  前后兩次結(jié)果不一樣,這是兩個線程爭奪CPU資源的結(jié)果。上面的示例中,我們使用到了兩個函數(shù),  pthread_create和pthread_join,并聲明了一個pthread_t型的變量。
  pthread_t在頭文件/usr/include/bits/pthreadtypes.h中定義:
  typedef unsigned long int pthread_t;
  它是一個線程的標識符。函數(shù)pthread_create用來創(chuàng)建一個線程,它的原型為:
  extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,
  void *(*__start_routine) (void *), void *__arg));
  第一個參數(shù)為指向線程標識符的指針,第二個參數(shù)用來設(shè)置線程屬性,第三個參數(shù)是線程運行函數(shù)的起始地址,最后一個參數(shù)是運行函數(shù)的參數(shù)。這里,我們的函數(shù)thread不需要參數(shù),所以最后一個參數(shù)設(shè)為空指針。第二個參數(shù)我們也設(shè)為空指針,這樣將生成默認屬性的線程。對線程屬性的設(shè)定和修改我們將在下一節(jié)闡述。當(dāng)創(chuàng)建線程成功時,函數(shù)返回0,若不為0則說明創(chuàng)建線程失敗,常見的錯誤返回代碼為EAGAIN和EINVAL。前者表示系統(tǒng)限制創(chuàng)建新的線程,例如線程數(shù)目過多了;后者表示第二個參數(shù)代表的線程屬性值非法。創(chuàng)建線程成功后,新創(chuàng)建的線程則運行參數(shù)三和參數(shù)四確定的函數(shù),原來的線程則繼續(xù)運行下一行代碼。
  函數(shù)pthread_join用來等待一個線程的結(jié)束。函數(shù)原型為:
  extern int pthread_join __P ((pthread_t __th, void **__thread_return));
  第一個參數(shù)為被等待的線程標識符,第二個參數(shù)為一個用戶定義的指針,它可以用來存儲被等待線程的返回值。這個函數(shù)是一個線程阻塞的函數(shù),調(diào)用它的函數(shù)將一直等待到被等待的線程結(jié)束為止,當(dāng)函數(shù)返回時,被等待線程的資源被收回。一個線程的結(jié)束有兩種途徑,一種是象我們上面的例子一樣,函數(shù)結(jié)束了,調(diào)用它的線程也就結(jié)束了;另一種方式是通過函數(shù)pthread_exit來實現(xiàn)。它的函數(shù)原型為:
  extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));
  唯一的參數(shù)是函數(shù)的返回代碼,只要pthread_join中的第二個參數(shù)thread_return不是NULL,這個值將被傳遞給thread_return。最后要說明的是,一個線程不能被多個線程等待,否則第一個接收到信號的線程成功返回,其余調(diào)用pthread_join的線程則返回錯誤代碼ESRCH。
  在這一節(jié)里,我們編寫了一個最簡單的線程,并掌握了最常用的三個函數(shù)pthread_create,pthread_join和pthread_exit。下面,我們來了解線程的一些常用屬性以及如何設(shè)置這些屬性。

3 修改線程的屬性
  在上一節(jié)的例子里,我們用pthread_create函數(shù)創(chuàng)建了一個線程,在這個線程中,我們使用了默認參數(shù),即將該函數(shù)的第二個參數(shù)設(shè)為NULL。的確,對大多數(shù)程序來說,使用默認屬性就夠了,但我們還是有必要來了解一下線程的有關(guān)屬性。
  屬性結(jié)構(gòu)為pthread_attr_t,它同樣在頭文件/usr/include/pthread.h中定義,喜歡追根問底的人可以自己去查看。屬性值不能直接設(shè)置,須使用相關(guān)函數(shù)進行操作,初始化的函數(shù)為pthread_attr_init,這個函數(shù)必須在pthread_create函數(shù)之前調(diào)用。屬性對象主要包括是否綁定、是否分離、堆棧地址、堆棧大小、優(yōu)先級。默認的屬性為非綁定、非分離、缺省1M的堆棧、與父進程同樣級別的優(yōu)先級。
  關(guān)于線程的綁定,牽涉到另外一個概念:輕進程(LWP:Light Weight Process)。輕進程可以理解為內(nèi)核線程,它位于用戶層和系統(tǒng)層之間。系統(tǒng)對線程資源的分配、對線程的控制是通過輕進程來實現(xiàn)的,一個輕進程可以控制一個或多個線程。默認狀況下,啟動多少輕進程、哪些輕進程來控制哪些線程是由系統(tǒng)來控制的,這種狀況即稱為非綁定的。綁定狀況下,則顧名思義,即某個線程固定的"綁"在一個輕進程之上。被綁定的線程具有較高的響應(yīng)速度,這是因為CPU時間片的調(diào)度是面向輕進程的,綁定的線程可以保證在需要的時候它總有一個輕進程可用。通過設(shè)置被綁定的輕進程的優(yōu)先級和調(diào)度級可以使得綁定的線程滿足諸如實時反應(yīng)之類的要求。
  設(shè)置線程綁定狀態(tài)的函數(shù)為pthread_attr_setscope,它有兩個參數(shù),第一個是指向?qū)傩越Y(jié)構(gòu)的指針,第二個是綁定類型,它有兩個取值:PTHREAD_SCOPE_SYSTEM(綁定的)和PTHREAD_SCOPE_PROCESS(非綁定的)。下面的代碼即創(chuàng)建了一個綁定的線程。
#include <pthread.h>
pthread_attr_t attr;
pthread_t tid;

/*初始化屬性值,均設(shè)為默認值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

pthread_create(&tid, &attr, (void *) my_function, NULL);

  線程的分離狀態(tài)決定一個線程以什么樣的方式來終止自己。在上面的例子中,我們采用了線程的默認屬性,即為非分離狀態(tài),這種情況下,原有的線程等待創(chuàng)建的線程結(jié)束。只有當(dāng)pthread_join()函數(shù)返回時,創(chuàng)建的線程才算終止,才能釋放自己占用的系統(tǒng)資源。而分離線程不是這樣子的,它沒有被其他的線程所等待,自己運行結(jié)束了,線程也就終止了,馬上釋放系統(tǒng)資源。程序員應(yīng)該根據(jù)自己的需要,選擇適當(dāng)?shù)姆蛛x狀態(tài)。設(shè)置線程分離狀態(tài)的函數(shù)為pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二個參數(shù)可選為PTHREAD_CREATE_DETACHED(分離線程)和 PTHREAD _CREATE_JOINABLE(非分離線程)。這里要注意的一點是,如果設(shè)置一個線程為分離線程,而這個線程運行又非常快,它很可能在pthread_create函數(shù)返回之前就終止了,它終止以后就可能將線程號和系統(tǒng)資源移交給其他的線程使用,這樣調(diào)用pthread_create的線程就得到了錯誤的線程號。要避免這種情況可以采取一定的同步措施,最簡單的方法之一是可以在被創(chuàng)建的線程里調(diào)用pthread_cond_timewait函數(shù),讓這個線程等待一會兒,留出足夠的時間讓函數(shù)pthread_create返回。設(shè)置一段等待時間,是在多線程編程里常用的方法。但是注意不要使用諸如wait()之類的函數(shù),它們是使整個進程睡眠,并不能解決線程同步的問題。
  另外一個可能常用的屬性是線程的優(yōu)先級,它存放在結(jié)構(gòu)sched_param中。用函數(shù)pthread_attr_getschedparam和函數(shù)pthread_attr_setschedparam進行存放,一般說來,我們總是先取優(yōu)先級,對取得的值修改后再存放回去。下面即是一段簡單的例子。
#include <pthread.h>
#include <sched.h>
pthread_attr_t attr;
pthread_t tid;
sched_param param;
int newprio=20;

pthread_attr_init(&attr);
pthread_attr_getschedparam(&attr, &param);
param.sched_priority=newprio;
pthread_attr_setschedparam(&attr, &param);
pthread_create(&tid, &attr, (void *)myfunction, myarg);
  

論壇徽章:
0
2 [報告]
發(fā)表于 2008-06-26 15:19 |只看該作者
4 線程的數(shù)據(jù)處理
  和進程相比,線程的最大優(yōu)點之一是數(shù)據(jù)的共享性,各個進程共享父進程處沿襲的數(shù)據(jù)段,可以方便的獲得、修改數(shù)據(jù)。但這也給多線程編程帶來了許多問題。我們必須當(dāng)心有多個不同的進程訪問相同的變量。許多函數(shù)是不可重入的,即同時不能運行一個函數(shù)的多個拷貝(除非使用不同的數(shù)據(jù)段)。在函數(shù)中聲明的靜態(tài)變量常常帶來問題,函數(shù)的返回值也會有問題。因為如果返回的是函數(shù)內(nèi)部靜態(tài)聲明的空間的地址,則在一個線程調(diào)用該函數(shù)得到地址后使用該地址指向的數(shù)據(jù)時,別的線程可能調(diào)用此函數(shù)并修改了這一段數(shù)據(jù)。在進程中共享的變量必須用關(guān)鍵字volatile來定義,這是為了防止編譯器在優(yōu)化時(如gcc中使用-OX參數(shù))改變它們的使用方式。為了保護變量,我們必須使用信號量、互斥等方法來保證我們對變量的正確使用。下面,我們就逐步介紹處理線程數(shù)據(jù)時的有關(guān)知識。

4.1 線程數(shù)據(jù)
  在單線程的程序里,有兩種基本的數(shù)據(jù):全局變量和局部變量。但在多線程程序里,還有第三種數(shù)據(jù)類型:線程數(shù)據(jù)(TSD: Thread-Specific Data)。它和全局變量很象,在線程內(nèi)部,各個函數(shù)可以象使用全局變量一樣調(diào)用它,但它對線程外部的其它線程是不可見的。這種數(shù)據(jù)的必要性是顯而易見的。例如我們常見的變量errno,它返回標準的出錯信息。它顯然不能是一個局部變量,幾乎每個函數(shù)都應(yīng)該可以調(diào)用它;但它又不能是一個全局變量,否則在A線程里輸出的很可能是B線程的出錯信息。要實現(xiàn)諸如此類的變量,我們就必須使用線程數(shù)據(jù)。我們?yōu)槊總線程數(shù)據(jù)創(chuàng)建一個鍵,它和這個鍵相關(guān)聯(lián),在各個線程里,都使用這個鍵來指代線程數(shù)據(jù),但在不同的線程里,這個鍵代表的數(shù)據(jù)是不同的,在同一個線程里,它代表同樣的數(shù)據(jù)內(nèi)容。
  和線程數(shù)據(jù)相關(guān)的函數(shù)主要有4個:創(chuàng)建一個鍵;為一個鍵指定線程數(shù)據(jù);從一個鍵讀取線程數(shù)據(jù);刪除鍵。
  創(chuàng)建鍵的函數(shù)原型為:
  extern int pthread_key_create __P ((pthread_key_t *__key,
  void (*__destr_function) (void *)));
  第一個參數(shù)為指向一個鍵值的指針,第二個參數(shù)指明了一個destructor函數(shù),如果這個參數(shù)不為空,那么當(dāng)每個線程結(jié)束時,系統(tǒng)將調(diào)用這個函數(shù)來釋放綁定在這個鍵上的內(nèi)存塊。這個函數(shù)常和函數(shù)pthread_once ((pthread_once_t*once_control, void (*initroutine) (void)))一起使用,為了讓這個鍵只被創(chuàng)建一次。函數(shù)pthread_once聲明一個初始化函數(shù),第一次調(diào)用pthread_once時它執(zhí)行這個函數(shù),以后的調(diào)用將被它忽略。

  在下面的例子中,我們創(chuàng)建一個鍵,并將它和某個數(shù)據(jù)相關(guān)聯(lián)。我們要定義一個函數(shù)createWindow,這個函數(shù)定義一個圖形窗口(數(shù)據(jù)類型為Fl_Window *,這是圖形界面開發(fā)工具FLTK中的數(shù)據(jù)類型)。由于各個線程都會調(diào)用這個函數(shù),所以我們使用線程數(shù)據(jù)。
/* 聲明一個鍵*/
pthread_key_t myWinKey;
/* 函數(shù) createWindow */
void createWindow ( void ) {
Fl_Window * win;
static pthread_once_t once= PTHREAD_ONCE_INIT;
/* 調(diào)用函數(shù)createMyKey,創(chuàng)建鍵*/
pthread_once ( & once, createMyKey) ;
/*win指向一個新建立的窗口*/
win=new Fl_Window( 0, 0, 100, 100, "MyWindow");
/* 對此窗口作一些可能的設(shè)置工作,如大小、位置、名稱等*/
setWindow(win);
/* 將窗口指針值綁定在鍵myWinKey上*/
pthread_setpecific ( myWinKey, win);
}

/* 函數(shù) createMyKey,創(chuàng)建一個鍵,并指定了destructor */
void createMyKey ( void ) {
pthread_keycreate(&myWinKey, freeWinKey);
}

/* 函數(shù) freeWinKey,釋放空間*/
void freeWinKey ( Fl_Window * win){
delete win;
}

  這樣,在不同的線程中調(diào)用函數(shù)createMyWin,都可以得到在線程內(nèi)部均可見的窗口變量,這個變量通過函數(shù)pthread_getspecific得到。在上面的例子中,我們已經(jīng)使用了函數(shù)pthread_setspecific來將線程數(shù)據(jù)和一個鍵綁定在一起。這兩個函數(shù)的原型如下:
  extern int pthread_setspecific __P ((pthread_key_t __key,__const void *__pointer));
  extern void *pthread_getspecific __P ((pthread_key_t __key));
  這兩個函數(shù)的參數(shù)意義和使用方法是顯而易見的。要注意的是,用pthread_setspecific為一個鍵指定新的線程數(shù)據(jù)時,必須自己釋放原有的線程數(shù)據(jù)以回收空間。這個過程函數(shù)pthread_key_delete用來刪除一個鍵,這個鍵占用的內(nèi)存將被釋放,但同樣要注意的是,它只釋放鍵占用的內(nèi)存,并不釋放該鍵關(guān)聯(lián)的線程數(shù)據(jù)所占用的內(nèi)存資源,而且它也不會觸發(fā)函數(shù)pthread_key_create中定義的destructor函數(shù)。線程數(shù)據(jù)的釋放必須在釋放鍵之前完成。

4.2 互斥鎖
  互斥鎖用來保證一段時間內(nèi)只有一個線程在執(zhí)行一段代碼。必要性顯而易見:假設(shè)各個線程向同一個文件順序?qū)懭霐?shù)據(jù),最后得到的結(jié)果一定是災(zāi)難性的。
  我們先看下面一段代碼。這是一個讀/寫程序,它們公用一個緩沖區(qū),并且我們假定一個緩沖區(qū)只能保存一條信息。即緩沖區(qū)只有兩個狀態(tài):有信息或沒有信息。

void reader_function ( void );
void writer_function ( void );

char buffer;
int buffer_has_item=0;
pthread_mutex_t mutex;
struct timespec delay;
void main ( void ){
pthread_t reader;
/* 定義延遲時間*/
delay.tv_sec = 2;
delay.tv_nec = 0;
/* 用默認屬性初始化一個互斥鎖對象*/
pthread_mutex_init (&mutex,NULL);
pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL);
writer_function( );
}

void writer_function (void){
while(1){
/* 鎖定互斥鎖*/
pthread_mutex_lock (&mutex);
if (buffer_has_item==0){
buffer=make_new_item( );
buffer_has_item=1;
}
/* 打開互斥鎖*/
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}

void reader_function(void){
while(1){
pthread_mutex_lock(&mutex);
if(buffer_has_item==1){
consume_item(buffer);
buffer_has_item=0;
}
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}
  這里聲明了互斥鎖變量mutex,結(jié)構(gòu)pthread_mutex_t為不公開的數(shù)據(jù)類型,其中包含一個系統(tǒng)分配的屬性對象。函數(shù)pthread_mutex_init用來生成一個互斥鎖。NULL參數(shù)表明使用默認屬性。如果需要聲明特定屬性的互斥鎖,須調(diào)用函數(shù)pthread_mutexattr_init。函數(shù)pthread_mutexattr_setpshared和函數(shù)pthread_mutexattr_settype用來設(shè)置互斥鎖屬性。前一個函數(shù)設(shè)置屬性pshared,它有兩個取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用來不同進程中的線程同步,后者用于同步本進程的不同線程。在上面的例子中,我們使用的是默認屬性PTHREAD_PROCESS_ PRIVATE。后者用來設(shè)置互斥鎖類型,可選的類型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它們分別定義了不同的上所、解鎖機制,一般情況下,選用最后一個默認屬性。
  pthread_mutex_lock聲明開始用互斥鎖上鎖,此后的代碼直至調(diào)用pthread_mutex_unlock為止,均被上鎖,即同一時間只能被一個線程調(diào)用執(zhí)行。當(dāng)一個線程執(zhí)行到pthread_mutex_lock處時,如果該鎖此時被另一個線程使用,那此線程被阻塞,即程序?qū)⒌却搅硪粋線程釋放此互斥鎖。在上面的例子中,我們使用了pthread_delay_np函數(shù),讓線程睡眠一段時間,就是為了防止一個線程始終占據(jù)此函數(shù)。
  上面的例子非常簡單,就不再介紹了,需要提出的是在使用互斥鎖的過程中很有可能會出現(xiàn)死鎖:兩個線程試圖同時占用兩個資源,并按不同的次序鎖定相應(yīng)的互斥鎖,例如兩個線程都需要鎖定互斥鎖1和互斥鎖2,a線程先鎖定互斥鎖1,b線程先鎖定互斥鎖2,這時就出現(xiàn)了死鎖。此時我們可以使用函數(shù)pthread_mutex_trylock,它是函數(shù)pthread_mutex_lock的非阻塞版本,當(dāng)它發(fā)現(xiàn)死鎖不可避免時,它會返回相應(yīng)的信息,程序員可以針對死鎖做出相應(yīng)的處理。另外不同的互斥鎖類型對死鎖的處理不一樣,但最主要的還是要程序員自己在程序設(shè)計注意這一點。

4.3 條件變量
  前一節(jié)中我們講述了如何使用互斥鎖來實現(xiàn)線程間數(shù)據(jù)的共享和通信,互斥鎖一個明顯的缺點是它只有兩種狀態(tài):鎖定和非鎖定。而條件變量通過允許線程阻塞和等待另一個線程發(fā)送信號的方法彌補了互斥鎖的不足,它常和互斥鎖一起使用。使用時,條件變量被用來阻塞一個線程,當(dāng)條件不滿足時,線程往往解開相應(yīng)的互斥鎖并等待條件發(fā)生變化。一旦其它的某個線程改變了條件變量,它將通知相應(yīng)的條件變量喚醒一個或多個正被此條件變量阻塞的線程。這些線程將重新鎖定互斥鎖并重新測試條件是否滿足。一般說來,條件變量被用來進行線承間的同步。
  條件變量的結(jié)構(gòu)為pthread_cond_t,函數(shù)pthread_cond_init()被用來初始化一個條件變量。它的原型為:
  extern int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr));
  其中cond是一個指向結(jié)構(gòu)pthread_cond_t的指針,cond_attr是一個指向結(jié)構(gòu)pthread_condattr_t的指針。結(jié)構(gòu)pthread_condattr_t是條件變量的屬性結(jié)構(gòu),和互斥鎖一樣我們可以用它來設(shè)置條件變量是進程內(nèi)可用還是進程間可用,默認值是PTHREAD_ PROCESS_PRIVATE,即此條件變量被同一進程內(nèi)的各個線程使用。注意初始化條件變量只有未被使用時才能重新初始化或被釋放。釋放一個條件變量的函數(shù)為pthread_cond_ destroy(pthread_cond_t cond)!
  函數(shù)pthread_cond_wait()使線程阻塞在一個條件變量上。它的函數(shù)原型為:
  extern int pthread_cond_wait __P ((pthread_cond_t *__cond,
  pthread_mutex_t *__mutex));
  線程解開mutex指向的鎖并被條件變量cond阻塞。線程可以被函數(shù)pthread_cond_signal和函數(shù)pthread_cond_broadcast喚醒,但是要注意的是,條件變量只是起阻塞和喚醒線程的作用,具體的判斷條件還需用戶給出,例如一個變量是否為0等等,這一點我們從后面的例子中可以看到。線程被喚醒后,它將重新檢查判斷條件是否滿足,如果還不滿足,一般說來線程應(yīng)該仍阻塞在這里,被等待被下一次喚醒。這個過程一般用while語句實現(xiàn)。
  另一個用來阻塞線程的函數(shù)是pthread_cond_timedwait(),它的原型為:
  extern int pthread_cond_timedwait __P ((pthread_cond_t *__cond,
  pthread_mutex_t *__mutex, __const struct timespec *__abstime));
  它比函數(shù)pthread_cond_wait()多了一個時間參數(shù),經(jīng)歷abstime段時間后,即使條件變量不滿足,阻塞也被解除。
  函數(shù)pthread_cond_signal()的原型為:
  extern int pthread_cond_signal __P ((pthread_cond_t *__cond));
  它用來釋放被阻塞在條件變量cond上的一個線程。多個線程阻塞在此條件變量上時,哪一個線程被喚醒是由線程的調(diào)度策略所決定的。要注意的是,必須用保護條件變量的互斥鎖來保護這個函數(shù),否則條件滿足信號又可能在測試條件和調(diào)用pthread_cond_wait函數(shù)之間被發(fā)出,從而造成無限制的等待。下面是使用函數(shù)pthread_cond_wait()和函數(shù)pthread_cond_signal()的一個簡單的例子。

pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count () {
pthread_mutex_lock (&count_lock);
while(count==0)
pthread_cond_wait( &count_nonzero, &count_lock);
count=count -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函數(shù)在pthread_cond_wait處被阻塞,并打開互斥鎖count_lock。此時,當(dāng)調(diào)用到函數(shù)increment_count時,pthread_cond_signal()函數(shù)改變條件變量,告知decrement_count()停止阻塞。讀者可以試著讓兩個線程分別運行這兩個函數(shù),看看會出現(xiàn)什么樣的結(jié)果。
  函數(shù)pthread_cond_broadcast(pthread_cond_t *cond)用來喚醒所有被阻塞在條件變量cond上的線程。這些線程被喚醒后將再次競爭相應(yīng)的互斥鎖,所以必須小心使用這個函數(shù)。

4.4 信號量
  信號量本質(zhì)上是一個非負的整數(shù)計數(shù)器,它被用來控制對公共資源的訪問。當(dāng)公共資源增加時,調(diào)用函數(shù)sem_post()增加信號量。只有當(dāng)信號量值大于0時,才能使用公共資源,使用后,函數(shù)sem_wait()減少信號量。函數(shù)sem_trywait()和函數(shù)pthread_ mutex_trylock()起同樣的作用,它是函數(shù)sem_wait()的非阻塞版本。下面我們逐個介紹和信號量有關(guān)的一些函數(shù),它們都在頭文件/usr/include/semaphore.h中定義。
  信號量的數(shù)據(jù)類型為結(jié)構(gòu)sem_t,它本質(zhì)上是一個長整型的數(shù)。函數(shù)sem_init()用來初始化一個信號量。它的原型為:
  extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));
  sem為指向信號量結(jié)構(gòu)的一個指針;pshared不為0時此信號量在進程間共享,否則只能為當(dāng)前進程的所有線程共享;value給出了信號量的初始值。
  函數(shù)sem_post( sem_t *sem )用來增加信號量的值。當(dāng)有線程阻塞在這個信號量上時,調(diào)用這個函數(shù)會使其中的一個線程不在阻塞,選擇機制同樣是由線程的調(diào)度策略決定的。
  函數(shù)sem_wait( sem_t *sem )被用來阻塞當(dāng)前線程直到信號量sem的值大于0,解除阻塞后將sem的值減一,表明公共資源經(jīng)使用后減少。函數(shù)sem_trywait ( sem_t *sem )是函數(shù)sem_wait()的非阻塞版本,它直接將信號量sem的值減一。
  函數(shù)sem_destroy(sem_t *sem)用來釋放信號量sem。
  下面我們來看一個使用信號量的例子。在這個例子中,一共有4個線程,其中兩個線程負責(zé)從文件讀取數(shù)據(jù)到公共的緩沖區(qū),另兩個線程從緩沖區(qū)讀取數(shù)據(jù)作不同的處理(加和乘運算)。
/* File sem.c */
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define MAXSTACK 100
int stack[MAXSTACK][2];
int size=0;
sem_t sem;
/* 從文件1.dat讀取數(shù)據(jù),每讀一次,信號量加一*/
void ReadData1(void){
FILE *fp=fopen("1.dat","r");
while(!feof(fp)){
fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);
sem_post(&sem);
++size;
}
fclose(fp);
}
/*從文件2.dat讀取數(shù)據(jù)*/
void ReadData2(void){
FILE *fp=fopen("2.dat","r");
while(!feof(fp)){
fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);
sem_post(&sem);
++size;
}
fclose(fp);
}
/*阻塞等待緩沖區(qū)有數(shù)據(jù),讀取數(shù)據(jù)后,釋放空間,繼續(xù)等待*/
void HandleData1(void){
while(1){
sem_wait(&sem);
printf("Plus:%d+%d=%d\n",stack[size][0],stack[size][1],
stack[size][0]+stack[size][1]);
--size;
}
}

void HandleData2(void){
while(1){
sem_wait(&sem);
printf("Multiply:%d*%d=%d\n",stack[size][0],stack[size][1],
stack[size][0]*stack[size][1]);
--size;
}
}
int main(void){
pthread_t t1,t2,t3,t4;
sem_init(&sem,0,0);
pthread_create(&t1,NULL,(void *)HandleData1,NULL);
pthread_create(&t2,NULL,(void *)HandleData2,NULL);
pthread_create(&t3,NULL,(void *)ReadData1,NULL);
pthread_create(&t4,NULL,(void *)ReadData2,NULL);
/* 防止程序過早退出,讓它在此無限期等待*/
pthread_join(t1,NULL);
}

  在Linux下,我們用命令gcc -lpthread sem.c -o sem生成可執(zhí)行文件sem。 我們事先編輯好數(shù)據(jù)文件1.dat和2.dat,假設(shè)它們的內(nèi)容分別為1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 ,我們運行sem,得到如下的結(jié)果:
Multiply:-1*-2=2
Plus:-1+-2=-3
Multiply:9*10=90
Plus:-9+-10=-19
Multiply:-7*-8=56
Plus:-5+-6=-11
Multiply:-3*-4=12
Plus:9+10=19
Plus:7+8=15
Plus:5+6=11

  從中我們可以看出各個線程間的競爭關(guān)系。而數(shù)值并未按我們原先的順序顯示出來這是由于size這個數(shù)值被各個線程任意修改的緣故。這也往往是多線程編程要注意的問題。

5 小結(jié)
  多線程編程是一個很有意思也很有用的技術(shù),使用多線程技術(shù)的網(wǎng)絡(luò)螞蟻是目前最常用的下載工具之一,使用多線程技術(shù)的grep比單線程的grep要快上幾倍,類似的例子還有很多。希望大家能用多線程技術(shù)寫出高效實用的好程序來。

論壇徽章:
0
3 [報告]
發(fā)表于 2008-06-26 15:24 |只看該作者
Posix線程編程指南(1)   
http://www-900.ibm.com/developer ... i/part5/index.shtml

內(nèi)容:

一、 線程創(chuàng)建
二、線程取消
關(guān)于作者




線程創(chuàng)建與取消
楊沙洲(pubb@163.net)
2001 年 10 月

這是一個關(guān)于Posix線程編程的專欄。作者在闡明概念的基礎(chǔ)上,將向您詳細講述Posix線程庫API。本文是第一篇將向您講述線程的創(chuàng)建與取消。
一、線程創(chuàng)建

1.1 線程與進程
相對進程而言,線程是一個更加接近于執(zhí)行體的概念,它可以與同進程中的其他線程共享數(shù)據(jù),但擁有自己的棧空間,擁有獨立的執(zhí)行序列。在串行程序基礎(chǔ)上引入線程和進程是為了提高程序的并發(fā)度,從而提高程序運行效率和響應(yīng)時間。

線程和進程在使用上各有優(yōu)缺點:線程執(zhí)行開銷小,但不利于資源的管理和保護;而進程正相反。同時,線程適合于在SMP機器上運行,而進程則可以跨機器遷移。

1.2 創(chuàng)建線程
POSIX通過pthread_create()函數(shù)創(chuàng)建線程,API定義如下:

int  pthread_create(pthread_t  *  thread, pthread_attr_t * attr,
void * (*start_routine)(void *), void * arg)



與fork()調(diào)用創(chuàng)建一個進程的方法不同,pthread_create()創(chuàng)建的線程并不具備與主線程(即調(diào)用pthread_create()的線程)同樣的執(zhí)行序列,而是使其運行start_routine(arg)函數(shù)。thread返回創(chuàng)建的線程ID,而attr是創(chuàng)建線程時設(shè)置的線程屬性(見下)。pthread_create()的返回值表示線程創(chuàng)建是否成功。盡管arg是void *類型的變量,但它同樣可以作為任意類型的參數(shù)傳給start_routine()函數(shù);同時,start_routine()可以返回一個void *類型的返回值,而這個返回值也可以是其他類型,并由pthread_join()獲取。


1.3 線程創(chuàng)建屬性
pthread_create()中的attr參數(shù)是一個結(jié)構(gòu)指針,結(jié)構(gòu)中的元素分別對應(yīng)著新線程的運行屬性,主要包括以下幾項:

__detachstate,表示新線程是否與進程中其他線程脫離同步,如果置位則新線程不能用pthread_join()來同步,且在退出時自行釋放所占用的資源。缺省為PTHREAD_CREATE_JOINABLE狀態(tài)。這個屬性也可以在線程創(chuàng)建并運行以后用pthread_detach()來設(shè)置,而一旦設(shè)置為PTHREAD_CREATE_DETACH狀態(tài)(不論是創(chuàng)建時設(shè)置還是運行時設(shè)置)則不能再恢復(fù)到PTHREAD_CREATE_JOINABLE狀態(tài)。

__schedpolicy,表示新線程的調(diào)度策略,主要包括SCHED_OTHER(正常、非實時)、SCHED_RR(實時、輪轉(zhuǎn)法)和SCHED_FIFO(實時、先入先出)三種,缺省為SCHED_OTHER,后兩種調(diào)度策略僅對超級用戶有效。運行時可以用過pthread_setschedparam()來改變。

__schedparam,一個struct sched_param結(jié)構(gòu),目前僅有一個sched_priority整型變量表示線程的運行優(yōu)先級。這個參數(shù)僅當(dāng)調(diào)度策略為實時(即SCHED_RR或SCHED_FIFO)時才有效,并可以在運行時通過pthread_setschedparam()函數(shù)來改變,缺省為0。

__inheritsched,有兩種值可供選擇:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新線程使用顯式指定調(diào)度策略和調(diào)度參數(shù)(即attr中的值),而后者表示繼承調(diào)用者線程的值。缺省為PTHREAD_EXPLICIT_SCHED。

__scope,表示線程間競爭CPU的范圍,也就是說線程優(yōu)先級的有效范圍。POSIX的標準中定義了兩個值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示與系統(tǒng)中所有線程一起競爭CPU時間,后者表示僅與同進程中的線程競爭CPU。目前LinuxThreads僅實現(xiàn)了PTHREAD_SCOPE_SYSTEM一值。

pthread_attr_t結(jié)構(gòu)中還有一些值,但不使用pthread_create()來設(shè)置。

為了設(shè)置這些屬性,POSIX定義了一系列屬性設(shè)置函數(shù),包括pthread_attr_init()、pthread_attr_destroy()和與各個屬性相關(guān)的pthread_attr_get---/pthread_attr_set---函數(shù)。

1.4 線程創(chuàng)建的Linux實現(xiàn)
我們知道,Linux的線程實現(xiàn)是在核外進行的,核內(nèi)提供的是創(chuàng)建進程的接口do_fork()。內(nèi)核提供了兩個系統(tǒng)調(diào)用__clone()和fork(),最終都用不同的參數(shù)調(diào)用do_fork()核內(nèi)API。當(dāng)然,要想實現(xiàn)線程,沒有核心對多進程(其實是輕量級進程)共享數(shù)據(jù)段的支持是不行的,因此,do_fork()提供了很多參數(shù),包括CLONE_VM(共享內(nèi)存空間)、CLONE_FS(共享文件系統(tǒng)信息)、CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信號句柄表)和CLONE_PID(共享進程ID,僅對核內(nèi)進程,即0號進程有效)。當(dāng)使用fork系統(tǒng)調(diào)用時,內(nèi)核調(diào)用do_fork()不使用任何共享屬性,進程擁有獨立的運行環(huán)境,而使用pthread_create()來創(chuàng)建線程時,則最終設(shè)置了所有這些屬性來調(diào)用__clone(),而這些參數(shù)又全部傳給核內(nèi)的do_fork(),從而創(chuàng)建的"進程"擁有共享的運行環(huán)境,只有棧是獨立的,由__clone()傳入。

Linux線程在核內(nèi)是以輕量級進程的形式存在的,擁有獨立的進程表項,而所有的創(chuàng)建、同步、刪除等操作都在核外pthread庫中進行。pthread庫使用一個管理線程(__pthread_manager(),每個進程獨立且唯一)來管理線程的創(chuàng)建和終止,為線程分配線程ID,發(fā)送線程相關(guān)的信號(比如Cancel),而主線程(pthread_create())的調(diào)用者則通過管道將請求信息傳給管理線程。

二、線程取消

2.1 線程取消的定義
一般情況下,線程在其主體函數(shù)退出的時候會自動終止,但同時也可以因為接收到另一個線程發(fā)來的終止(取消)請求而強制終止。

2.2 線程取消的語義
線程取消的方法是向目標線程發(fā)Cancel信號,但如何處理Cancel信號則由目標線程自己決定,或者忽略、或者立即終止、或者繼續(xù)運行至Cancelation-point(取消點),由不同的Cancelation狀態(tài)決定。

線程接收到CANCEL信號的缺省處理(即pthread_create()創(chuàng)建線程的缺省狀態(tài))是繼續(xù)運行至取消點,也就是說設(shè)置一個CANCELED狀態(tài),線程繼續(xù)運行,只有運行至Cancelation-point的時候才會退出。

2.3 取消點
根據(jù)POSIX標準,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函數(shù)以及read()、write()等會引起阻塞的系統(tǒng)調(diào)用都是Cancelation-point,而其他pthread函數(shù)都不會引起Cancelation動作。但是pthread_cancel的手冊頁聲稱,由于LinuxThread庫與C庫結(jié)合得不好,因而目前C庫函數(shù)都不是Cancelation-point;但CANCEL信號會使線程從阻塞的系統(tǒng)調(diào)用中退出,并置EINTR錯誤碼,因此可以在需要作為Cancelation-point的系統(tǒng)調(diào)用前后調(diào)用pthread_testcancel(),從而達到POSIX標準所要求的目標,即如下代碼段:

pthread_testcancel();
    retcode = read(fd, buffer, length);
    pthread_testcancel();





2.4 程序設(shè)計方面的考慮
如果線程處于無限循環(huán)中,且循環(huán)體內(nèi)沒有執(zhí)行至取消點的必然路徑,則線程無法由外部其他線程的取消請求而終止。因此在這樣的循環(huán)體的必經(jīng)路徑上應(yīng)該加入pthread_testcancel()調(diào)用。

2.5 與線程取消相關(guān)的pthread函數(shù)
int pthread_cancel(pthread_t thread)
發(fā)送終止信號給thread線程,如果成功則返回0,否則為非0值。發(fā)送成功并不意味著thread會終止。

int pthread_setcancelstate(int state, int *oldstate)
設(shè)置本線程對Cancel信號的反應(yīng),state有兩種值:PTHREAD_CANCEL_ENABLE(缺。┖蚉THREAD_CANCEL_DISABLE,分別表示收到信號后設(shè)為CANCLED狀態(tài)和忽略CANCEL信號繼續(xù)運行;old_state如果不為NULL則存入原來的Cancel狀態(tài)以便恢復(fù)。

int pthread_setcanceltype(int type, int *oldtype)
設(shè)置本線程取消動作的執(zhí)行時機,type由兩種取值:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,僅當(dāng)Cancel狀態(tài)為Enable時有效,分別表示收到信號后繼續(xù)運行至下一個取消點再退出和立即執(zhí)行取消動作(退出);oldtype如果不為NULL則存入運來的取消動作類型值。

void pthread_testcancel(void)
檢查本線程是否處于Canceld狀態(tài),如果是,則進行取消動作,否則直接返回。

關(guān)于作者
楊沙洲,男,現(xiàn)攻讀國防科大計算機學(xué)院計算機軟件方向博士學(xué)位。您可以通過電子郵件pubb@163.net 跟他聯(lián)系。
(c) Copyright IBM Corp. 2001, (c) Copyright IBM China 2001, All Right Reserved
  關(guān)于 IBM  |  隱私條約  |  使用條款  |  聯(lián)系 IBM

-------------------------------------------------------------------------
Posix線程編程指南(2)   
   
內(nèi)容:

一. 概念及作用
二. 創(chuàng)建和注銷
三. 訪問
四. 使用范例
關(guān)于作者


相關(guān)內(nèi)容:

(1) 線程創(chuàng)建與取消  




線程私有數(shù)據(jù)
楊沙洲(pubb@163.net)
2001 年 10 月

這是一個關(guān)于Posix線程編程的專欄。作者在闡明概念的基礎(chǔ)上,將向您詳細講述Posix線程庫API。本文是第二篇將向您講述線程的私有數(shù)據(jù)。
一.概念及作用
在單線程程序中,我們經(jīng)常要用到"全局變量"以實現(xiàn)多個函數(shù)間共享數(shù)據(jù)。在多線程環(huán)境下,由于數(shù)據(jù)空間是共享的,因此全局變量也為所有線程所共有。但有時應(yīng)用程序設(shè)計中有必要提供線程私有的全局變量,僅在某個線程中有效,但卻可以跨多個函數(shù)訪問,比如程序可能需要每個線程維護一個鏈表,而使用相同的函數(shù)操作,最簡單的辦法就是使用同名而不同變量地址的線程相關(guān)數(shù)據(jù)結(jié)構(gòu)。這樣的數(shù)據(jù)結(jié)構(gòu)可以由Posix線程庫維護,稱為線程私有數(shù)據(jù)(Thread-specific Data,或TSD)。

二.創(chuàng)建和注銷
Posix定義了兩個API分別用來創(chuàng)建和注銷TSD:

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

該函數(shù)從TSD池中分配一項,將其值賦給key供以后訪問使用。如果destr_function不為空,在線程退出(pthread_exit())時將以key所關(guān)聯(lián)的數(shù)據(jù)為參數(shù)調(diào)用destr_function(),以釋放分配的緩沖區(qū)。

不論哪個線程調(diào)用pthread_key_create(),所創(chuàng)建的key都是所有線程可訪問的,但各個線程可根據(jù)自己的需要往key中填入不同的值,這就相當(dāng)于提供了一個同名而不同值的全局變量。在LinuxThreads的實現(xiàn)中,TSD池用一個結(jié)構(gòu)數(shù)組表示:

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

創(chuàng)建一個TSD就相當(dāng)于將結(jié)構(gòu)數(shù)組中的某一項設(shè)置為"in_use",并將其索引返回給*key,然后設(shè)置destructor函數(shù)為destr_function。

注銷一個TSD采用如下API:

int pthread_key_delete(pthread_key_t key)

這個函數(shù)并不檢查當(dāng)前是否有線程正使用該TSD,也不會調(diào)用清理函數(shù)(destr_function),而只是將TSD釋放以供下一次調(diào)用pthread_key_create()使用。在LinuxThreads中,它還會將與之相關(guān)的線程數(shù)據(jù)項設(shè)為NULL(見"訪問")。

三.訪問
TSD的讀寫都通過專門的Posix Thread函數(shù)進行,其API定義如下:

int  pthread_setspecific(pthread_key_t  key,  const   void  *pointer)
void * pthread_getspecific(pthread_key_t key)






寫入(pthread_setspecific())時,將pointer的值(不是所指的內(nèi)容)與key相關(guān)聯(lián),而相應(yīng)的讀出函數(shù)則將與key相關(guān)聯(lián)的數(shù)據(jù)讀出來。數(shù)據(jù)類型都設(shè)為void *,因此可以指向任何類型的數(shù)據(jù)。

在LinuxThreads中,使用了一個位于線程描述結(jié)構(gòu)(_pthread_descr_struct)中的二維void *指針數(shù)組來存放與key關(guān)聯(lián)的數(shù)據(jù),數(shù)組大小由以下幾個宏來說明:

#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_MAX為1024,因此一維數(shù)組大小為32。而具體存放的位置由key值經(jīng)過以下計算得到:
idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE
idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE





也就是說,數(shù)據(jù)存放與一個32×32的稀疏矩陣中。同樣,訪問的時候也由key值經(jīng)過類似計算得到數(shù)據(jù)所在位置索引,再取出其中內(nèi)容返回。

四.使用范例
以下這個例子沒有什么實際意義,只是說明如何使用,以及能夠使用這一機制達到存儲線程私有數(shù)據(jù)的目的。

#include <stdio.h>
#include <pthread.h>

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;
}





給例程創(chuàng)建兩個線程分別設(shè)置同一個線程私有數(shù)據(jù)為自己的線程ID,為了檢驗其私有性,程序錯開了兩個線程私有數(shù)據(jù)的寫入和讀出的時間,從程序運行結(jié)果可以看出,兩個線程對TSD的修改互不干擾。同時,當(dāng)線程退出時,清理函數(shù)會自動執(zhí)行,參數(shù)為tid。

論壇徽章:
0
4 [報告]
發(fā)表于 2008-06-26 15:24 |只看該作者
Posix線程編程指南(3)   


   
內(nèi)容:

一. 互斥鎖
二. 條件變量
三. 信號燈
四. 異步信號
五. 其他同步方式
關(guān)于作者


相關(guān)內(nèi)容:

(1) 線程創(chuàng)建與取消  
(2) 線程私有數(shù)據(jù)  




線程同步
楊沙洲(pubb@163.net)
2001 年 10 月

這是一個關(guān)于Posix線程編程的專欄。作者在闡明概念的基礎(chǔ)上,將向您詳細講述Posix線程庫API。本文是第三篇,將向您講述線程同步。
一.互斥鎖
盡管在Posix Thread中同樣可以使用IPC的信號量機制來實現(xiàn)互斥鎖mutex功能,但顯然semphore的功能過于強大了,在Posix Thread中定義了另外一套專門用于線程同步的mutex函數(shù)。

1. 創(chuàng)建和銷毀
有兩種方法創(chuàng)建互斥鎖,靜態(tài)方式和動態(tài)方式。POSIX定義了一個宏P(guān)THREAD_MUTEX_INITIALIZER來靜態(tài)初始化互斥鎖,方法如下:
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
在LinuxThreads實現(xiàn)中,pthread_mutex_t是一個結(jié)構(gòu),而PTHREAD_MUTEX_INITIALIZER則是一個結(jié)構(gòu)常量。

動態(tài)方式是采用pthread_mutex_init()函數(shù)來初始化互斥鎖,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)
銷毀一個互斥鎖即意味著釋放它所占用的資源,且要求鎖當(dāng)前處于開放狀態(tài)。由于在Linux中,互斥鎖并不占用任何資源,因此LinuxThreads中的pthread_mutex_destroy()除了檢查鎖狀態(tài)以外(鎖定狀態(tài)則返回EBUSY)沒有其他動作。

2. 互斥鎖屬性
互斥鎖的屬性在創(chuàng)建鎖的時候指定,在LinuxThreads實現(xiàn)中僅有一個鎖類型屬性,不同的鎖類型在試圖對一個已經(jīng)被鎖定的互斥鎖加鎖時表現(xiàn)不同。當(dāng)前(glibc2.2.3,linuxthreads0.9)有四個值可供選擇:

PTHREAD_MUTEX_TIMED_NP,這是缺省值,也就是普通鎖。當(dāng)一個線程加鎖以后,其余請求鎖的線程將形成一個等待隊列,并在解鎖后按優(yōu)先級獲得鎖。這種鎖策略保證了資源分配的公平性。
PTHREAD_MUTEX_RECURSIVE_NP,嵌套鎖,允許同一個線程對同一個鎖成功獲得多次,并通過多次unlock解鎖。如果是不同線程請求,則在加鎖線程解鎖時重新競爭。
PTHREAD_MUTEX_ERRORCHECK_NP,檢錯鎖,如果同一個線程請求同一個鎖,則返回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP類型動作相同。這樣就保證當(dāng)不允許多次加鎖時不會出現(xiàn)最簡單情況下的死鎖。
PTHREAD_MUTEX_ADAPTIVE_NP,適應(yīng)鎖,動作最簡單的鎖類型,僅等待解鎖后重新競爭。



3. 鎖操作
鎖操作主要包括加鎖pthread_mutex_lock()、解鎖pthread_mutex_unlock()和測試加鎖pthread_mutex_trylock()三個,不論哪種類型的鎖,都不可能被兩個不同的線程同時得到,而必須等待解鎖。對于普通鎖和適應(yīng)鎖類型,解鎖者可以是同進程內(nèi)任何線程;而檢錯鎖則必須由加鎖者解鎖才有效,否則返回EPERM;對于嵌套鎖,文檔和實現(xiàn)要求必須由加鎖者解鎖,但實驗結(jié)果表明并沒有這種限制,這個不同目前還沒有得到解釋。在同一進程中的線程,如果加鎖后沒有解鎖,則任何其他線程都無法再獲得鎖。

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()類似,不同的是在鎖已經(jīng)被占據(jù)時返回EBUSY而不是掛起等待。

4. 其他
POSIX線程鎖機制的Linux實現(xiàn)都不是取消點,因此,延遲取消類型的線程不會因收到取消信號而離開加鎖等待。值得注意的是,如果線程在加鎖后解鎖前被取消,鎖將永遠保持鎖定狀態(tài),因此如果在關(guān)鍵區(qū)段內(nèi)有取消點存在,或者設(shè)置了異步取消類型,則必須在退出回調(diào)函數(shù)中解鎖。

這個鎖機制同時也不是異步信號安全的,也就是說,不應(yīng)該在信號處理過程中使用互斥鎖,否則容易造成死鎖。

二.條件變量
條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動作:一個線程等待"條件變量的條件成立"而掛起;另一個線程使"條件成立"(給出條件成立信號)。為了防止競爭,條件變量的使用總是和一個互斥鎖結(jié)合在一起。

1. 創(chuàng)建和注銷
條件變量和互斥鎖一樣,都有靜態(tài)動態(tài)兩種創(chuàng)建方式,靜態(tài)方式使用PTHREAD_COND_INITIALIZER常量,如下:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER

動態(tài)方式調(diào)用pthread_cond_init()函數(shù),API定義如下:
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)

盡管POSIX標準中為條件變量定義了屬性,但在LinuxThreads中沒有實現(xiàn),因此cond_attr值通常為NULL,且被忽略。

注銷一個條件變量需要調(diào)用pthread_cond_destroy(),只有在沒有線程在該條件變量上等待的時候才能注銷這個條件變量,否則返回EBUSY。因為Linux實現(xiàn)的條件變量沒有分配什么資源,所以注銷動作只包括檢查是否有等待線程。API定義如下:
int pthread_cond_destroy(pthread_cond_t *cond)

2. 等待和激發(fā)
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(),其中計時等待方式如果在給定時刻前條件沒有滿足,則返回ETIMEOUT,結(jié)束等待,其中abstime以與time()系統(tǒng)調(diào)用相同意義的絕對時間形式出現(xiàn),0表示格林尼治時間1970年1月1日0時0分0秒。

無論哪種等待方式,都必須和一個互斥鎖配合,以防止多個線程同時請求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的競爭條件(Race Condition)。mutex互斥鎖必須是普通鎖(PTHREAD_MUTEX_TIMED_NP)或者適應(yīng)鎖(PTHREAD_MUTEX_ADAPTIVE_NP),且在調(diào)用pthread_cond_wait()前必須由本線程加鎖(pthread_mutex_lock()),而在更新條件等待隊列以前,mutex保持鎖定狀態(tài),并在線程掛起進入等待前解鎖。在條件滿足從而離開pthread_cond_wait()之前,mutex將被重新加鎖,以與進入pthread_cond_wait()前的加鎖動作對應(yīng)。

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

3. 其他
pthread_cond_wait()和pthread_cond_timedwait()都被實現(xiàn)為取消點,因此,在該處等待的線程將立即重新運行,在重新鎖定mutex后離開pthread_cond_wait(),然后執(zhí)行取消動作。也就是說如果pthread_cond_wait()被取消,mutex是保持鎖定狀態(tài)的,因而需要定義退出回調(diào)函數(shù)來為其解鎖。

以下示例集中演示了互斥鎖和條件變量的結(jié)合使用,以及取消對于條件等待動作的影響。在例子中,有兩個線程被啟動,并等待同一個條件變量,如果不使用退出回調(diào)函數(shù)(見范例中的注釋部分),則tid2將在pthread_mutex_lock()處永久等待。如果使用回調(diào)函數(shù),則tid2的條件等待及主線程的條件激發(fā)都能正常工作。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

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);
}





如果不做注釋5的pthread_cancel()動作,即使沒有那些sleep()延時操作,child1和child2都能正常工作。注釋3和注釋4的延遲使得child1有時間完成取消動作,從而使child2能在child1退出之后進入請求鎖操作。如果沒有注釋1和注釋2的回調(diào)函數(shù)定義,系統(tǒng)將掛起在child2請求鎖的地方;而如果同時也不做注釋3和注釋4的延時,child2能在child1完成取消動作以前得到控制,從而順利執(zhí)行申請鎖的操作,但卻可能掛起在pthread_cond_wait()中,因為其中也有申請mutex的操作。child1函數(shù)給出的是標準的條件變量的使用方式:回調(diào)函數(shù)保護,等待條件前鎖定,pthread_cond_wait()返回后解鎖。

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

三.信號燈
信號燈與互斥鎖和條件變量的主要不同在于"燈"的概念,燈亮則意味著資源可用,燈滅則意味著不可用。如果說后兩中同步方式側(cè)重于"等待"操作,即資源不可用的話,信號燈機制則側(cè)重于點燈,即告知資源可用;沒有等待線程的解鎖或激發(fā)條件都是沒有意義的,而沒有等待燈亮的線程的點燈操作則有效,且能保持燈亮狀態(tài)。當(dāng)然,這樣的操作原語也意味著更多的開銷。

信號燈的應(yīng)用除了燈亮/燈滅這種二元燈以外,也可以采用大于1的燈數(shù),以表示資源數(shù)大于1,這時可以稱之為多元燈。

1. 創(chuàng)建和注銷
POSIX信號燈標準定義了有名信號燈和無名信號燈兩種,但LinuxThreads的實現(xiàn)僅有無名燈,同時有名燈除了總是可用于多進程之間以外,在使用上與無名燈并沒有很大的區(qū)別,因此下面僅就無名燈進行討論。

int sem_init(sem_t *sem, int pshared, unsigned int value)
這是創(chuàng)建信號燈的API,其中value為信號燈的初值,pshared表示是否為多進程共享而不僅僅是用于一個進程。LinuxThreads沒有實現(xiàn)多進程共享信號燈,因此所有非0值的pshared輸入都將使sem_init()返回-1,且置errno為ENOSYS。初始化好的信號燈由sem變量表征,用于以下點燈、滅燈操作。

int sem_destroy(sem_t * sem)
被注銷的信號燈sem要求已沒有線程在等待該信號燈,否則返回-1,且置errno為EBUSY。除此之外,LinuxThreads的信號燈注銷函數(shù)不做其他動作。

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()的非阻塞版,如果信號燈計數(shù)大于0,則原子地減1并返回0,否則立即返回-1,errno置為EAGAIN。

3. 獲取燈值
int sem_getvalue(sem_t * sem, int * sval)
讀取sem中的燈計數(shù),存于*sval中,并返回0。

4. 其他
sem_wait()被實現(xiàn)為取消點,而且在支持原子"比較且交換"指令的體系結(jié)構(gòu)上,sem_post()是唯一能用于異步信號處理函數(shù)的POSIX異步信號安全的API。

四.異步信號
由于LinuxThreads是在核外使用核內(nèi)輕量級進程實現(xiàn)的線程,所以基于內(nèi)核的異步信號操作對于線程也是有效的。但同時,由于異步信號總是實際發(fā)往某個進程,所以無法實現(xiàn)POSIX標準所要求的"信號到達某個進程,然后再由該進程將信號分發(fā)到所有沒有阻塞該信號的線程中"原語,而是只能影響到其中一個線程。

POSIX異步信號同時也是一個標準C庫提供的功能,主要包括信號集管理(sigemptyset()、sigfillset()、sigaddset()、sigdelset()、sigismember()等)、信號處理函數(shù)安裝(sigaction())、信號阻塞控制(sigprocmask())、被阻塞信號查詢(sigpending())、信號等待(sigsuspend())等,它們與發(fā)送信號的kill()等函數(shù)配合就能實現(xiàn)進程間異步信號功能。LinuxThreads圍繞線程封裝了sigaction()和raise(),本節(jié)集中討論LinuxThreads中擴展的異步信號函數(shù),包括pthread_sigmask()、pthread_kill()和sigwait()三個函數(shù)。毫無疑問,所有POSIX異步信號函數(shù)對于線程都是可用的。

int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask)
設(shè)置線程的信號屏蔽碼,語義與sigprocmask()相同,但對不允許屏蔽的Cancel信號和不允許響應(yīng)的Restart信號進行了保護。被屏蔽的信號保存在信號隊列中,可由sigpending()函數(shù)取出。

int pthread_kill(pthread_t thread, int signo)
向thread號線程發(fā)送signo信號。實現(xiàn)中在通過thread線程號定位到對應(yīng)進程號以后使用kill()系統(tǒng)調(diào)用完成發(fā)送。

int sigwait(const sigset_t *set, int *sig)
掛起線程,等待set中指定的信號之一到達,并將到達的信號存入*sig中。POSIX標準建議在調(diào)用sigwait()等待信號以前,進程中所有線程都應(yīng)屏蔽該信號,以保證僅有sigwait()的調(diào)用者獲得該信號,因此,對于需要等待同步的異步信號,總是應(yīng)該在創(chuàng)建任何線程以前調(diào)用pthread_sigmask()屏蔽該信號的處理。而且,調(diào)用sigwait()期間,原來附接在該信號上的信號處理函數(shù)不會被調(diào)用。

如果在等待期間接收到Cancel信號,則立即退出等待,也就是說sigwait()被實現(xiàn)為取消點。

五.其他同步方式
除了上述討論的同步方式以外,其他很多進程間通信手段對于LinuxThreads也是可用的,比如基于文件系統(tǒng)的IPC(管道、Unix域Socket等)、消息隊列(Sys.V或者Posix的)、System V的信號燈等。只有一點需要注意,LinuxThreads在核內(nèi)是作為共享存儲區(qū)、共享文件系統(tǒng)屬性、共享信號處理、共享文件描述符的獨立進程看待的。



Posix線程編程指南(4)   


   
內(nèi)容:

1. 線程終止方式
2. 線程終止時的清理
3. 線程終止的同步及其返回值
4. 關(guān)于pthread_exit()和return
參考資料
關(guān)于作者


相關(guān)內(nèi)容:

(1) 線程創(chuàng)建與取消  
(2) 線程私有數(shù)據(jù)  
(3) 線程同步  




線程終止
楊沙洲(pubb@163.net)
2001 年 11 月

這是一個關(guān)于Posix線程編程的專欄。作者在闡明概念的基礎(chǔ)上,將向您詳細講述Posix線程庫API。本文是第四篇將向您講述線程中止。
1.線程終止方式
一般來說,Posix的線程終止有兩種情況:正常終止和非正常終止。線程主動調(diào)用pthread_exit()或者從線程函數(shù)中return都將使線程正常退出,這是可預(yù)見的退出方式;非正常終止是線程在其他線程的干預(yù)下,或者由于自身運行出錯(比如訪問非法地址)而退出,這種退出方式是不可預(yù)見的。

2.線程終止時的清理
不論是可預(yù)見的線程終止還是異常終止,都會存在資源釋放的問題,在不考慮因運行出錯而退出的前提下,如何保證線程終止時能順利的釋放掉自己所占用的資源,特別是鎖資源,就是一個必須考慮解決的問題。

最經(jīng)常出現(xiàn)的情形是資源獨占鎖的使用:線程為了訪問臨界資源而為其加上鎖,但在訪問過程中被外界取消,如果線程處于響應(yīng)取消狀態(tài),且采用異步方式響應(yīng),或者在打開獨占鎖以前的運行路徑上存在取消點,則該臨界資源將永遠處于鎖定狀態(tài)得不到釋放。外界取消操作是不可預(yù)見的,因此的確需要一個機制來簡化用于資源釋放的編程。

在POSIX線程API中提供了一個pthread_cleanup_push()/pthread_cleanup_pop()函數(shù)對用于自動釋放資源--從pthread_cleanup_push()的調(diào)用點到pthread_cleanup_pop()之間的程序段中的終止動作(包括調(diào)用pthread_exit()和取消點終止)都將執(zhí)行pthread_cleanup_push()所指定的清理函數(shù)。API定義如下:

void pthread_cleanup_push(void (*routine) (void  *),  void *arg)
void pthread_cleanup_pop(int execute)






pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的棧結(jié)構(gòu)管理,void routine(void *arg)函數(shù)在調(diào)用pthread_cleanup_push()時壓入清理函數(shù)棧,多次對pthread_cleanup_push()的調(diào)用將在清理函數(shù)棧中形成一個函數(shù)鏈,在執(zhí)行該函數(shù)鏈時按照壓棧的相反順序彈出。execute參數(shù)表示執(zhí)行到pthread_cleanup_pop()時是否在彈出清理函數(shù)的同時執(zhí)行該函數(shù),為0表示不執(zhí)行,非0為執(zhí)行;這個參數(shù)并不影響異常終止時清理函數(shù)的執(zhí)行。

pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式實現(xiàn)的,這是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()帶有一個"}",因此這兩個函數(shù)必須成對出現(xiàn),且必須位于程序的同一級別的代碼段中才能通過編譯。在下面的例子里,當(dāng)線程在"do some work"中終止時,將主動調(diào)用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狀態(tài),上述代碼段就有可能出錯,因為CANCEL事件有可能在pthread_cleanup_push()和pthread_mutex_lock()之間發(fā)生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之間發(fā)生,從而導(dǎo)致清理函數(shù)unlock一個并沒有加鎖的mutex變量,造成錯誤。因此,在使用清理函數(shù)的時候,都應(yīng)該暫時設(shè)置成PTHREAD_CANCEL_DEFERRED模式。為此,POSIX的Linux實現(xiàn)中還提供了一對不保證可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()擴展函數(shù),功能與以下代碼段相當(dāng):

{ int oldtype;
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
pthread_cleanup_push(routine, arg);
...
pthread_cleanup_pop(execute);
pthread_setcanceltype(oldtype, NULL);
}





3.線程終止的同步及其返回值
一般情況下,進程中各個線程的運行都是相互獨立的,線程的終止并不會通知,也不會影響其他線程,終止的線程所占用的資源也并不會隨著線程的終止而得到釋放。正如進程之間可以用wait()系統(tǒng)調(diào)用來同步終止并釋放資源一樣,線程之間也有類似機制,那就是pthread_join()函數(shù)。

void pthread_exit(void *retval)
int pthread_join(pthread_t th, void **thread_return)
int pthread_detach(pthread_t th)






pthread_join()的調(diào)用者將掛起并等待th線程終止,retval是pthread_exit()調(diào)用者線程(線程ID為th)的返回值,如果thread_return不為NULL,則*thread_return=retval。需要注意的是一個線程僅允許唯一的一個線程使用pthread_join()等待它的終止,并且被等待的線程應(yīng)該處于可join狀態(tài),即非DETACHED狀態(tài)。

如果進程中的某個線程執(zhí)行了pthread_detach(th),則th線程將處于DETACHED狀態(tài),這使得th線程在結(jié)束運行時自行釋放所占用的內(nèi)存資源,同時也無法由pthread_join()同步,pthread_detach()執(zhí)行之后,對th請求pthread_join()將返回錯誤。

一個可join的線程所占用的內(nèi)存僅當(dāng)有線程對其執(zhí)行了pthread_join()后才會釋放,因此為了避免內(nèi)存泄漏,所有線程的終止,要么已設(shè)為DETACHED,要么就需要使用pthread_join()來回收。

4.關(guān)于pthread_exit()和return
理論上說,pthread_exit()和線程宿體函數(shù)退出的功能是相同的,函數(shù)結(jié)束時會在內(nèi)部自動調(diào)用pthread_exit()來清理線程相關(guān)的資源。但實際上二者由于編譯器的處理有很大的不同。

在進程主函數(shù)(main())中調(diào)用pthread_exit(),只會使主函數(shù)所在的線程(可以說是進程的主線程)退出;而如果是return,編譯器將使其調(diào)用進程退出的代碼(如_exit()),從而導(dǎo)致進程及其所有線程結(jié)束運行。

其次,在線程宿主函數(shù)中主動調(diào)用return,如果return語句包含在pthread_cleanup_push()/pthread_cleanup_pop()對中,則不會引起清理函數(shù)的執(zhí)行,反而會導(dǎo)致segment fault。

論壇徽章:
0
5 [報告]
發(fā)表于 2008-06-26 15:25 |只看該作者
Posix線程編程指南(5)   


   
內(nèi)容:

1.獲得本線程ID
2.判斷兩個線程是否為同一線程
3.僅執(zhí)行一次的操作
4.pthread_kill_other_threads_np()
關(guān)于作者


相關(guān)內(nèi)容:

(1) 線程創(chuàng)建與取消  
(2) 線程私有數(shù)據(jù)  
(3) 線程同步  
(4) 線程終止  




雜項
楊沙洲(pubb@163.net)
2001 年 11 月

這是一個關(guān)于Posix線程編程的專欄。作者在闡明概念的基礎(chǔ)上,將向您詳細講述Posix線程庫API。本文是第五篇將向您講述pthread_self()、pthread_equal()和pthread_once()等雜項函數(shù)。
在Posix線程規(guī)范中還有幾個輔助函數(shù)難以歸類,暫且稱其為雜項函數(shù),主要包括pthread_self()、pthread_equal()和pthread_once()三個,另外還有一個LinuxThreads非可移植性擴展函數(shù)pthread_kill_other_threads_np()。本文就介紹這幾個函數(shù)的定義和使用。

1.獲得本線程ID


pthread_t pthread_self(void)

本函數(shù)返回本線程的標識符。

在LinuxThreads中,每個線程都用一個pthread_descr結(jié)構(gòu)來描述,其中包含了線程狀態(tài)、線程ID等所有需要的數(shù)據(jù)結(jié)構(gòu),此函數(shù)的實現(xiàn)就是在線程棧幀中找到本線程的pthread_descr結(jié)構(gòu),然后返回其中的p_tid項。

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

2.判斷兩個線程是否為同一線程
int pthread_equal(pthread_t thread1, pthread_t thread2)

判斷兩個線程描述符是否指向同一線程。在LinuxThreads中,線程ID相同的線程必然是同一個線程,因此,這個函數(shù)的實現(xiàn)僅僅判斷thread1和thread2是否相等。

3.僅執(zhí)行一次的操作
int pthread_once(pthread_once_t *once_control, void (*init_routine) (void))

本函數(shù)使用初值為PTHREAD_ONCE_INIT的once_control變量保證init_routine()函數(shù)在本進程執(zhí)行序列中僅執(zhí)行一次。

#include <stdio.h>
#include <pthread.h>

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()函數(shù)僅執(zhí)行一次,且究竟在哪個線程中執(zhí)行是不定的,盡管pthread_once(&once,once_run)出現(xiàn)在兩個線程中。

LinuxThreads使用互斥鎖和條件變量保證由pthread_once()指定的函數(shù)執(zhí)行且僅執(zhí)行一次,而once_control則表征是否執(zhí)行過。如果once_control的初值不是PTHREAD_ONCE_INIT(LinuxThreads定義為0),pthread_once()的行為就會不正常。在LinuxThreads中,實際"一次性函數(shù)"的執(zhí)行狀態(tài)有三種:NEVER(0)、IN_PROGRESS(1)、DONE(2),如果once初值設(shè)為1,則由于所有pthread_once()都必須等待其中一個激發(fā)"已執(zhí)行一次"信號,因此所有pthread_once()都會陷入永久的等待中;如果設(shè)為2,則表示該函數(shù)已執(zhí)行過一次,從而所有pthread_once()都會立即返回0。

4. pthread_kill_other_threads_np()
void pthread_kill_other_threads_np(void)

這個函數(shù)是LinuxThreads針對本身無法實現(xiàn)的POSIX約定而做的擴展。POSIX要求當(dāng)進程的某一個線程執(zhí)行exec*系統(tǒng)調(diào)用在進程空間中加載另一個程序時,當(dāng)前進程的所有線程都應(yīng)終止。由于LinuxThreads的局限性,該機制無法在exec中實現(xiàn),因此要求線程執(zhí)行exec前手工終止其他所有線程。pthread_kill_other_threads_np()的作用就是這個。

需要注意的是,pthread_kill_other_threads_np()并沒有通過pthread_cancel()來終止線程,而是直接向管理線程發(fā)"進程退出"信號,使所有其他線程都結(jié)束運行,而不經(jīng)過Cancel動作,當(dāng)然也不會執(zhí)行退出回調(diào)函數(shù)。盡管LinuxThreads的實驗結(jié)果與文檔說明相同,但代碼實現(xiàn)中卻是用的__pthread_sig_cancel信號來kill線程,應(yīng)該效果與執(zhí)行pthread_cancel()是一樣的,其中原因目前還不清楚。

論壇徽章:
0
6 [報告]
發(fā)表于 2008-06-26 16:06 |只看該作者

呵呵, 支持。

謝謝分享。

論壇徽章:
0
7 [報告]
發(fā)表于 2008-06-26 16:26 |只看該作者
IBM developers的那個教程很好

論壇徽章:
0
8 [報告]
發(fā)表于 2008-06-26 16:47 |只看該作者
恩,作為入門不錯~~
不過多線程也就那么幾個函數(shù),涉及同步的函數(shù)就不是線程獨有的了,UNPv2中有很多~

論壇徽章:
0
9 [報告]
發(fā)表于 2008-06-26 17:10 |只看該作者

但為什么核心級線程的效率和進程還是差不多呢?

在沒有鎖的情況下, 各跑40w數(shù)據(jù)都是1秒鐘。 有鎖的情況下,線程還比進程慢?

論壇徽章:
0
10 [報告]
發(fā)表于 2008-06-27 08:43 |只看該作者
好長...
study..
您需要登錄后才可以回帖 登錄 | 注冊

本版積分規(guī)則 發(fā)表回復(fù)

  

北京盛拓優(yōu)訊信息技術(shù)有限公司. 版權(quán)所有 京ICP備16024965號-6 北京市公安局海淀分局網(wǎng)監(jiān)中心備案編號:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報專區(qū)
中國互聯(lián)網(wǎng)協(xié)會會員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關(guān)心和支持過ChinaUnix的朋友們 轉(zhuǎn)載本站內(nèi)容請注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP