?? linux(1).txt
字號(hào):
線程的數(shù)據(jù)處理
和進(jìn)程相比,線程的最大優(yōu)點(diǎn)之一是數(shù)據(jù)的共享性,各個(gè)進(jìn)程共享父進(jìn)程處沿襲的數(shù)據(jù)段,
可以方便的獲得、修改數(shù)據(jù)。但這也給多線程編程帶來了許多問題。我們必須當(dāng)心有多個(gè)不同的進(jìn)程訪問
相同的變量。許多函數(shù)是不可重入的,即同時(shí)不能運(yùn)行一個(gè)函數(shù)的多個(gè)拷貝(除非使用不同的數(shù)據(jù)段)。
在函數(shù)中聲明的靜態(tài)變量常常帶來問題,函數(shù)的返回值也會(huì)有問題。因?yàn)槿绻祷氐氖呛瘮?shù)內(nèi)部靜態(tài)聲明
的空間的地址,則在一個(gè)線程調(diào)用該函數(shù)得到地址后使用該地址指向的數(shù)據(jù)時(shí),別的線程可能調(diào)用此函數(shù)
并修改了這一段數(shù)據(jù)。在進(jìn)程中共享的變量必須用關(guān)鍵字volatile來定義,這是為了防止編譯器在優(yōu)化時(shí)
(如gcc中使用-OX參數(shù))改變它們的使用方式。為了保護(hù)變量,我們必須使用信號(hào)量、互斥等方法來保證
我們對(duì)變量的正確使用。下面,我們就逐步介紹處理線程數(shù)據(jù)時(shí)的有關(guān)知識(shí)。
1、線程數(shù)據(jù)
在單線程的程序里,有兩種基本的數(shù)據(jù):全局變量和局部變量。但在多線程程序里,
還有第三種數(shù)據(jù)類型:線程數(shù)據(jù)(TSD: Thread-Specific Data)。它和全局變量很象,在線程內(nèi)部,
各個(gè)函數(shù)可以象使用全局變量一樣調(diào)用它,但它對(duì)線程外部的其它線程是不可見的。這種數(shù)據(jù)的必要性
是顯而易見的。例如我們常見的變量errno,它返回標(biāo)準(zhǔn)的出錯(cuò)信息。它顯然不能是一個(gè)局部變量,
幾乎每個(gè)函數(shù)都應(yīng)該可以調(diào)用它;但它又不能是一個(gè)全局變量,否則在A線程里輸出的很可能是B線程的
出錯(cuò)信息。要實(shí)現(xiàn)諸如此類的變量,我們就必須使用線程數(shù)據(jù)。我們?yōu)槊總€(gè)線程數(shù)據(jù)創(chuàng)建一個(gè)鍵,它和
這個(gè)鍵相關(guān)聯(lián),在各個(gè)線程里,都使用這個(gè)鍵來指代線程數(shù)據(jù),但在不同的線程里,這個(gè)鍵代表的數(shù)據(jù)是
不同的,在同一個(gè)線程里,它代表同樣的數(shù)據(jù)內(nèi)容。
和線程數(shù)據(jù)相關(guān)的函數(shù)主要有4個(gè):創(chuàng)建一個(gè)鍵;為一個(gè)鍵指定線程數(shù)據(jù);從一個(gè)鍵讀取線程數(shù)據(jù);
刪除鍵。
創(chuàng)建鍵的函數(shù)原型為:
extern int pthread_key_create __P ((pthread_key_t *__key,void (*__destr_function) (void *)));
第一個(gè)參數(shù)為指向一個(gè)鍵值的指針,第二個(gè)參數(shù)指明了一個(gè)destructor函數(shù),如果這個(gè)參數(shù)不為空,
那么當(dāng)每個(gè)線程結(jié)束時(shí),系統(tǒng)將調(diào)用這個(gè)函數(shù)來釋放綁定在這個(gè)鍵上的內(nèi)存塊。這個(gè)函數(shù)常和函數(shù)
pthread_once ((pthread_once_t*once_control, void (*initroutine) (void)))一起使用,為了讓這
個(gè)鍵只被創(chuàng)建一次。函數(shù)pthread_once聲明一個(gè)初始化函數(shù),第一次調(diào)用pthread_once時(shí)它執(zhí)行這個(gè)函數(shù)
,以后的調(diào)用將被它忽略。
在下面的例子中,我們創(chuàng)建一個(gè)鍵,并將它和某個(gè)數(shù)據(jù)相關(guān)聯(lián)。我們要定義一個(gè)函數(shù)createWindow,
這個(gè)函數(shù)定義一個(gè)圖形窗口(數(shù)據(jù)類型為Fl_Window *,這是圖形界面開發(fā)工具FLTK中的數(shù)據(jù)類型)。
由于各個(gè)線程都會(huì)調(diào)用這個(gè)函數(shù),所以我們使用線程數(shù)據(jù)。
/* 聲明一個(gè)鍵*/
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指向一個(gè)新建立的窗口*/
win=new Fl_Window( 0, 0, 100, 100, "MyWindow");
/* 對(duì)此窗口作一些可能的設(shè)置工作,如大小、位置、名稱等*/
setWindow(win);
/* 將窗口指針值綁定在鍵myWinKey上*/
pthread_setpecific ( myWinKey, win);
}
/* 函數(shù) createMyKey,創(chuàng)建一個(gè)鍵,并指定了destructor */
void createMyKey ( void ) {
pthread_keycreate(&myWinKey, freeWinKey);
}
/* 函數(shù) freeWinKey,釋放空間*/
void freeWinKey ( Fl_Window * win){
delete win;
}
這樣,在不同的線程中調(diào)用函數(shù)createMyWin,都可以得到在線程內(nèi)部均可見的窗口變量,
這個(gè)變量通過函數(shù)pthread_getspecific得到。在上面的例子中,我們已經(jīng)使用了函數(shù)
pthread_setspecific來將線程數(shù)據(jù)和一個(gè)鍵綁定在一起。這兩個(gè)函數(shù)的原型如下:
extern int pthread_setspecific __P ((pthread_key_t __key,__const void *__pointer));
extern void *pthread_getspecific __P ((pthread_key_t __key));
這兩個(gè)函數(shù)的參數(shù)意義和使用方法是顯而易見的。要注意的是,用pthread_setspecific為一
個(gè)鍵指定新的線程數(shù)據(jù)時(shí),必須自己釋放原有的線程數(shù)據(jù)以回收空間。這個(gè)過程函數(shù)
pthread_key_delete用來刪除一個(gè)鍵,這個(gè)鍵占用的內(nèi)存將被釋放,但同樣要注意的是,
它只釋放鍵占用的內(nèi)存,并不釋放該鍵關(guān)聯(lián)的線程數(shù)據(jù)所占用的內(nèi)存資源,而
且它也不會(huì)觸發(fā)函數(shù)pthread_key_create中定義的destructor函數(shù)。線程數(shù)據(jù)的釋放必須在釋放鍵之前
完成。
2、互斥鎖
互斥鎖用來保證一段時(shí)間內(nèi)只有一個(gè)線程在執(zhí)行一段代碼。必要性顯而易見:假設(shè)各個(gè)線程向同一個(gè)
文件順序?qū)懭霐?shù)據(jù),最后得到的結(jié)果一定是災(zāi)難性的。
我們先看下面一段代碼。這是一個(gè)讀/寫程序,它們公用一個(gè)緩沖區(qū),并且我們假定一個(gè)緩沖區(qū)只能
保存一條信息。即緩沖區(qū)只有兩個(gè)狀態(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;
/* 定義延遲時(shí)間*/
delay.tv_sec = 2;
delay.tv_nec = 0;
/* 用默認(rèn)屬性初始化一個(gè)互斥鎖對(duì)象*/
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ù)類型,其中包含一個(gè)系統(tǒng)分配的
屬性對(duì)象。函數(shù)pthread_mutex_init用來生成一個(gè)互斥鎖。NULL參數(shù)表明使用默認(rèn)屬性。如果需要聲明特
定屬性的互斥鎖,須調(diào)用函數(shù)pthread_mutexattr_init。函數(shù)pthread_mutexattr_setpshared和函數(shù)
pthread_mutexattr_settype用來設(shè)置互斥鎖屬性。前一個(gè)函數(shù)設(shè)置屬性pshared,它有兩個(gè)取值,
PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用來不同進(jìn)程中的線程同步,
后者用于同步本進(jìn)程的不同線程。在上面的例子中,我們使用的是默認(rèn)屬性PTHREAD_PROCESS_ PRIVATE。
后者用來設(shè)置互斥鎖類型,可選的類型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、
PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它們分別定義了不同的上所、解鎖機(jī)制,一般情
況下,選用最后一個(gè)默認(rèn)屬性。
pthread_mutex_lock聲明開始用互斥鎖上鎖,此后的代碼直至調(diào)用pthread_mutex_unlock為止,
均被上鎖,即同一時(shí)間只能被一個(gè)線程調(diào)用執(zhí)行。當(dāng)一個(gè)線程執(zhí)行到pthread_mutex_lock處時(shí),
如果該鎖此時(shí)被另一個(gè)線程使用,那此線程被阻塞,即程序?qū)⒌却搅硪粋€(gè)線程釋放此互斥鎖。
在上面的例子中,我們使用了pthread_delay_np函數(shù),讓線程睡眠一段時(shí)間,就是為了防止一個(gè)線程始終
占據(jù)此函數(shù)。
上面的例子非常簡(jiǎn)單,就不再介紹了,需要提出的是在使用互斥鎖的過程中很有可能會(huì)出現(xiàn)死鎖:
兩個(gè)線程試圖同時(shí)占用兩個(gè)資源,并按不同的次序鎖定相應(yīng)的互斥鎖,例如兩個(gè)線程都需要鎖定互斥鎖
1和互斥鎖2,a線程先鎖定互斥鎖1,b線程先鎖定互斥鎖2,這時(shí)就出現(xiàn)了死鎖。此時(shí)我們可以使用函數(shù)
pthread_mutex_trylock,它是函數(shù)pthread_mutex_lock的非阻塞版本,當(dāng)它發(fā)現(xiàn)死鎖不可避免時(shí),
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -