——本文一個例子展開,介紹Linux下面線程的操作、多線程的同步和互斥。 前言線程?為什么有了進程還需要線程呢,他們有什么區(qū)別?使用線程有什么優(yōu)勢呢?還有多線程編程的一些細節(jié)問題,如線程之間怎樣同步、互斥,這些東西將在本文中介紹。我在某QQ群里見到這樣一道面試題:
我們帶著這題開始這篇文章,結束之后,大家就都會做了。本文的框架如下:
1、進程與線程進程是程序執(zhí)行時的一個實例,即它是程序已經(jīng)執(zhí)行到何種程度的數(shù)據(jù)結構的匯集。從內(nèi)核的觀點看,進程的目的就是擔當分配系統(tǒng)資源(CPU時間、內(nèi)存等)的基本單位。 線程是進程的一個執(zhí)行流,是CPU調(diào)度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。一個進程由幾個線程組成(擁有很多相對獨立的執(zhí)行流的用戶程序共享應用程序的大部分數(shù)據(jù)結構),線程與同屬一個進程的其他的線程共享進程所擁有的全部資源。
進程有獨立的地址空間,一個進程崩潰后,在保護模式下不會對其它進程產(chǎn)生影響,而線程只是一個進程中的不同執(zhí)行路徑。線程有自己的堆棧和局部變量,但線程沒有單獨的地址空間,一個線程死掉就等于整個進程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對于一些要求同時進行并且又要共享某些變量的并發(fā)操作,只能用線程,不能用進程。 2、使用線程的理由從上面我們知道了進程與線程的區(qū)別,其實這些區(qū)別也就是我們使用線程的理由??偟膩碚f就是:進程有獨立的地址空間,線程沒有單獨的地址空間(同一進程內(nèi)的線程共享進程的地址空間)。(下面的內(nèi)容摘自Linux下的多線程編程) 使用多線程的理由之一是和進程相比,它是一種非常"節(jié)儉"的多任務操作方式。我們知道,在Linux系統(tǒng)下,啟動一個新的進程必須分配給它獨立的地址空間,建立眾多的數(shù)據(jù)表來維護它的代碼段、堆棧段和數(shù)據(jù)段,這是一種"昂貴"的多任務工作方式。而運行于一個進程中的多個線程,它們彼此之間使用相同的地址空間,共享大部分數(shù)據(jù),啟動一個線程所花費的空間遠遠小于啟動一個進程所花費的空間,而且,線程間彼此切換所需的時間也遠遠小于進程間切換所需要的時間。據(jù)統(tǒng)計,總的說來,一個進程的開銷大約是一個線程開銷的30倍左右,當然,在具體的系統(tǒng)上,這個數(shù)據(jù)可能會有較大的區(qū)別。 使用多線程的理由之二是線程間方便的通信機制。對不同進程來說,它們具有獨立的數(shù)據(jù)空間,要進行數(shù)據(jù)的傳遞只能通過通信的方式進行,這種方式不僅費時,而且很不方便。線程則不然,由于同一進程下的線程之間共享數(shù)據(jù)空間,所以一個線程的數(shù)據(jù)可以直接為其它線程所用,這不僅快捷,而且方便。當然,數(shù)據(jù)的共享也帶來其他一些問題,有的變量不能同時被兩個線程所修改,有的子程序中聲明為static的數(shù)據(jù)更有可能給多線程程序帶來災難性的打擊,這些正是編寫多線程程序時最需要注意的地方。 除了以上所說的優(yōu)點外,不和進程比較,多線程程序作為一種多任務、并發(fā)的工作方式,當然有以下的優(yōu)點:
============================= 從函數(shù)調(diào)用上來說,進程創(chuàng)建使用fork()操作;線程創(chuàng)建使用clone()操作。Richard Stevens大師這樣說過:
Threads help with both problems. Threads are sometimes called lightweight processes since a thread is "lighter weight" than a process. That is, thread creation can be 10–100 times faster than process creation. All threads within a process share the same global memory. This makes the sharing of information easy between the threads, but along with this simplicity comes the problem of synchronization. ============================= 3、有關線程操作的函數(shù)#include <pthread.h> int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func) (void *), void *arg); int pthread_join (pthread_t tid, void ** status); pthread_t pthread_self (void); int pthread_detach (pthread_t tid); void pthread_exit (void *status); pthread_create用于創(chuàng)建一個線程,成功返回0,否則返回Exxx(為正數(shù))。
pthread_join用于等待某個線程退出,成功返回0,否則返回Exxx(為正數(shù))。
pthread_self用于返回當前線程的ID。 pthread_detach用于是指定線程變?yōu)?strong>分離狀態(tài),就像進程脫離終端而變?yōu)楹笈_進程類似。成功返回0,否則返回Exxx(為正數(shù))。變?yōu)榉蛛x狀態(tài)的線程,如果線程退出,它的所有資源將全部釋放。而如果不是分離狀態(tài),線程必須保留它的線程ID,退出狀態(tài)直到其它線程對它調(diào)用了pthread_join。
pthread_exit用于終止線程,可以指定返回值,以便其他線程通過pthread_join函數(shù)獲取該線程的返回值。
知道了這些函數(shù)之后,我們試圖來完成本文一開始的問題: 1)有一int型全局變量g_Flag初始值為0; 2)在主線稱中起動線程1,打印“this is thread1”,并將g_Flag設置為1 3)在主線稱中啟動線程2,打印“this is thread2”,并將g_Flag設置為2 這3點很簡單嘛!??!不就是調(diào)用pthread_create創(chuàng)建線程。代碼如下: /* * 1)有一int型全局變量g_Flag初始值為0; * * 2)在主線稱中起動線程1,打印“this is thread1”,并將g_Flag設置為1 * * 3)在主線稱中啟動線程2,打印“this is thread2”,并將g_Flag設置為2 * */ #include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<errno.h> #include<unistd.h> int g_Flag=0; void* thread1(void*); void* thread2(void*); /* * when program is started, a single thread is created, called the initial thread or main thread. * Additional threads are created by pthread_create. * So we just need to create two thread in main(). */ int main(int argc, char** argv) { printf("enter main\n"); pthread_t tid1, tid2; int rc1=0, rc2=0; rc2 = pthread_create(&tid2, NULL, thread2, NULL); if(rc2 != 0) printf("%s: %d\n",__func__, strerror(rc2)); rc1 = pthread_create(&tid1, NULL, thread1, &tid2); if(rc1 != 0) printf("%s: %d\n",__func__, strerror(rc1)); printf("leave main\n"); exit(0); } /* * thread1() will be execute by thread1, after pthread_create() * it will set g_Flag = 1; */ void* thread1(void* arg) { printf("enter thread1\n"); printf("this is thread1, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self()); g_Flag = 1; printf("this is thread1, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self()); printf("leave thread1\n"); pthread_exit(0); } /* * thread2() will be execute by thread2, after pthread_create() * it will set g_Flag = 2; */ void* thread2(void* arg) { printf("enter thread2\n"); printf("this is thread2, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self()); g_Flag = 2; printf("this is thread1, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self()); printf("leave thread2\n"); pthread_exit(0); } 這樣就完成了1)、2)、3)這三點要求。編譯執(zhí)行得如下結果: netsky@ubuntu:~/workspace/pthead_test$ gcc -lpthread test.c 如果程序中使用到了pthread庫中的函數(shù),除了要#include<pthread.h>,在編譯的時候還有加上-lpthread 選項。 netsky@ubuntu:~/workspace/pthead_test$ ./a.out 或者是: netsky@ubuntu:~/workspace/pthead_test$ ./a.out
“4) 線程序1需要在線程2退出后才能退出”第4點也很容易解決,直接在thread1的函數(shù)退出之前調(diào)用pthread_join就OK了。 4、線程之間的互斥上面的代碼似乎很好的解決了問題的前面4點要求,其實不然?。?!因為g_Flag是一個全局變量,線程thread1和thread2可以同時對它進行操作,需要對它進行加鎖保護,thread1和thread2要互斥訪問才行。下面我們就介紹如何加鎖保護——互斥鎖。
互斥鎖的相關操作函數(shù)如下: #include <pthread.h> int pthread_mutex_lock(pthread_mutex_t * mptr); int pthread_mutex_unlock(pthread_mutex_t * mptr); //Both return: 0 if OK, positive Exxx value on error 在對臨界資源進行操作之前需要pthread_mutex_lock先加鎖,操作完之后pthread_mutex_unlock再解鎖。而且在這之前需要聲明一個pthread_mutex_t類型的變量,用作前面兩個函數(shù)的參數(shù)。具體代碼見第5節(jié)。 5、線程之間的同步第5點——主線程在檢測到g_Flag從1變?yōu)?,或者從2變?yōu)?的時候退出。就需要用到線程同步技術!線程同步需要條件變量。
“使用條件變量可以以原子方式阻塞線程,直到某個特定條件為真為止。”即可用到第5點,主線程main函數(shù)阻塞于等待g_Flag從1變?yōu)?,或者從2變?yōu)?。條件變量的相關函數(shù)如下: #include <pthread.h> int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr); int pthread_cond_signal(pthread_cond_t *cptr); //Both return: 0 if OK, positive Exxx value on error pthread_cond_wait用于等待某個特定的條件為真,pthread_cond_signal用于通知阻塞的線程某個特定的條件為真了。在調(diào)用者兩個函數(shù)之前需要聲明一個pthread_cond_t類型的變量,用于這兩個函數(shù)的參數(shù)。 為什么條件變量始終與互斥鎖一起使用,對條件的測試是在互斥鎖(互斥)的保護下進行的呢?因為“某個特性條件”通常是在多個線程之間共享的某個變量?;コ怄i允許這個變量可以在不同的線程中設置和檢測。 通常,pthread_cond_wait只是喚醒等待某個條件變量的一個線程。如果需要喚醒所有等待某個條件變量的線程,需要調(diào)用: int pthread_cond_broadcast (pthread_cond_t * cptr); 默認情況下面,阻塞的線程會一直等待,知道某個條件變量為真。如果想設置最大的阻塞時間可以調(diào)用: int pthread_cond_timedwait (pthread_cond_t * cptr, pthread_mutex_t *mptr, const struct timespec *abstime); 如果時間到了,條件變量還沒有為真,仍然返回,返回值為ETIME。 6、試題最終代碼通過前面的介紹,我們可以輕松的寫出代碼了,如下所示: /* 是否熟悉POSIX多線程編程技術?如熟悉,編寫程序完成如下功能: 1)有一int型全局變量g_Flag初始值為0; 2)在主線稱中起動線程1,打印“this is thread1”,并將g_Flag設置為1 3)在主線稱中啟動線程2,打印“this is thread2”,并將g_Flag設置為2 4)線程序1需要在線程2退出后才能退出 5)主線程在檢測到g_Flag從1變?yōu)?,或者從2變?yōu)?的時候退出 */ #include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<errno.h> #include<unistd.h> typedef void* (*fun)(void*); int g_Flag=0; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; void* thread1(void*); void* thread2(void*); /* * when program is started, a single thread is created, called the initial thread or main thread. * Additional threads are created by pthread_create. * So we just need to create two thread in main(). */ int main(int argc, char** argv) { printf("enter main\n"); pthread_t tid1, tid2; int rc1=0, rc2=0; rc2 = pthread_create(&tid2, NULL, thread2, NULL); if(rc2 != 0) printf("%s: %d\n",__func__, strerror(rc2)); rc1 = pthread_create(&tid1, NULL, thread1, &tid2); if(rc1 != 0) printf("%s: %d\n",__func__, strerror(rc1)); pthread_cond_wait(&cond, &mutex); printf("leave main\n"); exit(0); } /* * thread1() will be execute by thread1, after pthread_create() * it will set g_Flag = 1; */ void* thread1(void* arg) { printf("enter thread1\n"); printf("this is thread1, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self()); pthread_mutex_lock(&mutex); if(g_Flag == 2) pthread_cond_signal(&cond); g_Flag = 1; printf("this is thread1, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self()); pthread_mutex_unlock(&mutex); pthread_join(*(pthread_t*)arg, NULL); printf("leave thread1\n"); pthread_exit(0); } /* * thread2() will be execute by thread2, after pthread_create() * it will set g_Flag = 2; */ void* thread2(void* arg) { printf("enter thread2\n"); printf("this is thread2, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self()); pthread_mutex_lock(&mutex); if(g_Flag == 1) pthread_cond_signal(&cond); g_Flag = 2; printf("this is thread2, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self()); pthread_mutex_unlock(&mutex); printf("leave thread2\n"); pthread_exit(0); } 編譯運行可以得到符合要求的結果! ——這篇日志就算是獻給我自己生日的禮物! 加油,努力,不要放棄! |
|