linux 多线程编程笔记
一,?線程基礎知識
1,線程的概念
線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。線程自己基本上不擁有系統資源,只擁有一點在運行
中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。
2,線程的優點
(1)?????? 通過為每種事件類型的處理分配單獨的線程,能夠簡化處理異步時間的代碼。
(2)?????? 多個線程可以自動共享相同的存儲地址空間和文件描述符。
(3)?????? 有些問題可以通過將其分解從而改善整個程序的吞吐量。
(4)?????? 交互的程序可以通過使用多線程實現相應時間的改善,多線程可以把程序中處理用戶輸入輸出的部分與其它部分分開。
3,線程的缺點
???? 線程也有不足之處。編寫多線程程序需要更全面更深入的思考。在一個多線程程序里,因時間分配上的細微偏差或者因共享了不該共享的變量而造成不良
???? 影響的可能性是很大的。調試一個多線程程序也比調試一個單線程程序困難得多。
4,線程的結構
???? 線程包含了表示進程內執行環境必需的信息,其中包括進程中標識線程的線程ID,一組寄存器值、棧、調度優先級和策略、信號屏蔽子,errno變量以及線
?? 程私有數據。進程的所有信息對該進程的所有線程都是共享的,包括可執行的程序文本,程序的全局內存和堆內存、棧以及文件描述符。
?? 進程ID在整個系統中是唯一的,但線程不同,線程ID只在它所屬的進程環境中有效。
5,線程的創建
?使用pthread_create函數。
?當pthread_creat成功返回時, tidp指向的內存單元被設置為新創建線程的線程ID。_attr參數用于定制各種不同的線程屬性。可以把它設置為NULL,創建默認的線程屬性。
新創建的線程從__start_routine函數的地址開始運行,該函數只有一個無類型指針參數arg,如果需要向__start_routine函數傳遞的參數不止一個,那么需要把這些參數放到
一個結構中,然后把這個結構的地址作為arg參數傳入。返回值:成功-0,失敗-返回錯誤編號,可以用strerror(errno)函數得到錯誤信息.
6,.線程的終止
?????? 線程是依進程而存在的,當進程終止時,線程也就終止了。當然也有在不終止整個進程的情況下停止它的控制流。
(1)線程只是從啟動例程中返回,返回值是線程的退出碼。
(2)線程可以被同一進程中的其他線程取消。
(3)線程調用pthread_exit.
?7,終止線程, 怎樣正確處理線程終止。
? 調用 pthread_exit
rval_prt是一個無類型指針,與傳給啟動例程的單個參數類似。進程中的其他線程可以調用pthread_join函數訪問到這個指針。
?
? 在 Linux 平臺下,當處理線程結束時需要注意的一個問題就是如何讓一個線程善始善終,讓其所占資源得到正確釋放。在 Linux 平臺默認情況下,雖然各個線程之間是
相互獨立的,一個線程的終止不會去通知或影響其他的線程。但是已經終止的線程的資源并不會隨著線程的終止而得到釋放,我們需要調用 pthread_join() 來獲得另一
個線程的終止狀態并且釋放該線程所占的資源。
pthread_join:獲得進程的終止狀態
#include <pthread.h> int pthread_join(pthread_t thread,void **rval_ptr);返回值:若成功返回0,否則返回錯誤編號。
當一個線程通過調用pthread_exit退出或者簡單地從啟動歷程中返回時,進程中的其他線程可以通過調用pthread_join函數獲得進程的退出狀態。調用pthread_join進程
將一直阻塞,直到指定的線程調用pthread_exit,從啟動例程中或者被取消。如果線程只是從它的啟動歷程返回,rval_ptr將包含返回碼。(此時線程應該出現非分離狀態)。
在默認情況下,線程的終止狀態會保存到對該線程調用pthread_join(即默認情況下線程處于非分離狀態),如果線程已經處于分離狀態,線程的底層存儲資源可以在線程終
止時立即被收回。當線程被分離時,并不能用pthread_join函數等待它的終止狀態。對分離狀態的線程進行pthread_join的調用會產生失敗,返回EINVAL.
如果你壓根兒不關心一個線程的結束狀態,那么也可以將一個線程設置為 detached 狀態,從而來讓操作系統在該線程結束時來回收它所占的資源。將一個線程設置為
detached 狀態可以通過兩種方式來實現。
一種是調用 pthread_detach() 函數,可以將線程 設置為 detached 狀態。
?pthread_detach:使線程進入分離狀態。
#include <pthread.h> int pthread_detach(pthread_t tid);返回值:若成功則返回0,否則返回錯誤編號。
另一種方法是在創建線程時就將它設置為 detached 狀態,首先初始化一個線程屬性變量,然后將其設置為 detached 狀態,最后將它作為參數傳入線程創建函數 pthread_create(),這樣所創建出來的線程就直接處于 detached 狀態。方法如下代碼:
?
pthread_t tid;pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);pthread_create(&tid, &attr, THREAD_FUNCTION, arg);...pthread_attr_destroy(&attr);
總之為了在使用 Pthread 時 避免線程的資源在線程結束時不能得到正確釋放,從而避免產生潛在的內存泄漏問題,在對待線程結束時,要確保該線程處于
?
detached狀態,否則就需要調用 pthread_join() 函數來對其進行資源回收。
好了,說這么多,舉個例子先。
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #include <errno.h> #define THREADS 2 // 線程個數int num = 0; void *run(void *arg)// 線程執行函數,線程0執行五次減,線程1執行五次加 {int flag = (int)arg;if(flag %2) // 根據線程參數執行相應動作{for(int i = 0; i < 5; i++){num++;printf("running in thread %d, num: %d\n", flag, num);}}else{for(int i = 0; i < 5; i++){num--;printf("running in thread %d, num: %d\n", flag, num);}}return (void *)arg; } int main() {int i,err;void * ret;pthread_t tid[THREADS];for(i = 0; i < THREADS; i++) // 創建線程{if(0 != (err = pthread_create(&tid[i], NULL, run, (void *)i))){printf("thread create error %s\n",strerror(err));exit(-1);}}for(i = 0; i < THREADS; i++)阻塞等待線程,直到該線程退出 {if(0 != (err = pthread_join(tid[i], &ret))){printf("thread join error %s\n",strerror(err));exit(-1);}else{printf("thread %d exit code %d\n", i, (int)ret); // 打印線程退出代碼}}return 0; }編譯 此程序別忘了加上加上-lpthread參數。pthread庫不是linux默認的庫,所以在編譯時候需要指明libpthread.a庫。
g++? -o? thread_test ?thread_test.c? -lpthread
程序執行結果:
兩個線程分別執行自減 5次 自加 5次最后結果為0,我們可以看到 thread 1最后輸出為 0 結果是對的。但是仔細看所有的輸出,你會發現有異樣的東西。
thread 0 怎么輸出連續2個 -1,thread1 怎么輸出0然后直接 輸出-3 。原因是兩個線程可以同時操作 num 。 而且 num--(++),printf 不是一個原子操作 。
比如:當thread0 執行時 設此時num=-1 ,然后執行 num--, 此時 num的值變為 -2 而此時還沒打印 printf? num的值 轉而線程thread執行了num++,此時
num的值又變回 -1,所以會出現打印連續兩個 -1 。
?當多個線程對共享區域進行修改時,應該采用同步的方式 才能達到我們有時候的需要,后面再敘述。
二,線程高級知識
1,線程屬性
?線程具有屬性,用pthread_attr_t表示,在對該結構進行處理之前必須進行初始化,在使用后需要對其去除初始化。我們用pthread_attr_init函數對其初始化,用
?pthread_attr_destroy對其去除初始化。
線程屬性結構如下:
typedef struct {int detachstate; //線程的分離狀態int schedpolicy; //線程調度策略struct sched_param schedparam; //線程的調度參數int inheritsched; //線程的繼承性int scope; //線程的作用域size_t guardsize; //線程棧末尾的警戒緩沖區大小int stackaddr_set;//堆棧地址集void * stackaddr; //線程棧的位置size_t stacksize; //線程棧的大小 }pthread_attr_t;
屬性值不能直接設置,須使用相關函數進行操作,初始化的函數為pthread_attr_init,這個函數必須在pthread_create函數之前調用。屬性值不能直接設置,須使用相關函數進行操作,初始化的函數為pthread_attr_init,這個函數必須在pthread_create函數之前調用。屬性值不能直接設置,每個屬性都對應一些函數對其查看或修改。初始化的函數為pthread_attr_init,這個函數必須在pthread_create函數之前調用。
線程屬性修改函數介紹如下:
A,線程的分離狀態
線程的分離狀態決定一個線程以什么樣的方式來終止自己。在默認情況下線程是非分離狀態的,這種情況下,只有當pthread_join()函數返回時,
創建的線程才算終止,才能釋放自己占用的系統資源。而分離線程不是這樣子的,它沒有被其他的線程所等待,自己運行結束了,線程也就終止了,
馬上釋放系統資源。
獲取/修改線程的分離狀態屬性:
int pthread_attr_getdetachstate(const pthread_attr_t * attr,int *detachstate); int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate);參數:?attr?? 線程屬性變量 detachstate? 線程的分離狀態屬性 ,返回值:?若成功返回0,若失敗返回-1。
可以使用pthread_attr_setdetachstate函數把線程屬性detachstate設置為下面的兩個合法值之一:
設置為PTHREAD_CREATE_DETACHED,以分離狀態啟動線程;或者設置為PTHREAD_CREATE_JOINABLE,正常啟動線程。
可以使用pthread_attr_getdetachstate函數獲取當前的datachstate線程屬性。
?
B,線程的繼承性
函數pthread_attr_setinheritsched和pthread_attr_getinheritsched分別用來設置和得到線程的繼承性,這兩個函數的定義如下:
#include <pthread.h> Int pthread_attr_getinheritsched(const pthread_attr_t *attr,int *inheritsched); int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);參數:?attr 線程屬性變量 inheritsched?? 線程的繼承性? 返回值:若成功返回0,若失敗返回-1。
?這兩個函數具有兩個參數,第1個是指向屬性對象的指針,第2個是繼承性或指向繼承性的指針。繼承性決定調度的參數是從創建的進程中繼承還是使用在
schedpolicy和schedparam屬性中顯式設置的調度信息。Pthreads不為inheritsched指定默認值,因此如果你關心線程的調度策略和參數,必須先設置該屬性。
繼承性的可能值是PTHREAD_INHERIT_SCHED(表示新線程將繼承創建線程的調度策略和參數)和PTHREAD_EXPLICIT_SCHED(表示使用在schedpolicy
和schedparam屬性中顯式設置的調度策略和參數)。如果你需要顯式的設置一個線程的調度策略或參數,那么你必須在設置之前將inheritsched屬性設置為? THREAD_EXPLICIT_SCHED.
C,線程的調度策略
? 函數pthread_attr_setschedpolicy和pthread_attr_getschedpolicy分別用來設置和得到線程的調度策略。
#include <pthread.h> int pthread_attr_getschedpolicy(const pthread_attr_t *attr,int *policy); int pthread_attr_setschedpolicy(pthread_attr_t *attr,int policy);參數:attr? 線程屬性變量policy 調度策略返回值:若成功返回0,若失敗返回-1。
?這兩個函數具有兩個參數,第1個參數是指向屬性對象的指針,第2個參數是調度策略或指向調度策略的指針。調度策略可能的值是先進先出(SCHED_FIFO)、
輪轉法(SCHED_RR),或其它(SCHED_OTHER)。SCHED_FIFO策略允許一個線程運行直到有更高優先級的線程準備好,或者直到它自愿阻塞自己。
在SCHED_FIFO調度策略下,當有一個線程準備好時,除非有平等或更高優先級的線程已經在運行,否則它會很快開始執行。
SCHED_RR(輪循)策略是基本相同的,不同之處在于:如果有一個SCHED_RR策略的線程執行了超過一個固定的時期(時間片間隔)沒有阻塞,而另外的SCHED_RR或SCHBD_FIPO策略的相同優先級的線程準備好時,運行的線程將被搶占以便準備好的線程可以執行。
當有SCHED_FIFO或SCHED_RR策賂的線程在一個條件變量上等持或等持加鎖同一個互斥量時,它們將以優先級順序被喚醒。即,如果一個低優先級的SCHED_FIFO
線程和一個高優先織的SCHED_FIFO線程都在等待鎖相同的互斥且,則當互斥量被解鎖時,高優先級線程將總是被首先解除阻塞。
D,線程的調度參數
函數pthread_attr_getschedparam 和pthread_attr_setschedparam分別用來設置和得到線程的調度參數。
#include <pthread.h> int pthread_attr_getschedparam(const pthread_attr_t *attr,struct sched_param *param); int pthread_attr_setschedparam(pthread_attr_t *attr,const struct sched_param *param);參數:?attr 線程屬性變量 param?? sched_param結構 返回值:?若成功返回0,若失敗返回-1。
這兩個函數具有兩個參數,第1個參數是指向屬性對象的指針,第2個參數是sched_param結構或指向該結構的指針。結構sched_param在文件/usr/include /bits/sched.h中定義如下:
結構sched_param的子成員sched_priority控制一個優先權值,大的優先權值對應高的優先權。系統支持的最大和最小優先權值可以用sched_get_priority_max函數和sched_get_priority_min函數分別得到。結構sched_param的子成員sched_priority控制一個優先權值,大的優先權值對應高的優先權。系統支持的最大和最小優先權值可以用sched_get_priority_max函數和sched_get_priority_min函數分別得到。
注意:如果不是編寫實時程序,不建議修改線程的優先級。因為,調度策略是一件非常復雜的事情,如果不正確使用會導致程序錯誤,從而導致死鎖等問題。如:在多線程應用程序中為線程設置不同的優先級別,有可能因為共享資源而導致優先級倒置。
#include <pthread.h> int sched_get_priority_max(int policy); int sched_get_priority_min(int policy);參數:policy? 系統支持的線程優先權的最大和最小值 返回值:若成功返回0,若失敗返回-1。
下面是有關線程屬性的程序例子:
?
#include<pthread.h> #include <unistd.h> #include <sched.h> #incude <stdio.h>void * child_thread(void *arg) {int policy;int max_priority,min_priority;struct sched_param param;pthread_attr_t attr;pthread_attr_init(&attr); /*初始化線程屬性變量*/pthread_attr_setinheritsched(&attr,PTHREAD_EXPLICIT_SCHED);/*設置線程繼承性*/pthread_attr_getinheritsched(&attr,&policy); /*獲得線程的繼承性*/if(policy==PTHREAD_EXPLICIT_SCHED)printf("Inheritsched:PTHREAD_EXPLICIT_SCHED\n");if(policy==PTHREAD_INHERIT_SCHED)printf("Inheritsched:PTHREAD_INHERIT_SCHED\n");pthread_attr_setschedpolicy(&attr,SCHED_RR);/*設置線程調度策略*/pthread_attr_getschedpolicy(&attr,&policy);/*取得線程的調度策略*/if(policy==SCHED_FIFO)printf("Schedpolicy:SCHED_FIFO\n");if(policy==SCHED_RR)printf("Schedpolicy:SCHED_RR\n");if(policy==SCHED_OTHER)printf("Schedpolicy:SCHED_OTHER\n");sched_get_priority_max(max_priority);/*獲得系統支持的線程優先權的最大值*/sched_get_priority_min(min_priority);/* 獲得系統支持的線程優先權的最小值*/printf("Max priority:%u\n",max_priority);printf("Min priority:%u\n",min_priority);param.sched_priority=max_priority;pthread_attr_setschedparam(&attr,?m);/*設置線程的調度參數*/printf("sched_priority:%u\n",param.sched_priority);/*獲得線程的調度參數*/pthread_attr_destroy(&attr);}int main( ){pthread_t child_thread_id;pthread_create(&child_thread_id,NULL,child_thread,NULL);pthread_join(child_thread_id,NULL);return 0; }?
此外線程屬性還有如下,不在仔細敷述:
?線程的作用域? 函數pthread_attr_setscope和pthread_attr_getscope分別用來設置和得到線程的作用域
?線程堆棧的大小? 函數pthread_attr_setstacksize和pthread_attr_getstacksize分別用來設置和得到線程堆棧的大小
?線程堆棧的地址?? 函數pthread_attr_setstackaddr和pthread_attr_getstackaddr分別用來設置和得到線程堆棧的位置
?線程棧末尾的警戒緩沖區大小? 函數pthread_attr_getguardsize和pthread_attr_setguardsize分別用來設置和得到線程棧末尾的警戒緩沖區大小
?三,線程同步
線程的最大特點是資源的共享性,但資源共享中的同步問題是多線程編程的難點。linux下提供了多種方式來處理線程同步,最常用的是互斥鎖、條件變量和異步信號。?
1,互斥鎖
?什么是互斥鎖?
?
一種在多線程程序中同步訪問手段是使用互斥量。程序員給某個對象加上一把“鎖”,每次只允許一個線程去訪問它。如果想對代碼關鍵部分的訪問進行控制,你必須在
進入這段代碼之前鎖定一把互斥量,在完成操作之后再打開它。
互斥鎖函數有:
?
? pthread_mutex_init 初始化一個互斥量pthread_mutex_lock 給一個互斥量加鎖pthread_mutex_trylock 加鎖,如果失敗不阻塞 pthread_mutex_destroy 銷毀一個互斥量pthread_mutex_unlock 解鎖
?? 可以通過使用pthread的互斥接口保護數據,確保同一時間只有一個線程訪問數據。互斥量從本質上說是一把鎖,在訪問共享資源前對互斥量進行加鎖,在訪問完成后
?
?釋放互斥量上的鎖。對互斥量進行加鎖以后,任何其他試圖再次對互斥量加鎖的線程將會被阻塞直到當前線程釋放該互斥鎖。如果釋放互斥鎖時有多個線程阻塞,所以
在該互斥鎖上的阻塞線程都會變成可進行狀態,第一個變成運行狀態的線程可以對互斥量加鎖,其他線程在次被阻塞,等待下次運行狀態。
互斥量用pthread_mutex_t數據類型來表示。
(1) 在使用互斥量以前,必須首先對它進行初始化,pthread_mutex_init()或靜態賦值pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIER
?
#include <pthread.h> int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutex_t *attr);參數:mutex? 互斥量 attr???? 互斥鎖屬性 返回值:若成功則返回0,否則返回錯誤編號。
?mutex 是我們要鎖住的互斥量, attr 是互斥鎖的屬性,可用相應的函數修改:
?
attr_t有:
????? PTHREAD_MUTEX_TIMED_NP:其余線程等待隊列
????? PTHREAD_MUTEX_RECURSIVE_NP:嵌套鎖,允許線程多次加鎖,不同線程,解鎖后重新競爭
????? PTHREAD_MUTEX_ERRORCHECK_NP:檢錯,與一同,線程請求已用鎖,返回EDEADLK;
????? PTHREAD_MUTEX_ADAPTIVE_NP:適應鎖,解鎖后重新競爭
(2) 對互斥量加減鎖
????? 對互斥量加/減鎖:
?
#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);返回值:若成功則返回0,否則返回錯誤編號。
?
對互斥量進行加鎖,需要調用pthread_mutex_lock,如果互斥量已經上鎖,調用線程阻塞直至互斥量解鎖。
對互斥量解鎖,需要調用pthread_mutex_unlock.
如果線程不希望被阻塞,他可以使用pthread_mutex_trylock嘗試對互斥量進行加鎖。如果調用pthread_mutex_trylock時互斥量處于未鎖住狀態,
那么pthread_mutex_trylock將鎖住互斥量,否則就會失敗,不能鎖住互斥量,而返回EBUSY。
(3)清除鎖,destroy(此時鎖必需unlock,否則返回EBUSY,//Linux下互斥鎖不占用資源內存
????? 釋放對互斥變量分配的資源:
?
#include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex);返回值:若成功則返回0,否則返回錯誤編號。
好了,既然了解了互斥鎖那么現在我們就 把 最開始的那個多線程的例子進行改裝:
只需更改線程子函數,代碼如下:
?
void *run(void *arg) {int flag = (int)arg;if(flag %2){for(int i = 0; i < 5; i++){pthread_mutex_lock(&mylock);// 加鎖num++;printf("running in thread %d, num: %d\n", flag, num);pthread_mutex_unlock(&mylock);// 解鎖}}else{for(int i = 0; i < 5; i++){pthread_mutex_lock(&mylock);// 加鎖num--;printf("running in thread %d, num: %d\n", flag, num);pthread_mutex_unlock(&mylock);// 解鎖}}return (void *)arg; }?
執行結果如下:
?
可以和先前的那個進行相應的比較,發現是不是用互斥量后程序正常了!
應用互斥量需要注意的幾點:
?1、互斥量需要時間來加鎖和解鎖。鎖住較少互斥量的程序通常運行得更快。所以,互斥量應該盡量少,夠用即可,每個互斥量保護的區域應則盡量大。
? 2、互斥量的本質是串行執行。如果很多線程需要領繁地加鎖同一個互斥量,則線程的大部分時間就會在等待,這對性能是有害的。如果互斥量保護的
? 數據(或代碼)包含彼此無關的片段,則可以特大的互斥量分解為幾個小的互斥量來提高性能。這樣,任意時刻需要小互斥量的線程減少,線程等待時間
? 就會減少。所以,互斥量應該足夠多(到有意義的地步),每個互斥量保護的區域則應盡量的少。
2,條件變量(cond)
?什么是條件變量?
? 與互斥鎖不同,條件變量是用來等待而不是用來上鎖的。條件變量用來自動阻塞一個線程,直到某特殊情況發生為止。通常條件變量和互斥鎖同時使用。
? 條件變量使我們可以睡眠等待某種條件出現。條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動作:
? 一個線程等待"條件變量的條件成立"而掛起;另一個線程使"條件成立"(給出條件成立信號)。
條件的檢測是在互斥鎖的保護下進行的。如果一個條件為假,一個線程自動阻塞,并釋放等待狀態改變的互斥鎖。如果另一個線程改變了條件,它發信號
給關聯的條件變量,喚醒一個或多個等待它的線程,重新獲得互斥鎖,重新評價條件。如果兩進程共享可讀寫的內存,條件變量可以被用來實現這兩進程
間的線程同步。
利用線程間共享的全局變量進行同步的一種機制。
(1)初始化.init()或者pthread_cond_t cond=PTHREAD_COND_INITIALIER;屬性置為NULL
?
(2)等待條件成立.pthread_wait,pthread_timewait.wait()釋放鎖,并阻塞等待條件變量為真 timewait()設置等待時間,仍未signal,返回ETIMEOUT(加鎖保證只有一個線程wait)
(3)激活條件變量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待線程)
(4)清除條件變量:destroy;無線程等待,否則返回EBUSY
注意: 條件變量無論哪種等待方式,都必須和一個互斥鎖配合,以防止多個線程同時請求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()前的加鎖動作對應。
舉個例子先,利用2線程產生擁有連續10個AB的序列。
代碼如下:
?
#include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #define DE 1 // 是否debug #define THREADS 2 // 線程個數int num = 0; pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;//互斥量 pthread_cond_t ready = PTHREAD_COND_INITIALIZER;//條件變量 void debug_p(char *str, int i) {#ifdef DE // 如果定義了debug則輸出 調試信息printf("\nthread %d %s\n", i, str); #endif } void *my_start(void *arg)// {int id = (int)arg;char c = 'A' + id;int ret = -1,i = 0;for(; i < 10; i++){pthread_mutex_lock(&mylock);//調用pthread_cond_wait前,必須獲得互斥鎖while(id != num){debug_p("waitting", id);ret = pthread_cond_wait(&ready, &mylock);//把線程放入等待條件的線程列表,然后對互斥鎖進行解鎖,這兩部都是原子操作。pthread_cond_wait返回時,互斥量再次鎖住。 if(ret == 0)debug_p("wait success", id);elsedebug_p("wait failed", id);}printf("%c ", c);num = ++num%THREADS;pthread_mutex_unlock(&mylock);pthread_cond_broadcast(&ready);//喚醒等待該條件的所有線程 }return (void *)0; }int main(int argc, char* argv[]) {int i;pthread_t tid[THREADS];void *ret;for(i = 0; i < THREADS; i++){if(pthread_create(&tid[i], 0, my_start ,(void *)i) != 0){printf("threted create error!\n");exit(-1);}}for(i = 0; i < THREADS; i++){if(pthread_join(tid[i], &ret) != 0){printf("threted join error!\n");exit(-1);}}printf("\n");return 0; }
可以看到,在當 #define 1 開啟,即debug輸出我們自定義的調試信息時,thread 0 剛開始滿足條件輸出A,num加1 解鎖互斥量,
?
此時無等待該條件的線程。此時num=1 ,id=0 進入while循環 顯然thread 0進入等待狀態,然后執行thread 1 即此時 id=1,而此時
num也為1 此時跳過while循環輸出 B。此時num++, thread 1進入 waititng,然后解鎖互斥量,最后喚醒等待該條件的線程thread 0。
輸出 A ....
注釋掉// #define DE 即可輸出 :ABABABABABABABABABAB
3, 信號量
如同進程一樣,線程也可以通過信號量來實現通信,雖然是輕量級的。
信號量函數的名字都以"sem_"打頭。線程使用的基本信號量函數有四個。
#include <semaphore.h>
int sem_init (sem_t *sem , int pshared, unsigned int value);
這是對由sem指定的信號量進行初始化,設置好它的共享選項(linux 只支持為0,即表示它是當前進程的局部信號量),然后給它一個初始值VALUE。
兩個原子操作函數:
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
這兩個函數都要用一個由sem_init調用初始化的信號量對象的指針做參數。
sem_post:給信號量的值加1;
sem_wait:給信號量減1;對一個值為0的信號量調用sem_wait,這個函數將會等待直到有其它線程使它不再是0為止。
int sem_destroy(sem_t *sem);
這個函數的作用是再我們用完信號量后都它進行清理。歸還自己占有的一切資源。
?
條件變量與互斥鎖、信號量的區別:
?? 到這里,我們把posix的互斥鎖、信號量、條件變量都接受完了,下面我們來比較一下他們。
?? a.互斥鎖必須總是由給它上鎖的線程解鎖,信號量的掛出即不必由執行過它的等待操作的同一進程執行。一個線程可以等待某個給定信號燈,
?????? 而另一個線程可以掛出該信號燈。
??? b.互斥鎖要么鎖住,要么被解開(二值狀態,類型二值信號量)。
??? c.由于信號量有一個與之關聯的狀態(它的計數值),信號量掛出操作總是被記住。然而當向一個條件變量發送信號時,如果沒有線程等待在
???????? 該條件變量上,那么該信號將丟失。
??? d.互斥鎖是為了上鎖而優化的,條件變量是為了等待而優化的,信號燈即可用于上鎖,也可用于等待,因而可能導致更多的開銷和更高的復雜性。
待續...
參考資料:
<APUE>
<UNP,第二卷>
http://blog.csdn.net/lanyan822/article/details/7587972
?
總結
以上是生活随笔為你收集整理的linux 多线程编程笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java验证码(采用struts2实现)
- 下一篇: 教你解决ChartDirector Li