線程間無需特別的手段進行通訊,由於線程間能夠共享數據結構,也就是一個全局變量能夠被兩個線程同時使用。 不過要注意的是線程間須要作好同步,通常用 mutex。 能夠參考一些比較新的 UNIX/Linux 編程的書,都會提到 Posix 線程編程,好比《UNIX環境高級編程(第二版)》、《UNIX系統編程》等等。 Linux 的消息屬於 IPC,也就是進程間通訊,線程用不上。編程
使用多線程的理由之一是和進程相比,它是一種很是」節儉」的多任務操做方式。緩存
咱們知道,在 Linux 系統下,啓動一個新的進程必須分配給它獨立的地址空間,創建衆多的數據表來維護它的代碼段、堆棧段和數據段,這是一種」昂貴」的多任務工做方式。 而運行於一個進程中的多個線程,它們彼此之間使用相同的地址空間,共享大部分數據,啓動一個線程所花費的空間遠遠小於啓動一個進程所花費的空間,並且,線程間彼此切換所需的時間也遠遠小於進程間切換所須要的時間。markdown
使用多線程的理由之二是線程間方便的通訊機制。數據結構
對不一樣進程來講,它們具備獨立的數據空間,要進行數據的傳遞只能經過通訊的方式進行,這種方式不只費時,並且很不方便。 線程則否則,因爲同一進程下的線程之間共享數據空間,因此一個線程的數據能夠直接爲其它線程所用,這不只快捷,並且方便。 固然,數據的共享也帶來其餘一些問題,有的變量不能同時被兩個線程所修改,有的子程序中聲明爲 static 的數據更有可能給多線程程序帶來災難性的打擊,這些正是編寫多線程程序時最須要注意的地方。多線程
Linux 用 pthread_kill 對線程發信號。函數
Windows 用 PostThreadMessage 進行線程間通訊,但實際上極少用這種方法。仍是利用同步多一些 LINUX 下的同步和 Windows 原理都是同樣的。不過 Linux 下的 singal 中斷也很好用。post
用好信號量,共享資源就能夠了。測試
互斥鎖,是一種信號量,經常使用來防止兩個進程或線程在同一時刻訪問相同的共享資源。優化
須要的頭文件:pthread.hspa
互斥鎖標識符:pthread_mutex_t
互斥鎖初始化:
函數原型: int pthread_mutex_init (pthread_mutex_t* mutex,const pthread_mutexattr_t* mutexattr);
函數傳入值: mutex:互斥鎖。
mutexattr:
函數返回值:成功:0;出錯:-1
互斥操做函數
函數傳入值:mutex:互斥鎖。
函數返回值:成功:0;出錯:-1
使用形式:
pthread_mutex_t mutex; pthread_mutex_init (&mutex, NULL); /*定義*/ ... pthread_mutex_lock(&mutex); /*獲取互斥鎖*/ ... /*臨界資源*/ pthread_mutex_unlock(&mutex); /*釋放互斥鎖*/
若是一個線程已經給一個互斥量上鎖了,後來在操做的過程當中又再次調用了該上鎖的操做,那麼該線程將會無限阻塞在這個地方,從而致使死鎖。這就須要互斥量的屬性。
互斥量分爲下面三種:
互斥量的屬性類型爲pthread_mutexattr_t。 聲明後調用pthread_mutexattr_init()來建立該互斥量。而後調用 pthread_mutexattr_settype來設置屬性。 格式以下:int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int kind);
第一個參數attr,就是前面聲明的屬性變量;第二個參數kind,就是咱們要設置的屬性類型。他有下面幾個選項:
下面給出一個使用屬性的簡單過程:
pthread_mutex_t mutex; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE_NP); pthread_mutex_init(&mutex,&attr); pthread_mutex_destroy(&attr);
前面咱們提到在調用pthread_mutex_lock()的時候,若是此時mutex已經被其餘線程上鎖,那麼該操做將會一直阻塞在這個地方。若是咱們此時不想一直阻塞在這個地方,那麼能夠調用下面函數:pthread_mutex_trylock。
若是此時互斥量沒有被上鎖,那麼pthread_mutex_trylock將會返回0,並會對該互斥量上鎖。若是互斥量已經被上鎖,那麼會馬上返回EBUSY。
須要的頭文件:pthread.h
條件變量標識符:pthread_cond_t
互斥鎖的存在問題:
互斥鎖一個明顯的缺點是它只有兩種狀態:鎖定和非鎖定。設想一種簡單情景:多個線程訪問同一個共享資源時,並不知道什麼時候應該使用共享資源,若是在臨界區裏 加入判斷語句,或者能夠有效,但一來效率不高,二來複雜環境下就難以編寫了,這是咱們須要一個結構,能在條件成立時觸發相應線程,進行變量修改和訪問。
條件變量:
條件變量經過容許線程阻塞和等待另外一個線程發送信號的方法彌補了互斥鎖的不足,它常和互斥鎖一塊兒使用。使用時,條件變量被用來阻塞一個線程,當條件不知足時,線程每每解開相應的互斥鎖並等待條件發生變化。一旦其它的某個線程改變了條件變量,它將通知相應的條件變量喚醒一個或多個正被此條件變量阻塞的線程。 這些線程將從新鎖定互斥鎖並從新測試條件是否知足。
條件變量的相關函數
詳細說明
建立和註銷
條件變量和互斥鎖同樣,都有靜態動態兩種建立方式
靜態方式
靜態方式使用PTHREAD_COND_INITIALIZER常量,以下:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER
動態方式
動態方式調用pthread_cond_init()函數,API定義以下:
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)
儘管POSIX標準中爲條件變量定義了屬性,但在LinuxThreads中沒有實現,所以cond_attr值一般爲NULL,且被忽略。
註銷一個條件變量須要調用pthread_cond_destroy(),只有在沒有線程在該條件變量上等待的時候才能註銷這個條件變量,不然返回 EBUSY。由於Linux實現的條件變量沒有分配什麼資源,因此註銷動做只包括檢查是否有等待線程。API定義以下:int pthread_cond_destroy(pthread_cond_t *cond)
等待和激發
等待
等待條件有兩種方式:無條件等待pthread_cond_wait()和計時等待pthread_cond_timedwait(),其中計時等待方式 若是在給定時刻前條件沒有知足,則返回ETIMEOUT,結束等待,其中abstime以與time()系統調用相贊成義的絕對時間形式出現,0表示格林 尼治時間1970年1月1日0時0分0秒。
不管哪一種等待方式,都必須和一個互斥鎖配合,以防止多個線程同時請求pthread_cond_wait()(或 pthread_cond_timedwait(),下同)的競爭條件(Race Condition)。mutex互斥鎖必須是普通鎖(PTHREAD_MUTEX_TIMED_NP)或者適應鎖 (PTHREAD_MUTEX_ADAPTIVE_NP),且在調用pthread_cond_wait()前必須由本線程加鎖 (pthread_mutex_lock()),而在更新條件等待隊列之前,mutex保持鎖定狀態,並在線程掛起進入等待前解鎖。在條件知足從而離開 pthread_cond_wait()以前,mutex將被從新加鎖,以與進入pthread_cond_wait()前的加鎖動做對應。
激發
激發條件有兩種形式,pthread_cond_signal()激活一個等待該條件的線程,存在多個等待線程時按入隊順序激活其中一個;而pthread_cond_broadcast()則激活全部等待線程。</p>
其餘操做
pthread_cond_wait ()和pthread_cond_timedwait()都被實現爲取消點,所以,在該處等待的線程將當即從新運行,在從新鎖定mutex後離開 pthread_cond_wait(),而後執行取消動做。也就是說若是pthread_cond_wait()被取消,mutex是保持鎖定狀態的, 於是須要定義退出回調函數來爲其解鎖。
pthread_cond_wait實際上能夠看做是如下幾個動做的合體:
解鎖線程鎖;
等待條件爲true;
加鎖線程鎖;
使用形式:
// 線程一代碼 pthread_mutex_lock(&mutex); if (條件知足) pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); <p>// 線程二代碼 pthread_mutex_lock(&mutex); while (條件不知足) pthread_cond_wait(&cond, &mutex); pthread_mutex_unlock(&mutex); /*線程二中爲何使用while呢?由於在pthread_cond_signal和pthread_cond_wait返回之間,有時間差,假設在這 個時間差內,條件改變了,顯然須要從新檢查條件。也就是說在pthread_cond_wait被喚醒的時候可能該條件已經不成立。*/
信號量其實就是一個計數器,也是一個整數。 每一次調用 wait 操做將會使 semaphore 值減一,而若是 semaphore 值已經爲 0,則 wait 操做將會阻塞。 每一次調用post操做將會使semaphore值加一。
須要的頭文件:semaphore.h
信號量標識符:sem_t
主要函數:
sem_init
功能: 用於建立一個信號量,並初始化信號量的值。
函數原型: int sem_init (sem_t* sem, int pshared, unsigned int value);
函數傳入值: sem:信號量。
pshared:決定信號量可否在幾個進程間共享。 因爲目前 Linux 尚未實現進程間共享信息量,因此這個值只能取0。 value:初始計算器
函數返回值: 0:成功;-1:失敗。
其餘函數
//等待信號量 int sem_wait (sem_t* sem); int sem_trywait (sem_t* sem); //發送信號量 int sem_post (sem_t* sem); //獲得信號量值 int sem_getvalue (sem_t* sem); //刪除信號量 int sem_destroy (sem_t* sem);
功能:sem_wait和sem_trywait至關於 P 操做,它們都能將信號量的值減一, 二者的區別在於若信號量的值小於零時,sem_wait 將會阻塞進程,而 sem_trywait 則會當即返回。
sem_post 至關於 V 操做,它將信號量的值加一,同時發出喚醒的信號給等待的進程(或線程)。
sem_getvalue 獲得信號量的值。
sem_destroy 摧毀信號量。
使用形式:
sem_t sem; sem_init(&sem, 0, 1); /*信號量初始化*/ ... sem_wait(&sem); /*等待信號量*/ ... /*臨界資源*/ sem_post(&sem); /*釋放信號量*/
信號量與線程鎖、條件變量相比還有如下幾點不一樣:
首先在主函數中,咱們使用到了兩個函數,pthread_create
和 pthread_join
,並聲明瞭一個 pthread_t
型的變量。
pthread_t
在頭文件 pthread.h
中已經聲明,是線程的標示符
函數 pthread_create
用來建立一個線程,函數原型:
#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
若咱們的函數thread不須要參數,因此最後一個參數設爲空指針。第二個參數咱們也設爲空指針,這樣將生成默認屬性的線程。
返回值:
EAGAIN
表示系統限制建立新的線程,例如線程數目過多了;EINVAL
表示第二個參數表明的線程屬性值非法。建立線程成功後,新建立的線程則運行參數三和參數四肯定的函數, 原來的線程則繼續運行下一行代碼。
函數pthread_join用來等待一個線程的結束。函數原型爲:
#include <pthread.h> int pthread_join(pthread_t thread, void **retval);
第一個參數爲被等待的線程標識符,第二個參數爲一個用戶定義的指針,它能夠用來存儲被等待線程的返回值。這個函數是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束爲止,當函數返回時,被等待線程的資源被收回。
一個線程的結束有兩種途徑,
pthread_exit
來實現。它的函數原型爲:#include <pthread.h> void pthread_exit(void *retval);
惟一的參數是函數的返回代碼,只要 pthread_join 中的第二個參數 retval 不是NULL,這個值將被傳遞給 retval。
最後要說明的是,一個線程不能被多個線程等待,不然第一個接收到信號的線程成功返回,其他調用 pthread_join 的線程則返回錯誤代碼 ESRCH
。
設置線程綁定狀態的函數爲 pthread_attr_setscope
,它有兩個參數,第一個是指向屬性結構的指針,第二個是綁定類型,它有兩個取值:
PTHREAD_SCOPE_SYSTEM
(綁定的)PTHREAD_SCOPE_PROCESS
(非綁定的)下面的代碼即建立了一個綁定的線程。
#include <pthread.h> pthread_attr_t attr; pthread_t tid; /*初始化屬性值,均設爲默認值*/ pthread_attr_init(&attr); pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); pthread_create(&tid, &attr, (void *)task_func, NULL);
和進程相比,線程的最大優勢之一是數據的共享性,各個進程共享父進程處沿襲的數據段,能夠方便的得到、修改數據。
但這也給多線程編程帶來了許多問題。咱們必須小心有多個不一樣的進程訪問相同的變量。 許多函數是不可重入的,即同時不能運行一個函數的多個拷貝(除非使用不一樣的數據段)。 在函數中聲明的靜態變量經常帶來問題,函數的返回值也會有問題。 由於若是返回的是函數內部靜態聲明的空間的地址,則在一個線程調用該函數獲得地址後使用該地址指向的數據時,別的線程可能調用此函數並修改了這一段數據。 在進程中共享的變量必須用關鍵字 volatile
來定義,這是爲了防止編譯器在優化時(如 gcc 中使用 -OX 參數)改變它們的使用方式。 爲了保護變量,咱們必須使用信號量、互斥等方法來保證咱們對變量的正確使用。
互斥鎖用來保證一段時間內只有一個線程在執行一段代碼。必要性顯而易見:假設各個線程向同一個文件順序寫入數據,最後獲得的結果必定是災難性的
原來老是用互斥鎖(MUTEX)和環境變量(cond)去控制線程的通訊,用起來挺麻煩的,用信號量(SEM)來通訊控制就方便多了!
用到信號量就要包含semaphore.h頭文件。
能夠用sem_t類型來聲明一個型號量。
#include <semaphore.h> sem_t * sem_open(const char *name, int oflag, ...);
用 int sem_init(sem_t *sem, int pshared, unsigned int value) 函數來初始化型號量, 第一個參數就是用 sem_t 聲明的信號量, 第二變量若是爲 0,表示這個信號量只是當前進程中的型號量,若是不爲 0,這個信號量可能能夠在兩個進程中共享。 第三個參數就是初始化信號量的多少值。
如下是一個用信號控制的一個簡單的例子:
#include <stdio.h> #include <semaphore.h> #include <pthread.h> sem_t sem1, sem2; void *thread1(void *arg) { sem_wait(&sem1); setbuf(stdout,NULL);//這裏必須注意,因爲下面輸出"hello"中沒有‘n’符,因此可能因爲輸出緩存已滿,形成輸不出東西來,因此用這個函數把輸出緩存清空 printf("hello "); sem_post(&sem2); } void *thread2(void *arg) { sem_wait(&sem2); printf("world!n"); } int main() { pthread_t t1, t2; sem_init(&sem1,0,1);//初始化化信號量爲1,因此會先打印線程1 sem_init(&sem2,0,0);//初始化信號量爲0 pthread_create(&t1,NULL,thread1,NULL); pthread_create(&t2,NULL,thread2,NULL); pthread_join(t1,NULL); pthread_join(t2,NULL); sem_destroy(&sem1); sem_destroy(&sem2); return 0; }
//程序的實現是控制先讓thread1線程打印"hello "再讓thread2線程打印"world!"
mutex互斥體只用於保護臨界區的代碼(訪問共享資源),而不用於鎖之間的同步,即一個線程釋放mutex鎖後,立刻又可能獲取同一個鎖,而無論其它正在等待該mutex鎖的其它線程。
semaphore信號量除了起到保護臨界區的做用外,還用於鎖同步的功能,即一個線程釋放semaphore後,會保證正在等待該semaphore的線程優先執行,而不會立刻在獲取同一個semaphore。
若是兩個線程想經過一個鎖達到輸出1,2,1,2,1,2這樣的序列,應使用semaphore, 而使用 mutex 的結果可能爲1,1,1,1,1,2,2,2,111…..。