閱讀138 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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。  
成功:0
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既是可重入的,也是線程安全的。
 


最後更新:2017-04-02 15:28:25

  上一篇:go 你了解多少考研數學高分複習方法?
  下一篇:go oracle中的函數使用