多线程程序设计
一、線程理論基礎
1. 多線程
線程(thread)技術早在60年代就被提出,但真正應用多線程到操作系統(tǒng)中去,是在80年代中期,solaris是這方面的佼佼者。傳統(tǒng)的Unix也支持線程的概念,但是在一個進程(process)中只允許有一個線程,這樣多線程就意味著多進程。現(xiàn)在,多線程技術已經(jīng)被許多操作系統(tǒng)所支持,包括Windows/NT、Linux。
2.?為什么有了進程,還要引入線程呢?使用多線程到底有哪些好處?
使用多線程的理由之一是:
和進程相比,它是一種非常“節(jié)儉”的多任務操作方式。在Linux系統(tǒng)下,啟動一個新的進程必須分配給它獨立的地址空間,建立眾多的數(shù)據(jù)表來維護它的代碼段、堆棧段和數(shù)據(jù)段,這是一種"昂貴"的多任務工作方式。
運行于一個進程中的多個線程,它們之間使用相同的地址空間,而且線程間彼此切換所需的時間也遠遠小于進程間切換所需要的時間。據(jù)統(tǒng)計,一個進程的開銷大約是一個線程開銷的30倍左右。
使用多線程的理由之二是:
線程間方便的通信機制。對不同進程來說,它們具有獨立的數(shù)據(jù)空間,要進行數(shù)據(jù)的傳遞只能通過進程間通信的方式進行,這種方式不僅費時,而且很不方便。線程則不然,由于同一進程下的線程之間共享數(shù)據(jù)空間,所以一個線程的數(shù)據(jù)可以直接為其它線程所用,這不僅快捷,而且方便。
除了以上所說的優(yōu)點外,多線程程序作為一種多任務、并發(fā)的工作方式,有如下優(yōu)點:
1)使多CPU系統(tǒng)更加有效。操作系統(tǒng)會保證當線程數(shù)不大于CPU數(shù)目時,不同的線程運行于不同的CPU上。
2)改善程序結構。一個既長又復雜的進程可以考慮分為多個線程,成為幾個獨立或半獨立的運行部分,這樣的程序會利于理解和修改。
3.?Linux系統(tǒng)下的多線程
Linux系統(tǒng)下的多線程遵循POSIX線程接口,稱為pthread。編寫Linux下的多線程程序,需要使用頭文件pthread.h,連接時需要使用庫libpthread.a。
二、 多線程程序設計
1. 創(chuàng)建線程
#include <pthread.h>
int pthread_create(pthread_t * tidp,const pthread_attr_t *attr, void *(*start_rtn)(void),void *arg)
tidp:線程id
attr:線程屬性(通常為空)
start_rtn:線程要執(zhí)行的函數(shù),函數(shù)返回值為空指針型
arg:start_rtn的參數(shù),線程函數(shù)的參數(shù),必須為指針型
2.?編譯
因為pthread的庫不是linux系統(tǒng)的庫,所以在進行編譯的時候要加上-lpthread
# gcc filename -lpthread
3. 實例分析
thread_int.c代碼如下:
#include <stdio.h> #include <pthread.h> #include <unistd.h>void *create(void *arg) // pthread_create()函數(shù)最后一個參數(shù) { int *num;num=(int *)arg;printf("creat parameter is %d\n",*num);return (void *)0; }int main(int argc, char *argv[]) {pthread_t tidp;int error;int test=4;int *attr=&test;error=pthread_create(&tidp,NULL,create,(void *)attr); // 參數(shù)類型強制類型轉換if(error){printf("pthread_creat is created in not created ...\n");return -1;}sleep(1);printf("pthread_create is created ...\n");return 0; }thread_share.c代碼如下:
#include <stdio.h> #include <pthread.h>int a=1;void *create(void *arg) {printf("new pthread ... \n");printf("a=%d \n",a);return (void *)0; }int main(int argc,char *argv[]) {pthread_t tidp;int error;int a=5;error=pthread_create(&tidp,NULL,create,NULL);if(error!=0){printf("new thread is not create ... \n");return -1;}sleep(1);printf("new thread is created ... \n");return 0; }全局變量在線程函數(shù)中也是有效的。
thread_struct.c代碼如下:
#include <stdio.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h>struct menber {int a;char *s; };/*線程執(zhí)行函數(shù)*/ void *create(void *arg) {struct menber *temp;temp=(struct menber *)arg;printf("menber->a = %d \n",temp->a);printf("menber->s = %s \n",temp->s);return (void *)0; }int main(int argc,char *argv[]) {pthread_t tidp;int error;struct menber *b;/*為結構體指針b分配內(nèi)存并賦值*/b=(struct menber *)malloc( sizeof(struct menber) );b->a = 4;b->s = "zieckey";/*創(chuàng)建線程并運行線程執(zhí)行函數(shù)*/error = pthread_create(&tidp, NULL, create, (void *)b);if( error ){printf("phread is not created...\n");return -1;}sleep(1); //進程睡眠一秒使線程執(zhí)行完后進程才會結束printf("pthread is created...\n");return 0; }4.終止線程
如果進程中任何一個線程中調(diào)用exit或_exit,那么整個進程都會終止。線程的正常退出方式有:
(1) 線程從啟動例程中返回
(2) 線程可以被另一個進程終止
(3) 線程自己調(diào)用pthread_exit函數(shù)
#include <pthread.h>
void pthread_exit(void * rval_ptr)
功能:終止調(diào)用線程
Rval_ptr:線程退出返回值的指針,這個值pthread_join()函數(shù)可以接受。
5. 線程等待
#include <pthread.h>
int pthread_join(pthread_t tid,void **rval_ptr)
功能:阻塞調(diào)用線程,直到指定的線程終止。
Tid :等待退出的線程id
Rval_ptr:線程退出的返回值的指針,函數(shù)pthread_exit()函數(shù)或return返回的值。
thread_join.c代碼如下:
#include <pthread.h> #include <unistd.h> #include <stdio.h> void *thread(void *str) {int i;for (i = 0; i < 3; ++i){sleep(2);printf( "This in the thread : %d\n" , i );}return NULL; }int main() {pthread_t pth;int i;/*創(chuàng)建線程并執(zhí)行線程執(zhí)行函數(shù)*/int ret = pthread_create(&pth, NULL, thread, NULL);printf("The main process will be to run,but will be blocked soon\n"); /*阻塞等待線程退出*/pthread_join(pth, NULL);printf("thread was exit\n");for (i = 0; i < 3; ++i){sleep(1);printf( "This in the main : %d\n" , i );}return 0; }6. 線程標識
#include <pthread.h>
pthread_t pthread_self(void)
功能:獲取調(diào)用線程的 thread identifier
7. 清除
線程終止有兩種情況:正常終止和非正常終止。線程主動調(diào)用pthread_exit或者從線程函數(shù)中return都將使線程正常退出,這是可預見的退出方式;非正常終止是線程在其他線程的干預下,或者由于自身運行出錯(比如訪問非法地址)而退出,這種退出方式是不可預見的。
不論是可預見的線程終止還是異常終止,都會存在資源釋放的問題,如何保證線程終止時能順利的釋放掉自己所占用的資源,是一個必須考慮解決的問題。
從pthread_cleanup_push的調(diào)用點到pthread_cleanup_pop之間的程序段中的終止動作(包括調(diào)用pthread_exit()和異常終止,不包括return)都將執(zhí)行pthread_cleanup_push()所指定的清理函數(shù)。
#include <pthread.h>
void pthread_cleanup_push(void (*rtn)(void *),void *arg)
功能:將清除函數(shù)壓入清除棧
Rtn:清除函數(shù)
Arg:清除函數(shù)的參數(shù)
#include <pthread.h>
void pthread_cleanup_pop(int execute)
功能:將清除函數(shù)彈出清除棧
參數(shù):Execute執(zhí)行到pthread_cleanup_pop()時是否在彈出清理函數(shù)的同時執(zhí)行該函數(shù),非0:執(zhí)行; 0:不執(zhí)行
thread_clean.c代碼如下:
#include <stdio.h> #include <pthread.h> #include <unistd.h>/*線程清理函數(shù)*/ void *clean(void *arg) {printf("cleanup :%s\n",(char *)arg);return (void *)0; }/*線程1的執(zhí)行函數(shù)*/ void *thr_fn1(void *arg) {printf("thread 1 start \n");/*將線程清理函數(shù)壓入清除棧兩次*/pthread_cleanup_push( (void*)clean,"thread 1 first handler");pthread_cleanup_push( (void*)clean,"thread 1 second hadler");printf("thread 1 push complete \n");if(arg){return((void *)1); //線程運行到這里會結束,后面的代碼不會被運行。由于是用return退出,所以不會執(zhí)行線程清理函數(shù)。}pthread_cleanup_pop(0);pthread_cleanup_pop(0);return (void *)1; }/*線程2的執(zhí)行函數(shù)*/ void *thr_fn2(void *arg) {printf("thread 2 start \n");/*將線程清理函數(shù)壓入清除棧兩次*/pthread_cleanup_push( (void*)clean,"thread 2 first handler");pthread_cleanup_push( (void*)clean,"thread 2 second handler");printf("thread 2 push complete \n");if(arg){pthread_exit((void *)2);//線程運行到這里會結束,后面的代碼不會被運行。由于是用pthread_exit退出,所以會執(zhí)行線程清理函數(shù)。執(zhí)行的順序是先壓進棧的后執(zhí)行,即后進先出。}pthread_cleanup_pop(0);pthread_cleanup_pop(0);pthread_exit((void *)2); }int main(void) {int err;pthread_t tid1,tid2;void *tret;/*創(chuàng)建線程1并執(zhí)行線程執(zhí)行函數(shù)*/err=pthread_create(&tid1,NULL,thr_fn1,(void *)1);if(err!=0){printf("error .... \n");return -1;}/*創(chuàng)建線程2并執(zhí)行線程執(zhí)行函數(shù)*/err=pthread_create(&tid2,NULL,thr_fn2,(void *)1);if(err!=0){printf("error .... \n");return -1;}/*阻塞等待線程1退出,并獲取線程1的返回值*/err=pthread_join(tid1,&tret);if(err!=0){printf("error .... \n");return -1;}printf("thread 1 exit code %d \n",(int)tret);/*阻塞等待線程2退出,并獲取線程2的返回值*/err=pthread_join(tid2,&tret);if(err!=0){printf("error .... ");return -1;}printf("thread 2 exit code %d \n",(int)tret);return 1; }三、線程同步
進行多線程編程,因為無法知道哪個線程會在哪個時候對共享資源進行操作,因此讓如何保護共享資源變得復雜,通過下面這些技術的使用,可以解決線程之間對資源的競爭:
1)互斥量Mutex
2)信號燈Semaphore
3)條件變量Conditions
?
為什么需要互斥量:
Item * p =queue_list;
Queue_list=queue_list->next;
process_job(p);
free(p);
當線程1處理完Item *p=queue_list后,系統(tǒng)停止線程1的運行,改而運行線程2。線程2照樣取出頭節(jié)點,然后進行處理,最后釋放了該節(jié)點。過了段時間,線程1重新得到運行。而這個時候,p所指向
的節(jié)點已經(jīng)被線程2釋放掉,而線程1對此毫無知曉。他會接著運行process_job(p)。而這將導致無法預料的后果!
對于這種情況,系統(tǒng)給我們提供了互斥量。線程在取出頭節(jié)點前必須要等待互斥量,如果此時有其他線程已經(jīng)獲得該互斥量,那么該線程將會阻塞在這里。只有等到其他線程釋放掉該互斥量后,該線程才有可能得到該互斥量。互斥量從本質上說就是一把鎖, 提供對共享資源的保護訪問。
在Linux中, 互斥量使用類型pthread_mutex_t表示。在使用前, 要對它進行初始化:
1)對于靜態(tài)分配的互斥量, 可以把它設置為默認的mutex對象PTHREAD_MUTEX_INITIALIZER
2)對于動態(tài)分配的互斥量, 在申請內(nèi)存(malloc)之后, 通過pthread_mutex_init進行初始化, 并且在釋放內(nèi)存(free)前需要調(diào)用pthread_mutex_destroy。
創(chuàng)建
#include <pthread.h>
1)int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)
2)int pthread_mutex_destroy(pthread_mutex_t *mutex)
加鎖
對共享資源的訪問, 要使用互斥量進行加鎖, 如果互斥量已經(jīng)上了鎖, 調(diào)用線程會阻塞, 直到互斥量被解鎖。
1)int pthread_mutex_lock(pthread_mutex_t *mutex)
2)int pthread_mutex_trylock(pthread_mutex_t *mutex)
返回值: 成功則返回0, 出錯則返回錯誤編號。
trylock是非阻塞調(diào)用模式, 如果互斥量沒被鎖住, trylock函數(shù)將對互斥量加鎖, 并獲得對共享資源的訪問權限; 如果互斥量被鎖住了, trylock函數(shù)將不會阻塞等待而直接返回EBUSY, 表示共享資源處于忙狀態(tài)。
解鎖
在操作完成后,必須給互斥量解鎖,也就是前面所說的釋放。這樣其他等待該鎖的線程才有機會獲得該鎖,否則其他線程將會永遠阻塞。
int pthread_mutex_unlock(pthread_mutex_t *mutex)
互斥量PK信號量
1)Mutex是一把鑰匙,一個人拿了就可進入一個房間,出來的時候把鑰匙交給隊列的第一個。
2)Semaphore是一件可以容納N人的房間,如果人不滿就可以進去,如果人滿了,就要等待有人出來。對于N=1的情況,稱為binary semaphore。
3)Binary semaphore與Mutex的差異:
1. mutex要由獲得鎖的線程來釋放(誰獲得,誰釋放)。而semaphore可以由其它線程釋放
2. 初始狀態(tài)可能不一樣:mutex的初始值是1 ,而semaphore的初始值可能是0(或者為1)。
?
?
?
?
《新程序員》:云原生和全面數(shù)字化實踐50位技術專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結
- 上一篇: 【Visual C++】游戏开发笔记十四
- 下一篇: Android类似于滚动的通知栏实现