1. 程式人生 > >linux高級編程基礎系列:線程間通信

linux高級編程基礎系列:線程間通信

clas ++ lock 種類型 try 所有者 嘗試 .html 基礎

linux高級編程基礎系列:線程間通信

轉載:原文地址http://blog.163.com/jimking_2010/blog/static/1716015352013102510748824/

線程間通信機制:

線程是一種輕量級的進程。 進程的通信機制主要包括無名管道、有名管道、消息隊列、信號量、共享內存以及信號等。這些機制都是由linux內核來維護的,實現起來都比較復雜,而且占用大量的系統資源。 線程間的通信機制實現起來則相對簡單,主要包括互斥鎖、條件變量、讀寫鎖和線程信號等。 本文會對以上所涉及的線程間的通信機制一一展開。 互斥鎖通信機制: 1、互斥鎖基本原理:互斥鎖以排他的方式防止數據被並發修改。當多個線程共享相同的內存時,需要確保每個線程看到的數據是一樣的。如果是只讀,那麽一定是一樣的。如果是可讀可寫,在一個線程操作一個內存區域時,包含三個步驟,即讀出數據,修改數據,寫回數據。如果該線程在沒有寫回數據前,另一個線程來訪問同一個區域,如果是讀,得不到最新的數據狀態,如果是寫,則會造成數據覆蓋等問題。
互斥鎖就兩個狀態:開鎖(0),上鎖(1)。將某個共享資源和互斥鎖綁定後,對該共享資源的訪問操作如下: A】在訪問資源前,首先申請該互斥鎖,如果在開鎖狀態,則申請到該鎖對象,並立即占有該鎖(鎖定)。以防其他線程訪問修改此資源。如果該鎖處於鎖定狀態,默認阻塞等待。 B】原則上只有鎖定該互斥鎖的進程才能釋放此互斥鎖。但實際上,非鎖定的線程去解鎖也能成功。這個與鎖的條件有關,本文後續內容會詳細介紹。 互斥鎖基本操作函數如下: 功能 函數 初始化互斥鎖 pthread_mutex_init() 阻塞申請互斥鎖 pthread_mutex_lock() 釋放互斥鎖 pthread_mutex_unlock()
嘗試加鎖(非阻塞方式) pthread_mutex_trylock() 銷毀互斥鎖 pthread_mutex_destroy() 2、互斥鎖的初始化和銷毀:pthread_mutex_init()、pthread_mutex_destroy() A】頭文件:#include <pthread.h> B】函數原型:extern int pthread_mutex_init(pthread_mutex_t *__mutex,__const pthread_mutexattr_t *__mutexattr); extern int pthread_mutex_destroy(pthread_mutex_t *__mutex);
C】返回:操作成功返回0,不成功則返回非零值 D】參數: a、第一個參數為指向要初始化/銷毀的互斥鎖的指針。pthread_mutex_t即互斥量類型。在使用互斥鎖時,需在函數內定義一個這種類型的變量。其值可通過pthread_mutex_init()函數來以初始化,也可以通過使用pthread.h中定義的宏PTHREAD_MUTEX_INITIALIZER (只對靜態分配的互斥量)來初始化。如果是動態分配的互斥量,那麽釋放內存前需要用pthread_mutex_destroy,初始化用pthread_mutex_init()。 pthread.h中宏定義如下: #define PTHREAD_MUTEX_INITIALIZER { {0,} } 初始化方式如下: pthread_mutex_t p = PTHREAD_MUTEX_INITIALIZER; b、第二個參數mutexattr是指向屬性對象的指針,該屬性對象定義要初始化鎖的屬性。如果該指針為NULL,則表示使用默認屬性。鎖的屬性在本文後續部分有詳細的介紹。 3、互斥鎖的申請、釋放和嘗試解鎖:pthread_mutex_lock()、pthread_mutex_unlock()、pthread_mutex_trylock() A】函數原型:extern int pthread_mutex_lock(pthread_mutex_t *__mutex); extern int pthread_mutex_trylock(pthread_mutex_t *__mutex); extern int pthread_mutex_unlock(pthread_mutex_t *__mutex); B】返回:成功返回0,失敗返回一個錯誤編號,以指明錯誤。(pthread_mutex_unlock()未設置errno變量) 條件變量通信機制: 1、條件變量基本原理:條件變量的出現,可以彌補互斥鎖的缺陷,有些問題僅僅靠互斥鎖無法解決。但是條件變量不能單獨使用,必須配合互斥鎖一起實現對資源的互斥訪問。 例:互斥鎖無法解決的問題。 int i = 3;int j = 7; pthread A pthread_B pthread_mutex_lock(); pthread_mutex_lock() { { i++; if(i==j) j--; do_something(); } } pthread_mutex_unlock(); pthread_mutex_unlock(); —————————————————————————————————————— 上例中:兩個線程搶占互斥鎖,可能會導致pthread B中的do_something()函數永遠無法執行的情況。這是程序員不想看到的。仔細分析後,可得到線程B其實不需要一直的申請釋放鎖,其運行僅僅需要一種情況而已。在A線程滿足i == j時,通知B線程執行do_something()即可。 條件變量基本操作: 功能 函數 初始化條件變量 pthread_cond_init() 阻塞等待條件變量 pthread_cond_wait() 通知等待該條件變量的第一個線程 pthread_cond_signal() 在指定的時間之內等待條件變量 pthread_cond_timedwait() 銷毀條件變量狀態 pthread_cond_destroy() 2、條件變量的初始化和銷毀:pthread_cond_init()、pthread_cond_destroy() A】函數原型:extern int pthread_cond_init(pthread_cond_t *__restrict __cond,__const pthread_condattr_t *__restrict __cond_attr); extern int pthread_cond_destroy(pthread_cond_t *__cond); B】返回:成功返回0,失敗返回錯誤編號以指明錯誤。 C】參數:第一個參數指向要初始化或損壞的條件變量的指針,條件變量的類型為pthread_cond_t。第二個參數指向條件屬性對象的指針。該屬性對象定義要初始化的條件變量的特性,如果此變量初始化為NULL,則為默認屬性。關於條件屬性,本文後續會有詳細介紹。 3、通知等待條件變量的線程:pthread_cond_signal()、pthread_cond_broadcast() A】函數原型:extern int pthread_cond_signalpthread_cond_t *__cond); extern int pthread_cond_broadcastpthread_cond_t *__cond); B】說明: a、pthread_cond_signal()函數用於喚醒等待出現與條件變量cond相關聯的條件的第一個線程。如果cond上沒有阻塞任何線程,則此函數不起作用。如果cond阻塞了多個線程,則調度策略將確定要取消阻塞的線程。顯然,在此函數中,隱含的釋放了當前線程占用的信號量(備註:信號和信號量不是一個東西,在進程和進程通信中會詳細說明信號和信號量)。 b、pthread_cond_broadcast()函數用於喚醒等待出現與條件變量cond關聯的條件的所有線程。如果cond上沒有阻塞任何線程,則此函數不起作用。 4、等待條件變量:pthread_cond_wait()、pthread_cond_timedwait() A】函數原型:extern int pthread_cond_wait(pthread_cond_t *__restrict __cond,pthread_mutex_t *__restrict __mutex); extern int pthread_cond_timedwait( pthread_cond_t *__restrict __cond,pthread_mutex_t *__restrict __mutex ,__const struct timespec *__restrict __abstime); B】參數說明:cond是指向要等待的條件變量的指針,mutex指向與條件變量cond關聯的互斥鎖的指針。pthread_cond_wait()、pthread_cond_timedwait()函數的實現是一個先對互斥鎖進行解鎖,再加鎖的一個過程。pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)函數傳入的參數mutex用於保護條件,因為我們在調用pthread_cond_wait時,如果條件不成立我們就進入阻塞,但是進入阻塞這個期間,如果條件變量改變了的話,那我們就漏掉了這個條件。因為這個線程還沒有放到等待隊列上,所以調用pthread_cond_wait前要先鎖互斥量,即調用pthread_mutex_lock(),pthread_cond_wait在把線程放進阻塞隊列後,自動對mutex進行解鎖,使得其它線程可以獲得加鎖的權利。這樣其它線程才能對臨界資源進行訪問並在適當的時候喚醒這個阻塞的進程。當pthread_cond_wait返回的時候又自動給mutex加鎖。 Thread A:當滿足條件的時候發送一個信號。 Thread B:先給一個mutex加鎖,以便互斥訪問count的值。在一個while循環裏等待count值達到MAX_COUNT。因為當某個條件滿足時,可能會有多個線程被喚醒。所以需要判斷條件是否還滿足。pthread_cond_wait首先把調用線程放入條件變量的等待隊列,然後再釋放mutex。當函數返回時,mutex又會被加上鎖。最後對mutex解鎖,讓其他線程使用count變量。(加了寫鎖的等待就是占著茅坑不拉屎,有數據更新此操作域又執行不了寫操作,只能先解鎖咯~~~~) pthread_cond_timedwait()多了一個參數,即abstime,abstime是從1970年1月1日00:00:00以來的秒數,是一個絕對時間。等待時間到,則不阻塞,往下執行程序。timespec結構體聲明如下: struct timespec { long tv_sec; long tv_nsec; }; C】返回:如果成功,返回0,失敗則返回一個錯誤編號。 讀寫鎖通信機制: 1、讀寫鎖基本原理:在對數據的讀寫操作時,往往是讀占主要部分。為了滿足當前能夠允許多個讀出,但只允許一個寫入的需求。線程提供了讀寫鎖來實現。讀寫鎖基本原則如下: A】如果有其他線程讀數據,則允許其他線程執行讀操作,但是不允許寫操作。 B】如果有其他線程申請了寫鎖,則其他線程既不能申請讀鎖,也不能申請寫鎖 讀寫鎖基本操作函數如下: 功能 函數 初始化讀寫鎖 pthread_rwlock_init() 阻塞申請讀鎖 pthread_rwlock_rdlock() 非阻塞申請讀鎖 pthread_rwlock_tryrdlock() 阻塞申請寫鎖 pthread_rwlock_wrlock() 非阻塞申請寫鎖 pthread_rwlock_trywrlock() 釋放鎖(讀鎖和寫鎖) pthread_rwlock_unlock() 毀壞讀寫鎖 pthread_rwlock_destroy() 2、初始化/毀壞讀寫鎖:pthread_rwlock_init()、pthread_rwlock_destroy() A】函數原型:extern int pthread_rwlock_init(pthread_rwlock_t *__restrict __rwlock,__const pthread_rwlockattr_t *__restrict __attr); extern int pthread_rwlock_destroy(pthread_rwlock_t *__rwlock); B】返回:如果成功則返回0,否則返回一個錯誤編號,以指明錯誤。 C】參數說明:第一個參數指向要初始化的讀寫鎖的指針。類型為pthread_rwlock_t。第二個參數為讀寫鎖的屬性。在本文後續部分會詳細說明。 3、申請讀鎖,寫鎖和解除讀寫鎖:pthread_rwlock_rdlock()、pthread_rwlock_tryrdlock()、pthread_rwlock_wrlock()、pthread_rwlock_trywrlock()、pthread_rwlock_unlock() A】函數原型:extern int pthread_rwlock_rdlock(pthread_rwlock_t *__rwlock); extern int pthread_rwlock_tryrdlock(pthread_rwlock_t *__rwlock); extern int pthread_rwlock_wrlock(pthread_rwlock_t *__rwlock); extern int pthread_rwlock_trywrlock(pthread_rwlock_t *__rwlock); extern int pthread_rwlock_unlock(pthread_rwlock_t *__rwlock); B】說明:成功返回0,失敗則返回一個錯誤編號以表明錯誤。 調用pthread_rwlock_unlock時需註意: a、如果調用此函數來釋放讀寫鎖定rwlock上的讀鎖定,但當前在此讀寫鎖定上還保持著其他的讀鎖定,則讀鎖定將保持線程讀鎖定狀態。只不過當前線程已經不是其所有者之一。 b、如果此函數釋放讀寫鎖的最後一個讀鎖,則對象將處於沒有所有者的解鎖狀態。 c、如果調用此函數釋放讀寫鎖的寫鎖,則讀寫鎖定將處於沒有所有者的解鎖狀態。 線程信號: 線程是一種輕量級的進程,因此進程的信號同樣適用於線程。不過相對於進程信號,線程擁有與信號相關的私有數據——線程信號掩碼,則就決定了線程在信號操作時具有以下特性: A】每個線程可以先別的線程發送信號,pthread_kill()函數用來完成這一操作。 B】每個線程都可以設置自己的阻塞集合。pthread_sigmask()用來完成這一操作。類似於進程中的sigprocmask()函數。主進程創建出來的線程繼承主進程的掩碼。 C】每個線程需要設置針對某信號的處理方式,但同一個進程中對某信號的處理方式只能有一個有效,即最後一次設置的處理方式。即在所有的線程裏,同一信號在任何線程裏的對該信號的處理一定相同 D】如果別的進程向當前進程發來一個信號,具體由哪個形成去處理,是未知的。 1、向指定的線程發送信號:pthread_kill() A】函數原型:extern int pthread_kill(pthread_t __threadid,int __signo) B】參數說明:threadid是目標線程,而且是同一個進程內的線程,sigo是要發送的信號。pthread_kill()函數用於請求將信號傳送給線程,調用進程中,信號將被異步定向到線程。如果signo為0,就會進行錯誤檢查而不發送信號。成功完成後,pthread_kill()將返回0。否則會返回一個錯誤編號,用於指明錯誤(未設置errno變量)。 該函數的幫助文檔:http://www.man7.org/linux/man-pages/man3/pthread_kill.3.html 2、調用線程的信號掩碼:pthread_sigmask() A】函數原型:extern int pthread_sigmask(int __how,__const __sigset_t *restrict __newmask,__sigset_t *restrict __oldmask); B】說明:函數類似於進程信號中的sigprocmask()函數。pthread_sigmask()用來檢查或更改線程的信號掩碼。(此部分可參考進程和進程通信 信號部分內容)。 C】參數:第一個參數how定義如何更改調用線程的信號掩碼。其合法值有以下三個(此小點內容與sigprocmask()重復) #define SIG_BLOCK 0 #define SIG_UNBLOCK 1 #define SIG_SETMASK 2 宏說明: SIG_BLOCK:將第2個參數所描述的集合添加到當前進程阻塞的信號集中。 SIG_UNBLOCK:將第2個參數所描述的集合從當前進程阻塞信號集中刪除。 SIG_SETMASK:不管之前的阻塞信號,僅設置當前的進程阻塞的集合為第2個參數描述的對象。 如果newmask值是個空指針,則參數how沒有意義,且不會更改線程的阻塞信號集,因此該調用可以用於查詢當前受阻塞的信號。 D】返回:執行成功後,返回0,失敗後返回錯誤編號來指明錯誤(未設置errno變量),另外,如果由於某種原因pthread_sigmask()失敗,那麽線程的信號掩碼將不會變化。 【附言】 上面提到的互斥鎖的屬性,條件變量的屬性和讀寫鎖的屬性三塊內容,在下一篇文章中整理。放置於線程和線程通信目錄下,請關註。

linux高級編程基礎系列:線程間通信