我們知道,進程在各自獨立的地址空間中運行,進程之間共享數據需要用進程間通信機制,有些情況需要在一個進程中同時執行多個控制流程,這時候線程就派上了用場,比如實現一個圖形界面的下載軟件,一方面需要和用戶交互,等待和處理用戶的鼠標鍵盤事件,另一方面又需要同時下載多個文件,等待和處理從多個網絡主機發來的數據,這些任務都需要一個“等待-處理”的循環,可以用多線程實現,一個線程專門負責與用戶交互,另外幾個線程每個線程負責和一個網絡主機通信。
注: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_exitvoid 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_selfpthread_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_cancelint 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));
}
}