天天看點

Linux下多線程程式設計(C語言)

linux下多線程程式設計(c語言)

2.6核心開始使用nptl(native posix thread library)線程庫,這個線程庫有以下幾個目标: posix相容,都處理結果和應用,底啟動開銷,低連結開銷,與linux thread應用的二進制相容,軟硬體的可擴充能力,與c++內建等。

這裡的線程是指使用者空間的線程操作

一、線程相關操作

1.1  pthread_t

     pthread_t 在頭檔案  /usr/include/i386-linux-gnu/bits/pthreadtypes.h中定義:

  typedef unsigned long int pthread_t;

  它是一個線程的辨別符(線程id)。

    1.2 pthread_create

用來建立一個線程,它的原型為:

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

  (1)、第一個參數為指向線程辨別符的指針(線程id),第二個參數用來設定線程屬性,第三個參數是線程運作函數的起始位址,最後一個參數是運作函數的參數。

(2)、若函數線程運作函數thread不需要參數,則最後一個參數應設為空指針。

(3)、第二個參數選擇設為空指針,則将生成預設屬性的線程。(可以對其屬性進行設定和修改)

(4)、當建立線程成功時,函數傳回0,若不為0則說明建立線程失敗。

常見的錯誤傳回代碼為eagain和einval。

eagain表示系統限制建立新的線程,例如線程數目過多了;

einval表示第二個參數代表的線程屬性值非法。

(5)建立線程成功後,新建立的線程則運作參數三和參數四确定的函數,原來的線程則繼續運作下一行代碼。

(6)attr: 線程屬性包括:優先級、初始棧大小,是否應該成為一個守護線程。

預設設定,null

tidp是要建立的線程,建立成功後tipd為先線程的id

void *(* func) (void *)是一個函數指針,該函數指針的類型為void* (*)(void *)

1.3  pthread_join 和 pthread_exit  

線程退出的三種方式:

(1) 線上程建立以後,就開始運作相關的線程函數,在該線程函數運作完之後,該線程也就退出了。這是線程退出的一種方法: 運作完畢,自動退出;

       (2) 調用pthread_exit函數主動退出;

       (3) 程序終止函數exit函數,一旦結束了程序,那麼此程序中所有線程都将無條件終止。

     注意點:在預設線程屬性下,如果一個程序有很多線程在同時運作,一個線程在退出以後,目前線程所占用的資源并不會随着線程的終止而得到釋放。因為所有處在一個程序中的線程共享資源。

    線程中還有一個常用函數:pthread_join函數可以用于将目前線程挂起,,等待其他線程結束。實際上,這個函數是就是一個線程阻塞函數,調用它的函數将一直等待到被等待的線程結束為止。當函數傳回時,被等待線程的資源就被回收。

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

th: 等待線程的辨別符

thread_return:使用者定義的指針,用來存儲被等待線程的傳回值(不為null時)

成功:0

void pthread_exit(void *retval)

retval:調用者線程的傳回值,可由其他函數如pthread_join來檢索擷取。

1.4  互斥鎖相關

     互斥鎖用來保證一段時間内隻有一個線程在執行一段代碼。

必須被初始化為pthread_mutex_initializer(用于靜态配置設定的mutex,等價于 pthread_mutex_init(…, null))或者調用pthread_mutex_init。mutex也應該用pthread_mutex_destroy來銷毀。

    (1) pthread_mutex_init

     函 數pthread_mutex_init用來生成一個互斥鎖。null參數表明使用預設屬性。

    (2) pthread_mutex_lock   pthread_mutex_unlock   pthread_delay_np

   pthread_mutex_lock表示開始用互斥鎖上鎖,此後的代碼直至調用pthread_mutex_unlock為止,均被上鎖,即同一時間隻 能被一個線程調用執行。當一個線程執行到pthread_mutex_lock處時,如果該鎖此時被另一個線程使用,那此線程被阻塞,即程式将等待到另一 個線程釋放此互斥鎖。

(3) sleep是為了讓線程睡眠一段時間,讓線程釋放互斥鎖,等待另一個線程使用此鎖。

1.5取消一個線程

1.5.1  int pthread_cancel(pthread_t thread);

thread:線程的辨別符           成功:0

1.5.2  在取消線形程請求的接收端,線程可以用pthread_setcancelstate設定自己的取消狀态,

int pthread_setcancelstate(int state,int *oldstate);(是否接受取消的請求)

state:可以是pthread_cancel_enable,這個值允許線程接收取消請求;還可以是pthread_cancel_disable,它的作用是螢幕它們。

     線程以前的取消狀态可以用oldstate指針檢索出來。如果沒興趣可以傳一個null進去。

1.5.3 如果取消請求被接受了,線程會進入第二個控制層次----用pthread_setcanceltype設定取消類型。

int pthread_setcanceltype(int type,int *oldstate);

type:可以有兩種取值:

(1)一個是pthread_cancel_asynchoronous,接收到取消請求之後立刻采取行動;

(2)另一個是 pthread_cancel_deferred,在接收到取消請求之後、采取實際行動之前,先執行以下幾個函數之一:pthread_join、 pthread_cond_wait、pthread_cond_timewait、pthread_testcancel、sem_wait或 sigwait。  

1.6 擷取線程自身的id

pthread_t pthread_self(void);

1.7線程清理處理程式

