linux多线程简介
一、 多線程概念
和多進程相比,多線程是一種比較節省資源的多任務操作方式。啟動一個新的進程必須分配給它獨立的地址空間,每個進程都有自己的堆棧段和數據段,系統開銷比較高,進行數據的傳遞只能通過進程間通信的方式進行。在同一個進程中,可以運行多個線程,運行于同一個進程中的多個線程,它們彼此之間使用相同的地址空間,共享全局變量和對象,啟動一個線程所消耗的資源比啟動一個進程所消耗的資源要少。
多線程可以共享資源(變量和對象),對編程帶來了方便,但是某些對象雖然可以共享,但在同一個時間只能一個線程使用,多個線程同時使用會產生沖突,例如socket連接,數據庫連接池。
二、相關API
1、創建線程
在Linux下,采用pthread_create函數來創建一個新的線程。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);參數thread為為指向線程標識符的地址。
參數attr用于設置線程屬性,一般為空,表示使用默認屬性。
參數start_routine是線程運行函數的地址,填函數名就可以了。
參數arg是線程運行函數的參數。新創建的線程從start_routine函數的地址開始運行,該函數只有一個無類型指針參數arg。
在編譯時注意加上-lpthread參數,以調用靜態鏈接庫。因為pthread并非Linux系統的默認庫。
2、線程的終止
如果進程中的任一線程調用了exit,則整個進程會終止,所以,在線程的start_routine函數中,不能采用exit。
線程的終止有三種方式:
1)線程的start_routine函數代碼結束,自然消亡。
2)線程的start_routine函數調用pthread_exit結束。
3)被主進程或其它線程中止。
pthread_exit函數的聲明如下:
void pthread_exit(void *retval);參數retval填空,即0。
3、線程資源的回收
線程有joinable和unjoinable兩種狀態,如果線程是joinable狀態,當線程主函數終止時(自己退出或調用pthread_exit退出)不會釋放線程所占用內存資源和其它資源,這種線程被稱為“僵尸線程”。創建線程時默認是非分離的,或者稱為可連接的(joinable)。
避免僵尸線程就是如何正確的回收線程資源,有四種方法:
1)方法一(等待線程結束):
創建線程后,在主線程中調用pthread_join等待線程退出,類似于進程中wait/waitpid回收僵尸進程,一般不會采用這種方法,因為pthread_join會發生阻塞。
pthread_join(pthid,NULL);2)方法二:
創建線程前,調用pthread_attr_setdetachstate將線程設為detached,這樣線程退出時,系統自動回收線程資源。
pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); // 設置線程的屬性。pthread_create(&pthid,&attr,pth_main,(void*)((long)TcpServer.m_clientfd);3)方法三(分離線程->主):
創建線程后,在創建線程的程序中(主線程中)調用pthread_detach將新創建的線程設置為detached狀態。
pthread_detach(pthid);4)方法四(分離線程->子):
在線程主函數中(子線程中)調用pthread_detach改變自己的狀態。調用后和主線程分離,子線程結束時自己立即回收資源。
pthread_detach(pthread_self());4、查看線程
1)在top命令中,如果加上-H參數,top中的每一行顯示的不是進程,而是一個線程。
2)在ps命令中加-xH參數也可以顯示線程,加grep可以過濾內容。
ps -xH ps -xH|grep test三、使用多線程實現socket服務端
#include <stdio.h> #include <string.h> #include <unistd.h> #include <netdb.h> #include <stdlib.h> #include <signal.h> #include <pthread.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h>class CTcpServer { public:int m_listenfd; // 服務端用于監聽的socketint m_clientfd; // 客戶端連上來的socketCTcpServer();bool InitServer(int port); // 初始化服務端bool Accept(); // 等待客戶端的連接// 向對端發送報文int Send(const void *buf,const int buflen);// 接收對端的報文int Recv(void *buf,const int buflen);// void CloseClient(); // 關閉客戶端的socket,多線程服務端不需要這個函數。// void CloseListen(); // 關閉用于監聽的socket,多線程服務端不需要這個函數。~CTcpServer(); };CTcpServer TcpServer;// SIGINT和SIGTERM的處理函數 void EXIT(int sig) {printf("程序退出,信號值=%d\n",sig);close(TcpServer.m_listenfd); // 手動關閉m_listenfd,釋放資源exit(0); }// 與客戶端通信線程的主函數 void *pth_main(void *arg);int main() {// 忽略全部的信號for (int ii=0;ii<50;ii++) signal(ii,SIG_IGN);// 設置SIGINT和SIGTERM的處理函數signal(SIGINT,EXIT); signal(SIGTERM,EXIT);if (TcpServer.InitServer(5051)==false){ printf("服務端初始化失敗,程序退出。\n"); return -1; }while (1){if (TcpServer.Accept() == false) continue;pthread_t pthid; // 創建一線程,與新連接上來的客戶端通信if (pthread_create(&pthid,NULL,pth_main,(void*)((long)TcpServer.m_clientfd))!=0){ printf("創建線程失敗,程序退出。n"); return -1; }printf("與客戶端通信的線程已創建。\n");} }CTcpServer::CTcpServer() {// 構造函數初始化socketm_listenfd=m_clientfd=0; }CTcpServer::~CTcpServer() {if (m_listenfd!=0) close(m_listenfd); // 析構函數關閉socketif (m_clientfd!=0) close(m_clientfd); // 析構函數關閉socket }// 初始化服務端的socket,port為通信端口 bool CTcpServer::InitServer(int port) {if (m_listenfd!=0) { close(m_listenfd); m_listenfd=0; }m_listenfd = socket(AF_INET,SOCK_STREAM,0); // 創建服務端的socket// 把服務端用于通信的地址和端口綁定到socket上struct sockaddr_in servaddr; // 服務端地址信息的數據結構memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family = AF_INET; // 協議族,在socket編程中只能是AF_INETservaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 本主機的任意ip地址servaddr.sin_port = htons(port); // 綁定通信端口if (bind(m_listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) != 0 ){ close(m_listenfd); m_listenfd=0; return false; }// 把socket設置為監聽模式if (listen(m_listenfd,5) != 0 ) { close(m_listenfd); m_listenfd=0; return false; }return true; }bool CTcpServer::Accept() {if ( (m_clientfd=accept(m_listenfd,0,0)) <= 0) return false;return true; }int CTcpServer::Send(const void *buf,const int buflen) {return send(m_clientfd,buf,buflen,0); }int CTcpServer::Recv(void *buf,const int buflen) {return recv(m_clientfd,buf,buflen,0); }// 與客戶端通信線程的主函數 void *pth_main(void *arg) {int clientfd=(long) arg; // arg參數為新客戶端的socket。// 與客戶端通信,接收客戶端發過來的報文后,回復ok。char strbuffer[1024];while (1){memset(strbuffer,0,sizeof(strbuffer));if (recv(clientfd,strbuffer,sizeof(strbuffer),0)<=0) break;printf("接收:%s\n",strbuffer);strcpy(strbuffer,"ok");if (send(clientfd,strbuffer,strlen(strbuffer),0)<=0) break;printf("發送:%s\n",strbuffer);}printf("客戶端已斷開連接。\n");close(clientfd); // 關閉客戶端的連接。pthread_exit(0); }需要注意幾個問題:
1)線程主函數的函數體中,不能使用return;語句,如果想退出線程,可以用pthread_exit(0);返回。
2)線程可以共享全局變量,當然也可以共享TcpServer的m_clientfd成員變量,但是,創建線程的時候,為什么要把客戶端的socket用參數傳給線程主函數,而不是直接獲取TcpServer.m_clientfd的值,因為主進程調用pthread_create創建線程后,立即返回循環重新Accept,創建線程需要時間,如果在這段時間內有新的客戶端連接上來,TcpServer.m_clientfd的值會發生改變。
3)TcpServer.m_clientfd的強制轉換,在創建線程的時候,代碼如下:
if (pthread_create(&pthid,NULL,pth_main,(void*)((long)TcpServer.m_clientfd))!=0)線程中的代碼如下:
int clientfd=(long) arg; // arg參數為新客戶端的socket。四、 線程同步
線程同步是協調協同的意思
1、按預定的先后次序運行
2、公共資源同一時刻只能被一個線程使用,共享數據同一時刻只能被一個線程修改,以保證數據的完整性。
信號:類似進程間的信號處理
鎖機制:互斥鎖、讀寫鎖、自旋鎖
條件變量:使用通知的方式解鎖,與互斥鎖配合使用
信號量:包括無名線程信號量和命名線程信號量
總結
以上是生活随笔為你收集整理的linux多线程简介的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 二叉搜索树的最小绝对差
- 下一篇: 互斥锁、条件变量、自旋锁、读写锁