前言:有這樣一道面試題(來自http://blog.csdn.net/morewindows/article/details/7392749):
“編寫一個程序,開啟3個線程,這3個線程的ID分別為A、B、C,每個線程將自己的ID在屏幕上打印10遍,要求輸出結果必須按ABC的順序顯示;如:ABCABC….依次遞推。”
我們就從這樣一題出發,認識多線程,了解其同步機制,最后正確解答這一類題目。本文框架如下:
- 進程與線程
- 多線程的優越性
- 線程基本函數
- 多線程同步
- 題目代碼
一.進程與線程
進程的定義:進程是為了描述程序在並發執行時對系統資源的共享,所需的一個描述程序執行時動態特征的概念。進程是具有獨立功能的程序關於某個數據集合上的一次運行活動,是系統進行資源分配、調度和保護的獨立單位。
線程的定義:線程也成為輕量級進程,是進程中的一個運行實體,作為CPU的調度單位。一個進程由多個線程組成,線程與同屬一個進程的其他的線程共享進程所擁有的全部資源。
同一進程內的所有線程除了共享全局變量外還共享:
- 進程指令;
- 大多數數據;
- 打開的文件(即描述符);
- 信號處理函數和信號處置;
- 當前工作目錄;
- 用戶ID和組ID。
每個線程擁有各自的:
- 線程ID;
- 寄存器集合,包括程序計數器和棧指針;
- 棧(用於存放局部變量和返回地址);
- errno;
- 信號掩碼;
- 優先級。
結構上的不同可以讓我們更加了解進程和線程的相異之處:
(1)進程是資源分配的基本單位;線程與資源分配無關,它屬於某一個進程,並與進程內的其他線程共享進程的資源。
(2)當進程發生調度時,不同的進程擁有不同的虛擬地址空間,而同一進程內的不同線程共享同一地址空間。
(3)線程只由先關堆棧(系統棧或用戶棧)寄存器和線程控制塊組成。寄存器用來存儲線程內的局部變量,但不能存儲其他線程的相關變量。
(4)進程切換時涉及有關資源指針的保存和地址空間的變化;線程切換時,由於處於同一進程內,所以不涉及資源信息的保存和地址空間的變化,從而減少了操作系統的時間開銷。
二.多線程的優越性
在傳統的UNIX模型中,當一個進程需要另一個實體來完成某事,它就fork一個子進程並讓子進程去處理。但是fork的調用有如下缺點:
(1)fork的代價是昂貴的。fork要把父進程的內存印象復制到子進程,並在子進程中復制所有描述符等。
(2)fork返回之后父子進程之間信息的傳遞需要進程通信機制。調用fork之前父進程向尚未存在的子進程傳遞信息相當容易,因為子進程將從父進程數據空間及所有描述符的一個副本開始運行,但是從子進程向父進程返回信息卻比較費力。
針對這兩點,多線程技術相應而生,它具有如下優越性:
(1)它是一種非常"節儉"的多任務操作方式。而運行於一個進程中的多個線程,它們彼此之間使用相同的地址空間,共享大部分數據,啟動一個線程所花費的空間遠遠小於啟動一個進程所花費的空間,而且,線程間彼此切換所需的時間也遠遠小於進程間切換所需要的時間。
(2)線程間方便的通信更加方便。由於同一進程下的線程之間共享數據空間,所以一個線程的數據可以直接為其它線程所用,這不僅快捷,而且方便。但是,同一進程內的所有線程共享相同的全局內存,這樣線程之間的通信就變得相當簡單,隨之而來的就是同步問題。
三.基本線程函數
1.pthread_create函數,創建線程
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func)(void *), void *arg);
pthread_t *tid:一個進程內的各個線程是由線程ID標識的,如果新線程創建成功,返回tid指針。
const pthread_attr_t *attr:每個線程有多個屬性,包括優先級、初始棧大小、是否是一個守護線程等等。
void *(*func)(void *):線程啟動函數,線程從調用這個函數開始,或顯示結束(調用pthread_exit()),或隱式結束(讓該函數返回)。
void *arg:線程執行func函數的傳遞參數。
2.pthread_join函數,等待一個線程終止
int pthread_join(pthread_t *tid, void **status);
void **status:二級指針,如果status指針非空,那么所等待線程的返回值將存放在status指向的位置。
3.pthread_self函數,返回線程ID
int pthread_self(void);
跟進程比較,相當於getpid。
4.pthread_detach函數,線程分離
int pthread_detach(pthread_t tid);
線程或者是可匯合的(joinable),或者是脫離的(detach)。當可匯合的線程終止時,線程ID和退出狀態將保留,知道另外一個線程調用pthread_join。脫離的線程終止時,釋放所有的資源,因此我們不能等待它終止。若要一個線程知道另一個線程的終止時間,我們就要保留第二個線程的可匯合性。
5.pthread_exit函數,線程終止
int pthread_exit(void **status);
若線程未脫離,那么它的線程ID和退出狀態將保留到另外一個線程調用pthread_join為止。
四.多線程的同步
有了上面的基本函數還不足以完成本題的要求,為什么呢?因為題目要求按照ABCABC...的方式打印,而3個線程卻在搶占資源,所以無法控制排列順序。這時就需要用到多線程編程中的同步技術。
對於多線程編程來說,同步就是同一時間只允許一個線程訪問資源,而其他線程不能訪問。多線程有3種同步方式:
- 互斥鎖
- 條件變量
- 讀寫鎖
1.互斥鎖
互斥鎖是最基本的同步方式,它用來保護一個“臨界區”,保證任何時刻只由一個線程在執行其中的代碼。這個“臨界區”通常是線程的共享數據。
下面三個函數給一個互斥鎖上鎖和解鎖:
int pthread_mutex_lock(pthread_mutex_t *mptr); int pthread_mutex_trylock(pthread_mutex_t *mptr); int pthread_mutex_unlock(pthread_mutex_t *mptr);
假設線程2要給已經被線程1鎖住的互斥鎖(mutex)上鎖(即執行pthread_mutex_lock(mutex)),那么它將一直阻塞直到到線程1解鎖為止(即釋放mutex)。
如果互斥鎖變量時靜態分配的,通常初始化為常值PTHREAD_MUTEX_INITIALIZER,如果互斥鎖是動態分配的,那么在運行時調用pthread_mutex_init函數來初始化。
2.條件變量
互斥鎖用於上鎖,而條件變量則用於等待,通常它都會跟互斥鎖一起使用。
int pthread_cond_wait(pthread_cond_t *cptr,pthread_mutex_t *mptr); int pthread_cond_signal(pthread_cond_t *cptr);
通常pthread_cond_signal只喚醒等待在相應條件變量上的一個線程,若有多個線程需要被喚醒呢,這就要使用下面的函數了:
int pthread_cond_broadcast(pthread_cond_t *cptr);
3.讀寫鎖
互斥鎖將試圖進入連你姐去的其他簡稱阻塞住,而讀寫鎖是將讀和寫作了區分,讀寫鎖的分配規則如下:
(1)只要沒有線程持有某個給定的讀寫鎖用於寫,那么任意數目的線程可以持有該讀寫鎖用於讀;
(2)僅當沒有線程持有某個給定的讀寫鎖用於讀或用於寫時,才能分配該讀寫鎖用於寫。
int pthread_rwlock_rdlock(pthread_relock_t *rwptr); int pthread_rwlock_wrlock(pthread_relock_t *rwptr); int pthread_rwlock_unlock(pthread_relock_t *rwptr);
五.題目代碼
分析此題:
1.主線程main創建3個線程tid0,tid1,tid2;
2.設一個全局變量num,互斥鎖mutex保護此臨界區保證每次只有一個線程訪問num;
3.若搶占到資源的線程tid並不是我們需要的,那么讓它阻塞;
4.若搶占到資源的線程tid正好是我們需要的,那么就打印相應字母;
5.解鎖,喚醒其他兩個等待線程;
6.main函數等待3個線程打印結束才結束。
代碼如下:
#include<stdio.h> #include<stdlib.h> #include<error.h> #include<unistd.h> #include<pthread.h> int num=0; static pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t cond=PTHREAD_COND_INITIALIZER; void *func(void *); int main() { pthread_t tid[3]; int ret=0,i; for(i=0;i<3;i++) if((ret=pthread_create(&tid[i],NULL,func,(void*)i))!=0) printf("create thread_%c error\n",i+'A'); for(i=0;i<3;i++) pthread_join(tid[i],NULL); printf("\n"); return 0; } void *func(void *argc) { int i; for(i=0;i<10;i++) { pthread_mutex_lock(&mutex); while(num!=(int)argc) pthread_cond_wait(&cond,&mutex); printf("%c",num+'A'); num=(num+1)%3; pthread_mutex_unlock(&mutex); pthread_cond_broadcast(&cond); } pthread_exit(0); }
參考資料:
1. 《UNIX網絡編程卷2》
2.http://www.cnblogs.com/skynet/archive/2010/10/30/1865267.html
3.http://www.cnblogs.com/vamei/archive/2012/10/09/2715393.html