我們知道,進程在各自獨立的地址空間中運行,進程之間共享數據需要用進程間通信機制,有些情況需要在一個進程中同時執行多個控制流程,這時候線程就派上了用場,比如實現一個圖形界面的下載軟件,一方面需要和用戶交互,等待和處理用戶的鼠標鍵盤事件,另一方面又需要同時下載多個文件,等待和處理從多個網絡主機發來的數據,這些任務都需要一個“等待-處理”的循環,可以用多線程實現,一個線程專門負責與用戶交互,另外幾個線程每個線程負責和一個網絡主機通信。
注:linux 2.6 以後的線程就是由用戶態的pthread庫實現的.使用pthread以後, 在用戶看來, 每一個task_struct就對應一個線程, 而一組線程以及它們所共同引用的一組資源就是一個進程.在linux 2.6中, 內核有了線程組的概念, task_struct結構中增加了一個tgid(thread group id)字段. getpid(獲取進程ID)系統調用返回的也是tast_struct中的tgid, 而tast_struct中的pid則由gettid系統調用來返回。 當線程停止/繼續, 或者是收到一個致命信號時, 內核會將處理動作施加到整個線程組中。int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void*), void *restrict arg);
創建一個新的線程
參數
thread:線程ID
attr:設置線程的屬性,一般設置為NULL表示使用默認屬性
start_routine:是個函數地址,線程啟動後要執行的函數
arg:傳給線程啟動函數的參數
返回值:成功返回0;失敗返回錯誤碼;
以前學過的系統函數都是成功返回0,失敗返回-1,而錯誤號保存在全局變量errno中,而pthread庫的函數都是通過返回值返回錯誤號,雖然每個線程也都有一個errno,但這是為了兼容其它函數接口而提供的,pthread庫本身並不使用它,通過返回值返回錯誤碼更加清晰。由於pthread_create的錯誤碼不保存在errno中,因此不能直接用perror(3)打印錯誤信息,可以先用strerror(3)把錯誤號轉換成錯誤信息再打印。讀取返回值要比讀取線程內的errno變量的開銷更小!/** 實踐: 新的錯誤檢查與錯誤退出函數 **/ inline void err_check(const std::string &msg, int retno) { if (retno != 0) err_exit(msg, retno); } inline void err_exit(const std::string &msg, int retno) { std::cerr << msg << ": " << strerror(retno) << endl; exit(EXIT_FAILURE); }pthread_exit
void pthread_exit(void *value_ptr);
value_ptr:value_ptr不要指向一個局部變量,因為當其它線程得到這個返回指針時線程函數已經退出了。
返回值:無返回值,跟進程一樣,線程結束的時候無法返回到它的調用者(自身)
如果需要只終止某個線程而不終止整個進程,可以有三種方法:
1、從線程函數return。這種方法對主線程不適用,從main函數return相當於調用exit,而如果任意一個線程調用了exit或_exit,則整個進程的所有線程都終止。
2、一個線程可以調用pthread_cancel 終止同一進程中的另一個線程。
3、線程可以調用pthread_exit終止自己。
pthread_joinint pthread_join(pthread_t thread, void **value_ptr);
當pthread_create 中的 start_routine返回時,這個線程就退出了,其它線程可以調用pthread_join得到start_routine的返回值,類似於父進程調用wait(2)得到子進程的退出狀態。
調用該函數的線程將掛起等待,直到id為thread的線程終止。thread線程以不同的方法終止,通過pthread_join得到的終止狀態是不同的,總結如下:
1、如果thread線程通過return返回,value_ptr所指向的單元裡存放的是thread線程函數的返回值。
2、如果thread線程被別的線程調用pthread_cancel異常終止掉,value_ptr所指向的單元裡存放的是常數PTHREAD_CANCELED。
3、如果thread線程是自己調用pthread_exit終止的,value_ptr所指向的單元存放的是傳給pthread_exit的參數。
如果對thread線程的終止狀態不感興趣,可以傳NULL給value_ptr參數。
/** 示例: 等待線程退出 **/ void *thread_rotine(void *args) { for (int i = 0; i < 10; ++i) { printf("B"); fflush(stdout); usleep(20); } pthread_exit(NULL); } int main() { pthread_t thread; int ret = pthread_create(&thread, NULL, thread_rotine, NULL); err_check("pthread_create", ret); for (int i = 0; i < 10; ++i) { printf("A"); fflush(stdout); usleep(20); } ret = pthread_join(thread, NULL); err_check("pthread_join", ret); putchar('\n'); return 0; }pthread_self
pthread_t pthread_self(void);返回線程ID
在Linux上,pthread_t類型是一個地址值,屬於同一進程的多個線程調用getpid(2)可以得到相同的進程號,而調用pthread_self(3)得到的線程號各不相同。線程id只在當前進程中保證是唯一的,在不同的系統中pthread_t這個類型有不同的實現,它可能是一個整數值,也可能是一個結構體,也可能是一個地址,所以不能簡單地當成整數用printf打印。
/** 示例:主控線程與子線程傳遞數據 **/ typedef struct _Student { char name[20]; unsigned int age; } Student; void *threadFunction(void *args) { cout << "In Thread: " << pthread_self() << endl; Student tmp = *(Student *)(args); cout << "Name: " << tmp.name << endl; cout << "Age: " << tmp.age << endl; pthread_exit(NULL); } int main() { Student student = {"tach",22}; pthread_t thread; //啟動創建並啟動線程 pthread_create(&thread,NULL,threadFunction,&student); //等待線程結束 pthread_join(thread,NULL); return 0; }pthread_cancel
int pthread_cancel(pthread_t thread);線程取消的方法是向目標線程發Cancel信號,但如何處理Cancel信號則由目標線程自己決定,或者忽略、或者立即終止、或者繼續運行至Cancelation-point(取消點),由不同的Cancelation狀態決定。
pthread_detach
int pthread_detach(pthread_t thread);
一般情況下,線程終止後,其終止狀態一直保留到其它線程調用pthread_join獲取它的狀態為止(僵線程)。但是線程也可以被置為detach狀態,這樣的線程一旦終止就立刻回收它占用的所有資源,而不保留終止狀態。不能對一個已經處於detach狀態的線程調用pthread_join,這樣的調用將返回EINVAL。對一個尚未detach的線程調用pthread_join或pthread_detach都可以把該線程置為detach狀態,也就是說,不能對同一線程調用兩次pthread_join,或者如果已經對一個線程調用了pthread_detach就不能再調用pthread_join了。
這個函數既可以在主線程中調用,也可以在thread_function裡面調用。
總結:進程 VS. 線程
進程(pid_t)
線程(pthread_t)
Fork
Pthread_create
Waitpit
Pthread_join/Pthread_detach
Kill
Pthread_cancel
Pid
Pthead_self
Exit/return
Pthread_exit/return
僵屍進程(沒有調用wait/waitpid等函數)
僵屍線程(沒有調用pthread_join/pthread_detach)
/** 將並發echo server改造成多線程形式 **/ void echo_server(int clientSocket); void *thread_routine(void *arg); int main() { int sockfd = socket(AF_INET,SOCK_STREAM,0); if (sockfd == -1) err_exit("socket error"); int optval = 1; if (setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval)) == -1) err_exit("setsockopt error"); struct sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(8002); serverAddr.sin_addr.s_addr = INADDR_ANY; //綁定本機的任意一個IP地址 if (bind(sockfd,(struct sockaddr *)&serverAddr,sizeof(serverAddr)) == -1) err_exit("bind error"); if (listen(sockfd,SOMAXCONN) == -1) err_exit("listen error"); while (true) { int peerSockfd = accept(sockfd, NULL, NULL); if (peerSockfd == -1) err_exit("accept error"); pthread_t tid; /**注意: 下面這種用法可能會產生問題 當另一個連接快讀快速到達, peerSockfd的內容更改, 新創建的線程尚未將該值取走時,線程讀取的就不是 我們原來想讓線程讀取的值了 int ret = pthread_create(&tid, NULL, thread_routine, (void *)&peerSockfd); **/ //解決方案: 為每一個鏈接創建一塊內存 ,注意之後要釋放 int *p = new int(peerSockfd); int ret = pthread_create(&tid, NULL, thread_routine, p); if (ret != 0) err_thread("pthread_create error", ret); } close(sockfd); }
void *thread_routine(void *args) { //將線程設置分離狀態, 避免出現僵屍線程 pthread_detach(pthread_self()); int peerSockfd = *(int *)args; //注意函數中指針取出之後記得將內存釋放掉 delete (int *)args; echo_server(peerSockfd); cout << "thread " << pthread_self() << " exiting ..." << endl; pthread_exit(NULL); } void echo_server(int clientSocket) { char buf[BUFSIZ] = {0}; int readBytes; while ((readBytes = read(clientSocket, buf, sizeof(buf))) >= 0) { if (readBytes == 0) { cerr << "client connect closed" << endl; break; } if (write(clientSocket, buf, readBytes) == -1) { cerr << "server thread write error" << endl; break; } cout << buf; bzero(buf, sizeof(buf)); } }