互斥量、條件變數與pthread_cond_wait()函式的使用
1.Linux“執行緒”
程序與執行緒之間是有區別的,不過linux核心只提供了輕量程序的支援,未實現執行緒模型。Linux是一種“多程序單執行緒”的作業系統。Linux本身只有程序的概念,而其所謂的“執行緒”本質上在核心裡仍然是程序。
大家知道,程序是資源分配的單位,同一程序中的多個執行緒共享該程序的資源(如作為共享記憶體的全域性變數)。Linux中所謂的“執行緒”只是在被建立時clone了父程序的資源,因此clone出來的程序表現為“執行緒”,這一點一定要弄清楚。因此,Linux“執行緒”這個概念只有在打冒號的情況下才是最準確的。
目前Linux中最流行的執行緒機制為LinuxThreads,所採用的就是執行緒-程序“一對一”模型,排程交給核心,而在使用者級實現一個包括訊號處理在內的執行緒管理機制。LinuxThreads由Xavier Leroy (
gcc -D -REENTRANT -lpthread xxx. c |
其中-REENTRANT巨集使得相關庫函式(如stdio.h、errno.h中函式) 是可重入的、執行緒安全的(thread-safe),-lpthread則意味著連結庫目錄下的libpthread.a或libpthread.so檔案。使用Linuxthread庫需要2.0以上版本的Linux核心及相應版本的C庫(libc 5.2.18、libc 5.4.12、libc 6)。
2.“執行緒”控制 執行緒建立 程序被建立時,系統會為其建立一個主執行緒,而要在程序中建立新的執行緒,則可以呼叫pthread_create:
pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (start_routine)(void*), void *arg); |
start_routine為新執行緒的入口函式,arg為傳遞給start_routine的引數。 每個執行緒都有自己的執行緒ID,以便在程序內區分。執行緒ID在pthread_create呼叫時回返給建立執行緒的呼叫者;一個執行緒也可以在建立後使用pthread_self()呼叫獲取自己的執行緒ID:
pthread_self (void) ; |
執行緒退出 執行緒的退出方式有三: (1)執行完成後隱式退出; (2)由執行緒本身顯示呼叫pthread_exit 函式退出;
pthread_exit (void * retval) ; |
(3)被其他執行緒用pthread_cance函式終止:
pthread_cance (pthread_t thread) ; |
在某執行緒中呼叫此函式,可以終止由引數thread 指定的執行緒。 如果一個執行緒要等待另一個執行緒的終止,可以使用pthread_join函式,該函式的作用是呼叫pthread_join的執行緒將被掛起直到執行緒ID為引數thread的執行緒終止:
pthread_join (pthread_t thread, void** threadreturn); |
3.執行緒通訊 執行緒互斥 互斥意味著“排它”,即兩個執行緒不能同時進入被互斥保護的程式碼。Linux下可以通過pthread_mutex_t 定義互斥體機制完成多執行緒的互斥操作,該機制的作用是對某個需要互斥的部分,在進入時先得到互斥體,如果沒有得到互斥體,表明互斥部分被其它執行緒擁有,此時欲獲取互斥體的執行緒阻塞,直到擁有該互斥體的執行緒完成互斥部分的操作為止。 下面的程式碼實現了對共享全域性變數x 用互斥體mutex 進行保護的目的:
int x; // 程序中的全域性變數 pthread_mutex_t mutex; pthread_mutex_init(&mutex, NULL); //按預設的屬性初始化互斥體變數mutex pthread_mutex_lock(&mutex); // 給互斥體變數加鎖 … //對變數x 的操作 phtread_mutex_unlock(&mutex); // 給互斥體變數解除鎖 |
執行緒同步 同步就是執行緒等待某個事件的發生。只有當等待的事件發生執行緒才繼續執行,否則執行緒掛起並放棄處理器。當多個執行緒協作時,相互作用的任務必須在一定的條件下同步。 Linux下的C語言程式設計有多種執行緒同步機制,最典型的是條件變數(condition variable)。pthread_cond_init用來建立一個條件變數,其函式原型為:
pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr); |
pthread_cond_wait和pthread_cond_timedwait用來等待條件變數被設定,值得注意的是這兩個等待呼叫需要一個已經上鎖的互斥體mutex,這是為了防止在真正進入等待狀態之前別的執行緒有可能設定該條件變數而產生競爭。pthread_cond_wait的函式原型為:
pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex); |
pthread_cond_broadcast用於設定條件變數,即使得事件發生,這樣等待該事件的執行緒將不再阻塞:
pthread_cond_broadcast (pthread_cond_t *cond) ; |
pthread_cond_signal則用於解除某一個等待執行緒的阻塞狀態:
pthread_cond_signal (pthread_cond_t *cond) ; |
pthread_cond_destroy 則用於釋放一個條件變數的資源。 在標頭檔案semaphore.h 中定義的訊號量則完成了互斥體和條件變數的封裝,按照多執行緒程式設計中訪問控制機制,控制對資源的同步訪問,提供程式設計人員更方便的呼叫介面。
sem_init(sem_t *sem, int pshared, unsigned int val); |
這個函式初始化一個訊號量sem 的值為val,引數pshared 是共享屬性控制,表明是否在程序間共享。
sem_wait(sem_t *sem); |
呼叫該函式時,若sem為無狀態,呼叫執行緒阻塞,等待訊號量sem值增加(post )成為有訊號狀態;若sem為有狀態,呼叫執行緒順序執行,但訊號量的值減一。
sem_post(sem_t *sem); |
呼叫該函式,訊號量sem的值增加,可以從無訊號狀態變為有訊號狀態。
4.例項 下面我們還是以名的生產者/消費者問題為例來闡述Linux執行緒的控制和通訊。一組生產者執行緒與一組消費者執行緒通過緩衝區發生聯絡。生產者執行緒將生產的產品送入緩衝區,消費者執行緒則從中取出產品。緩衝區有N 個,是一個環形的緩衝池。
- #include <stdio.h>
- #include <pthread.h>
- #define BUFFER_SIZE 16 // 緩衝區數量
- struct prodcons
- {
- // 緩衝區相關資料結構
- int buffer[BUFFER_SIZE]; /* 實際資料存放的陣列*/
- pthread_mutex_t lock; /* 互斥體lock 用於對緩衝區的互斥操作 */
- int readpos, writepos; /* 讀寫指標*/
- pthread_cond_t notempty; /* 緩衝區非空的條件變數 */
- pthread_cond_t notfull; /* 緩衝區未滿的條件變數 */
- };
- /* 初始化緩衝區結構 */
- void init(struct prodcons *b)
- {
- pthread_mutex_init(&b->lock, NULL);
- pthread_cond_init(&b->notempty, NULL);
- pthread_cond_init(&b->notfull, NULL);
- b->readpos = 0;
- b->writepos = 0;
- }
- /* 將產品放入緩衝區,這裡是存入一個整數*/
- void put(struct prodcons *b, int data)
- {
- pthread_mutex_lock(&b->lock);
- /* 等待緩衝區未滿*/
- if ((b->writepos + 1) % BUFFER_SIZE == b->readpos)
- {
- pthread_cond_wait(&b->notfull, &b->lock);
- }
- /* 寫資料,並移動指標 */
- b->buffer[b->writepos] = data;
- b->writepos++;
- if (b->writepos >= BUFFER_SIZE)
- b->writepos = 0;
- /* 設定緩衝區非空的條件變數*/
- pthread_cond_signal(&b->notempty);
- pthread_mutex_unlock(&b->lock);
- }
- /* 從緩衝區中取出整數*/
- int get(struct prodcons *b)
- {
- int data;
- pthread_mutex_lock(&b->lock);
- /* 等待緩衝區非空*/
- if (b->writepos == b->readpos)
- {
- pthread_cond_wait(&b->notempty, &b->lock);
- }
- /* 讀資料,移動讀指標*/
- data = b->buffer[b->readpos];
- b->readpos++;
- if (b->readpos >= BUFFER_SIZE)
- b->readpos = 0;
- /* 設定緩衝區未滿的條件變數*/
- pthread_cond_signal(&b->notfull);
- pthread_mutex_unlock(&b->lock);
- return data;
- }
- /* 測試:生產者執行緒將1 到10000 的整數送入緩衝區,消費者線
- 程從緩衝區中獲取整數,兩者都列印資訊*/
- #define OVER ( - 1)
- struct prodcons buffer;
- void *producer(void *data)
- {
- int n;
- for (n = 0; n < 10000; n++)
- {
- printf("%d --->\n", n);
- put(&buffer, n);
- } put(&buffer, OVER);
- return NULL;
- }
- void *consumer(void *data)
- {
- int d;
- while (1)
- {
- d = get(&buffer);
- if (d == OVER)
- break;
- printf("--->%d \n", d);
- }
- return NULL;
- }
- int main(void)
- {
- pthread_t th_a, th_b;
- void *retval;
- init(&buffer);
- /* 建立生產者和消費者執行緒*/
- pthread_create(&th_a, NULL, producer, 0);
- pthread_create(&th_b, NULL, consumer, 0);
- /* 等待兩個執行緒結束*/
- pthread_join(th_a, &retval);
- pthread_join(th_b, &retval);
- return 0;
- }
5.WIN32、VxWorks、Linux執行緒類比 目前為止,筆者已經創作了《基於嵌入式作業系統VxWorks的多工併發程式設計》(《軟體報》2006年5~12期連載)、《深入淺出Win32多執行緒程式設計》(天極網技術專題)系列,我們來找出這兩個系列文章與本文的共通點。 看待技術問題要瞄準其本質,不管是Linux、VxWorks還是WIN32,其涉及到多執行緒的部分都是那些內容,無非就是執行緒控制和執行緒通訊,它們的許多函式只是名稱不同,其實質含義是等價的,下面我們來列個三大作業系統共同點詳細表單:
事項 | WIN32 | VxWorks | Linux |
執行緒建立 | CreateThread | taskSpawn | pthread_create |
執行緒終止 | 執行完成後退出;執行緒自身呼叫ExitThread函式即終止自己;被其他執行緒呼叫函式TerminateThread函式 | 執行完成後退出;由執行緒本身呼叫exit退出;被其他執行緒呼叫函式taskDelete終止 | 執行完成後退出;由執行緒本身呼叫pthread_exit 退出;被其他執行緒呼叫函式pthread_cance終止 |
獲取執行緒ID | GetCurrentThreadId | taskIdSelf | pthread_self |
建立互斥 | CreateMutex | semMCreate | pthread_mutex_init |
獲取互斥 | WaitForSingleObject、WaitForMultipleObjects | semTake | pthread_mutex_lock |
釋放互斥 | ReleaseMutex | semGive | phtread_mutex_unlock |
建立訊號量 | CreateSemaphore | semBCreate、semCCreate | sem_init |
等待訊號量 | WaitForSingleObject | semTake | sem_wait |
釋放訊號量 | ReleaseSemaphore | semGive | sem_post |
6.小結 本章講述了Linux下多執行緒的控制及執行緒間通訊程式設計方法,給出了一個生產者/消費者的例項,並將Linux的多執行緒與WIN32、VxWorks多執行緒進行了類比,總結了一般規律。鑑於多執行緒程式設計已成為開發併發應用程式的主流方法,學好本章的意義也便不言自明。
完
- #include <stdio.h>
- #include <stdio.h>
- #include <pthread.h>
- void thread(void)
- {
- int i;
- for(i=0;i<3;i++)
- printf("This is a pthread.\n");
- }
- int main(void)
- {
- pthread_t id;
- int i,ret;
- ret=pthread_create(&id,NULL,(void *) thread,NULL);
- if(ret!=0){
- printf ("Create pthread error!\n");
- exit (1);
- }
- for(i=0;i<3;i++)
- printf("This is the main process.\n");
- pthread_join(id,NULL);
- return (0);
- }
編譯:
gcc example1.c -lpthread -o example1
- #include <pthread.h>
- #include <stdio.h>
- #include <sys/time.h>
- #include <string.h>
- #define MAX 10
- pthread_t thread[2];
- pthread_mutex_t mut;
- int number=0, i;
- void *thread1()
- {
- printf ("thread1 : I'm thread 1\n");
- for (i = 0; i < MAX; i++)
- {
- printf("thread1 : number = %d\n",number);
- pthread_mutex_lock(&mut);
- number++;
- pthread_mutex_unlock(&mut);
- sleep(2);
- }
- printf("thread1 :主函式在等我完成任務嗎?\n");
- pthread_exit(NULL);
- }
- void *thread2()
- {
- printf("thread2 : I'm thread 2\n");
- for (i = 0; i < MAX; i++)
- {
- printf("thread2 : number = %d\n",number);
- pthread_mutex_lock(&mut);
- number++;
- pthread_mutex_unlock(&mut);
- sleep(3);
- }
- printf("thread2 :主函式在等我完成任務嗎?\n");
- pthread_exit(NULL);
- }
- void thread_create(void)
- {
- int temp;
- memset(&thread, 0, sizeof(thread)); //comment1
- //建立執行緒
- if((temp = pthread_create(&thread[0], NULL, thread1, NULL)) != 0) //comment2
- printf("執行緒1建立失敗!\n");
- else
- printf("執行緒1被建立\n");
- if((temp = pthread_create(&thread[1], NULL, thread2, NULL)) != 0) //comment3
- printf("執行緒2建立失敗");
- else
- printf("執行緒2被建立\n");
- }
- void thread_wait(void)
- {
- //等待執行緒結束
- if(thread[0] !=0) { //comment4
- pthread_join(thread[0],NULL);
- printf("執行緒1已經結束\n");
- }
- if(thread[1] !=0) { //comment5
- pthread_join(thread[1],NULL);
- printf("執行緒2已經結束\n");
- }
- }
- int main()
- {
- //用預設屬性初始化互斥鎖
- pthread_mutex_init(&mut,NULL);
- printf("我是主函式哦,我正在建立執行緒,呵呵\n");
- thread_create();
- printf("我是主函式哦,我正在等待執行緒完成任務阿,呵呵\n");
- thread_wait();
- return 0;
- }