void pthread_clean_push(void (*rtn)(void *),void *arg)

void pthread_clean_pop(iny excute)

清理函數的調用順序是由pthread_clean_push函數安排的。

它在下列幾種情況下執行:

1.調用pthread_exit時

2.響應取消請求時

3.用非零execute參數調用pthread_clean_pop時

如果execute參數為0,清理函數将不被調用。無論何種情況,pthread_clean_pop都将删除上次pthread_clean_push建立的清理處理程式。

如果線程使用return從例程傳回,那麼pthread_clean_push建立的清理處理程式不會被執行。

1.8 讀寫鎖:reader-writer locks

1.8.1  多個線程可以同時獲得讀鎖(reader-writer lock in read mode),但是隻有一個線程能夠獲得寫鎖(reader-writer lock in write mode)

1.8.2 讀寫鎖有三種狀态

      (1).一個或者多個線程獲得讀鎖,其他線程無法獲得寫鎖

       (2)一個線程獲得寫鎖,其他線程無法獲得讀鎖

       (3).沒有線程獲得此讀寫鎖

1.8.3類型為pthread_rwlock_t

1.8.4建立和關閉讀寫鎖

int pthread_rwlock_init(  pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr)

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

pthread_rwlock_rdlock:獲得讀鎖

pthread_rwlock_wrlock:獲得寫鎖

pthread_rwlock_unlock:釋放鎖,不管是讀鎖還是寫鎖都是調用此函數

注意具體 實作可能對同時獲得讀鎖的線程個數有限制,是以在調用 pthread_rwlock_rdlock的時候需要檢查錯誤值,而另外兩個pthread_rwlock_wrlock和 pthread_rwlock_unlock則一般不用檢查,如果我們代碼寫的正确的話。

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

  1.8.4  conditional variable:條件

a.     條件必須被mutex保護起來

b.     類型為:pthread_cond_t,必須被初始化為pthread_cond_initializer(用于靜态配置設定的條件,等價于pthread_cond_init(…, null))或者調用pthread_cond_init

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condxattr_t *restrict attr)

int pthread_cond_destroy(pthread_cond_t *cond);

c.     pthread_cond_wait 函數用于等待條件發生(=true)。pthread_cond_timedwait類似,隻是當等待逾時的時候傳回一個錯誤值etimedout。逾時 的時間用timespec結構指定。此外,兩個函數都需要傳入一個mutex用于保護條件

int pthread_cond_wait( pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,

       const struct timespec *restrict timeout);

d.     timespec結構定義如下:

struct timespec {

       time_t tv_sec;       /* seconds */

       long   tv_nsec;      /* nanoseconds */

};

注意timespec的時間是絕對時間而非相對時間,是以需要先調用gettimeofday函數獲得目前時間,再轉換成timespec結構,加上偏移量。

e.     有兩個函數用于通知線程條件被滿足(=true):

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

兩者的差別是前者會喚醒單個線程,而後者會喚醒多個線程。

1.9 (????????????????????????)

int pthread_detach(pthread_t tid);

線程或者是可彙合的 (joinable)或者是脫離的(detached)。當可彙合的線程終止時,其線程id和退出狀态将保留,直到另外一個線程調用 pthread_join。脫離的線程則像守護程序:當它終止時,所有的資源都釋放,我們不能等待它終止。如果一個線程需要知道另一個線程什麼時候終止, 最好保留第二個線程的可彙合性。pthread_detach函數将指定的線程變為脫離的。該函數通常被想脫離自己的線程調用,如:pthread_detach (pthread_self ( ));

1.10

線程安全:概念比較直覺。一般說來,一個函數被稱為線程安全的,當且僅當被多個并發線程反複調用時,它會一直産生正确的結果。

可重入:概念基本沒有比較正式的完整解釋,但是它比線程安全要求更嚴格。根據經驗,所謂“重入”,常見的情況是,程式執行到某個函數foo()時,收到信号,于是暫停目前正在執行的函數,轉到信号處理函數,而這個信号處理函數的執行過程中,又恰恰也會進入到剛剛執行的函數foo(),這樣便發生了所謂的重入。此時如果foo()能夠正确的運作,而且處理完成後,之前暫停的foo()也能夠正确運作,則說明它是可重入的。

線程安全的條件:

要 確定函數線程安全,主要需要考慮的是線程之間的共享變量。屬于同一程序的不同線程會共享程序記憶體空間中的全局區和堆,而私有的線程空間則主要包括棧和寄存 器。是以,對于同一程序的不同線程來說,每個線程的局部變量都是私有的,而全局變量、局部靜态變量、配置設定于堆的變量都是共享的。在對這些共享變量進行通路 時,如果要保證線程安全,則必須通過加鎖的方式。

可重入的判斷條件:

要確定函數可重入,需滿足一下幾個條件:

1、不在函數内部使用靜态或全局資料

2、不傳回靜态或全局資料,所有資料都由函數的調用者提供。

3、使用本地資料,或者通過制作全局資料的本地拷貝來保護全局資料。

4、不調用不可重入函數。

     可重入與線程安全并不等同,一般說來,可重入的函數一定是線程安全的,但反過來不一定成立。

比如:strtok函數是既不可重入的,也不是線程安全的;加鎖的strtok不是可重入的,但線程安全;而strtok_r既是可重入的,也是線程安全的。