9.2 Linux線程編程 9.2.1 線程基本編程 這里要講的線程相關操作都是用戶空間中的線程的操作。在Linux中,一般pthread線程庫是一套通用的線程庫,是由POSIX提出的,因此具有很好的可移植性。 (1)函數說明。 創建線程實際上就是確定調用該線程函數的入口點,這里通常使用的函數是pthread_create()。在線程創建以后,就開始運行相關的線程函數,在該函數運行完之后,該線程也就退出了,這也是線程退出一種方法。另一種退出線程的方法是使用函數pthread_exit(),這是線程的主動行為。這里要注意的是,在使用線程函數時,不能隨意使用exit()退出函數進行出錯處理,由于exit()的作用是使調用進程終止,往往一個進程包含多個線程,因此,在使用exit()之后,該進程中的所有線程都終止了。因此,在線程中就可以使用pthread_exit()來代替進程中的exit()。 由于一個進程中的多個線程是共享數據段的,因此通常在線程退出之后,退出線程所占用的資源并不會隨著線程的終止而得到釋放。正如進程之間可以用wait()系統調用來同步終止并釋放資源一樣,線程之間也有類似機制,那就是pthread_join()函數。pthread_join()可以用于將當前線程掛起來等待線程的結束。這個函數是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束為止,當函數返回時,被等待線程的資源就被收回。 前面已提到線程調用pthread_exit()函數主動終止自身線程。但是在很多線程應用中,經常會遇到在別的線程中要終止另一個線程的執行的問題。此時調用pthread_cancel()函數實現這種功能,但在被取消的線程的內部需要調用pthread_setcancel()函數和pthread_setcanceltype()函數設置自己的取消狀態,例如被取消的線程接收到另一個線程的取消請求之后,是接受還是忽略這個請求;如果接受,是立刻進行終止操作還是等待某個函數的調用等。 (2)函數格式。 表9.1列出了pthread_create()函數的語法要點。 表9.2列出了pthread_exit()函數的語法要點。 表9.3列出了pthread_join()函數的語法要點。 表9.4列出了pthread_cancel()函數的語法要點。 (3)函數使用。 以下實例中創建了3個線程,為了更好地描述線程之間的并行執行,讓3個線程重用同一個執行函數。每個線程都有5次循環(可以看成5個小任務),每次循環之間會隨機等待1~10s的時間,意義在于模擬每個任務的到達時間是隨機的,并沒有任何特定規律。 /* thread.c */ #include #include #include #define THREAD_NUMBER 3 /*線程數*/ #define REPEAT_NUMBER 5 /*每個線程中的小任務數*/ #define DELAY_TIME_LEVELS 10.0 /*小任務之間的最大時間間隔*/ void *thrd_func(void *arg) { /* 線程函數例程 */ int thrd_num = (int)arg; int delay_time = 0; int count = 0; printf("Thread %d is starting\n", thrd_num); for (count = 0; count 多線程 */ res = pthread_create(&thread[no], NULL, thrd_func, (void*)no); if (res != 0) { printf("Create thread %d failed\n", no); exit(res); } } printf("Create treads success\n Waiting for threads to finish...\n"); for (no = 0; no 互斥 由于線程共享進程的資源和地址空間,因此在對這些資源進行操作時,必須考慮到線程間資源訪問的同步與互斥問題。這里主要介紹POSIX中兩種線程同步機制,分別為互斥鎖和信號量。這兩個同步機制可以互相通過調用對方來實現,但互斥鎖更適合用于同時可用的資源是惟一的情況;信號量更適合用于同時可用的資源為多個的情況。 1.互斥鎖線程控制 (1)函數說明。 互斥鎖是用一種簡單的加鎖方法來控制對共享資源的原子操作。這個互斥鎖只有兩種狀態,也就是上鎖和解鎖,可以把互斥鎖看作某種意義上的全局變量。在同一時刻只能有一個線程掌握某個互斥鎖,擁有上鎖狀態的線程能夠對共享資源進行操作。若其他線程希望上鎖一個已經被上鎖的互斥鎖,則該線程就會掛起,直到上鎖的線程釋放掉互斥鎖為止?梢哉f,這把互斥鎖保證讓每個線程對共享資源按順序進行原子操作。 互斥鎖機制主要包括下面的基本函數。 n 互斥鎖初始化:pthread_mutex_init() n 互斥鎖上鎖:pthread_mutex_lock() n 互斥鎖判斷上鎖:pthread_mutex_trylock() n 互斥鎖接鎖:pthread_mutex_unlock() n 消除互斥鎖:pthread_mutex_destroy() 其中,互斥鎖可以分為快速互斥鎖、遞歸互斥鎖和檢錯互斥鎖。這3種鎖的區別主要在于其他未占有互斥鎖的線程在希望得到互斥鎖時是否需要阻塞等待。快速鎖是指調用線程會阻塞直至擁有互斥鎖的線程解鎖為止。遞歸互斥鎖能夠成功地返回,并且增加調用線程在互斥上加鎖的次數,而檢錯互斥鎖則為快速互斥鎖的非阻塞版本,它會立即返回并返回一個錯誤信息。默認屬性為快速互斥鎖。 (2)函數格式。 表9.5列出了pthread_mutex_init()函數的語法要點。 表9.6列出了pthread_mutex_lock()等函數的語法要點。 (3)使用實例。 下面的實例是在9.2.1小節示例代碼的基礎上增加互斥鎖功能,實現原本獨立與無序的多個線程能夠按順序執行。 /*thread_mutex.c*/ #include #include #include #define THREAD_NUMBER 3 /* 線程數 */ #define REPEAT_NUMBER 3 /* 每個線程的小任務數 */ #define DELAY_TIME_LEVELS 10.0 /*小任務之間的最大時間間隔*/ pthread_mutex_t mutex; void *thrd_func(void *arg) { int thrd_num = (int)arg; int delay_time = 0, count = 0; int res; /* 互斥鎖上鎖 */ res = pthread_mutex_lock(&mutex); if (res) { printf("Thread %d lock failed\n", thrd_num); pthread_exit(NULL); } printf("Thread %d is starting\n", thrd_num); for (count = 0; count 互斥。信號量本質上是一個非負的整數計數器,它被用來控制對公共資源的訪問。這里先來簡單復習一下PV原子操作的工作原理。 PV原子操作是對整數計數器信號量sem的操作。一次P操作使sem減一,而一次V操作使sem加一。進程(或線程)根據信號量的值來判斷是否對公共資源具有訪問權限。當信號量sem的值大于等于零時,該進程(或線程)具有公共資源的訪問權限;相反,當信號量sem的值小于零時,該進程(或線程)就將阻塞直到信號量sem的值大于等于0為止。 PV原子操作主要用于進程或線程間的同步和互斥這兩種典型情況。若用于互斥,幾個進程(或線程)往往只設置一個信號量sem,它們的操作流程如圖9.2所示。 當信號量用于同步操作時,往往會設置多個信號量,并安排不同的初始值來實現它們之間的順序執行,它們的操作流程如圖9.3所示。 圖9.2 信號量互斥操作 圖9.3 信號量同步操作 (2)函數說明。 Linux實現了POSIX的無名信號量,主要用于線程間的互斥與同步。這里主要介紹幾個常見函數。 n sem_init()用于創建一個信號量,并初始化它的值。 n sem_wait()和sem_trywait()都相當于P操作,在信號量大于零時它們都能將信號量的值減一,兩者的區別在于若信號量小于零時,sem_wait()將會阻塞進程,而sem_trywait()則會立即返回。 n sem_post()相當于V操作,它將信號量的值加一同時發出信號來喚醒等待的進程。 n sem_getvalue()用于得到信號量的值。 n sem_destroy()用于刪除信號量。 (3)函數格式。 表9.7列出了sem_init()函數的語法要點。 表9.8列出了sem_wait()等函數的語法要點。 (4)使用實例。 在前面已經通過互斥鎖同步機制實現了多線程的順序執行。下面的例子是用信號量同步機制實現3個線程之間的有序執行,只是執行順序是跟創建線程的順序相反。 /*thread_sem.c*/ #include #include #include #include #define THREAD_NUMBER 3 /* 線程數 */ #define REPEAT_NUMBER 3 /* 每個線程中的小任務數 */ #define DELAY_TIME_LEVELS 10.0 /*小任務之間的最大時間間隔*/ sem_t sem[THREAD_NUMBER]; void *thrd_func(void *arg) { int thrd_num = (int)arg; int delay_time = 0; int count = 0; /* 進行P操作 */ sem_wait(&sem[thrd_num]); printf("Thread %d is starting\n", thrd_num); for (count = 0; count = 0; no--) { res = pthread_join(thread[no], &thrd_ret); if (!res) { printf("Thread %d joined\n", no); } else { printf("Thread %d join failed\n", no); } /* 進行V操作 */ sem_post(&sem[(no + THREAD_NUMBER - 1) % THREAD_NUMBER]); } for (no = 0; no Linux中采用“一對一”的線程機制,也就是一個用戶線程對應一個內核線程。綁定屬性就是指一個用戶線程固定地分配給一個內核線程,因為CPU時間片的調度是面向內核線程(也就是輕量級進程)的,因此具有綁定屬性的線程可以保證在需要的時候總有一個內核線程與之對應。而與之對應的非綁定屬性就是指用戶線程和內核線程的關系不是始終固定的,而是由系統來控制分配的。 n 分離屬性。 分離屬性是用來決定一個線程以什么樣的方式來終止自己。在非分離情況下,當一個線程結束時,它所占用的系統資源并沒有被釋放,也就是沒有真正的終止。只有當pthread_join()函數返回時,創建的線程才能釋放自己占用的系統資源。而在分離屬性情況下,一個線程結束時立即釋放它所占有的系統資源。這里要注意的一點是,如果設置一個線程的分離屬性,而這個線程運行又非常快,那么它很可能在pthread_create()函數返回之前就終止了,它終止以后就可能將線程號和系統資源移交給其他的線程使用,這時調用pthread_create()的線程就得到了錯誤的線程號。 這些屬性的設置都是通過特定的函數來完成的,通常首先調用pthread_attr_init()函數進行初始化,之后再調用相應的屬性設置函數,最后調用pthread_attr_destroy()函數對分配的屬性結構指針進行清理和回收。設置綁定屬性的函數為pthread_attr_setscope(),設置線程分離屬性的函數為pthread_attr_setdetachstate(),設置線程優先級的相關函數為pthread_attr_getschedparam()(獲取線程優先級)和pthread_attr_setschedparam()(設置線程優先級)。在設置完這些屬性后,就可以調用pthread_create()函數來創建線程了。 (2)函數格式。 表9.9列出了pthread_attr_init()函數的語法要點。 表9.10列出了pthread_attr_setscope()函數的語法要點。 表9.11列出了pthread_attr_setdetachstate()函數的語法要點。 表9.12 pthread_attr_getschedparam()函數語法要點 表9.13列出了pthread_attr_setschedparam()函數的語法要點。 (3)使用實例。 下面的實例是在我們已經很熟悉的實例的基礎上增加線程屬性設置的功能。為了避免不必要的復雜性,這里就創建一個線程,這個線程具有綁定和分離屬性,而且主線程通過一個finish_flag標志變量來獲得線程結束的消息,而并不調用pthread_join()函數。 /*thread_attr.c*/ #include #include #include #define REPEAT_NUMBER 3 /* 線程中的小任務數 */ #define DELAY_TIME_LEVELS 10.0 /* 小任務之間的最大時間間隔 */ int finish_flag = 0; void *thrd_func(void *arg) { int delay_time = 0; int count = 0; printf("Thread is starting\n"); for (count = 0; count < REPEAT_NUMBER; count++) { delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1; sleep(delay_time); printf("\tThread : job %d delay = %d\n", count, delay_time); } printf("Thread finished\n"); finish_flag = 1; pthread_exit(NULL); } int main(void) { pthread_t thread; pthread_attr_t attr; int no = 0, res; void * thrd_ret; srand(time(NULL)); /* 初始化線程屬性對象 */ res = pthread_attr_init(&attr); if (res != 0) { printf("Create attribute failed\n"); exit(res); } /* 設置線程綁定屬性 */ res = pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); /* 設置線程分離屬性 */ res += pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (res != 0) { printf("Setting attribute failed\n"); exit(res); } res = pthread_create(&thread, &attr, thrd_func, NULL); if (res != 0) { printf("Create thread failed\n"); exit(res); } /* 釋放線程屬性對象 */ pthread_attr_destroy(&attr); printf("Create tread success\n"); while(!finish_flag) { printf("Waiting for thread to finish...\n"); sleep(2); } return 0; } 接下來可以在線程運行前后使用“free”命令查看內存的使用情況。以下是運行結果: $ ./thread_attr Create tread success Waiting for thread to finish... Thread is starting Waiting for thread to finish... Thread : job 0 delay = 3 Waiting for thread to finish... Thread : job 1 delay = 2 Waiting for thread to finish... Waiting for thread to finish... Waiting for thread to finish... Waiting for thread to finish... Thread : job 2 delay = 9 Thread finished /* 程序運行之前 */ $ free total used free shared buffers cached Mem: 255556 191940 63616 10 5864 61360 -/+ buffers/cache: 124716 130840 Swap: 377488 18352 359136 /* 程序運行之中 */ $ free total used free shared buffers cached Mem: 255556 191948 63608 10 5888 61336 -/+ buffers/cache: 124724 130832 Swap: 377488 18352 359136 /* 程序運行之后 */ $ free total used free shared buffers cached Mem: 255556 191940 63616 10 5904 61320 -/+ buffers/cache: 124716 130840 Swap: 377488 18352 359136 可以看到,線程在運行結束后就收回了系統資源,并釋放內存。 |