ZDNet 軟件頻道 更新時(shí)間:2007-08-31作者:賽迪網(wǎng) 來(lái)源:賽迪網(wǎng)
本文關(guān)鍵詞:
客戶(hù)/服務(wù)器 多線(xiàn)程 Linux在傳統的Unix模型中,當一個(gè)進(jìn)程需要由另一個(gè)實(shí)體執行某件事時(shí),該進(jìn)程派生(fork)一個(gè)子進(jìn)程,讓子進(jìn)程去進(jìn)行處理。
Unix下的大多數網(wǎng)絡(luò )服務(wù)器程序都是這么編寫(xiě)的,即父進(jìn)程接受連接,派生子進(jìn)程,子進(jìn)程處理與客戶(hù)的交互。
雖然這種模型很多年來(lái)使用得很好,但是fork時(shí)有一些問(wèn)題:
1. fork是昂貴的。內存映像要從父進(jìn)程拷貝到子進(jìn)程,所有描述字要在子進(jìn)程中復制等等。目前有的Unix實(shí)現使用一種叫做寫(xiě)時(shí)拷貝(copy-on-write)的技術(shù),可避免父進(jìn)程數據空間向子進(jìn)程的拷貝。盡管有這種優(yōu)化技術(shù),fork仍然是昂貴的。
2. fork子進(jìn)程后,需要用進(jìn)程間通信(IPC)在父子進(jìn)程之間傳遞信息。Fork之前的信息容易傳遞,因為子進(jìn)程從一開(kāi)始就有父進(jìn)程數據空間及所有描述字的拷貝。但是從子進(jìn)程返回信息給父進(jìn)程需要做更多的工作。
線(xiàn)程有助于解決這兩個(gè)問(wèn)題。線(xiàn)程有時(shí)被稱(chēng)為輕權進(jìn)程(lightweight process),因為線(xiàn)程比進(jìn)程“輕權”,一般來(lái)說(shuō),創(chuàng )建一個(gè)線(xiàn)程要比創(chuàng )建一個(gè)進(jìn)程快10~100倍。
一個(gè)進(jìn)程中的所有線(xiàn)程共享相同的全局內存,這使得線(xiàn)程很容易共享信息,但是這種簡(jiǎn)易性也帶來(lái)了同步問(wèn)題。
一個(gè)進(jìn)程中的所有線(xiàn)程不僅共享全局變量,而且共享:進(jìn)程指令、大多數數據、打開(kāi)的文件(如描述字)、信號處理程序和信號處置、當前工作目錄、用戶(hù)ID和組ID。
但是每個(gè)線(xiàn)程有自己的線(xiàn)程ID、寄存器集合(包括程序計數器和棧指針)、棧(用于存放局部變量和返回地址)、error、信號掩碼、優(yōu)先級。
在Linux中線(xiàn)程編程符合Posix.1標準,稱(chēng)為Pthreads。所有的pthread函數都以pthread_開(kāi)頭。
以下先講述5個(gè)基本線(xiàn)程函數,在調用它們前均要包括pthread.h頭文件。然后再給出用它們編寫(xiě)的一個(gè)TCP客戶(hù)/服務(wù)器程序例子。
第一個(gè)函數:
int pthread_create (pthread_t *tid,const pthread_attr_t *attr,void *(*func)(void *),void *arg);
一個(gè)進(jìn)程中的每個(gè)線(xiàn)程都由一個(gè)線(xiàn)程ID(thread ID)標識,其數據類(lèi)型是pthread_t(常常是unsigned int)。如果新的線(xiàn)程
創(chuàng )建成功,其ID將通過(guò)tid指針?lè )祷亍?div style="height:15px;">
每個(gè)線(xiàn)程都有很多屬性:優(yōu)先級、起始棧大小、是否應該是一個(gè)守護線(xiàn)程等等,當創(chuàng )建線(xiàn)程時(shí),我們可通過(guò)初始化一個(gè)pthread_attr_t
變量說(shuō)明這些屬性以覆蓋缺省值。我們通常使用缺省值,在這種情況下,我們將attr參數說(shuō)明為空指針。
最后,當創(chuàng )建一個(gè)線(xiàn)程時(shí),我們要說(shuō)明一個(gè)它將執行的函數。線(xiàn)程以調用該函數開(kāi)始,然后或者顯式地終止(調用pthread_exit)或者隱式地終止(讓該函數返回)。函數的地址由func參數指定,該函數的調用參數是一個(gè)指針arg,如果我們需要多個(gè)調用參數,我們必須將它們打包成一個(gè)結構,然后將其地址當作唯一的參數傳遞給起始函數。
在func和arg的聲明中,func函數取一個(gè)通用指針(void *)參數,并返回一個(gè)通用指針(void *),這就使得我們可以傳遞一個(gè)
指針(指向任何我們想要指向的東西)給線(xiàn)程,由線(xiàn)程返回一個(gè)指針(同樣指向任何我們想要指向的東西)。
調用成功,返回0,出錯時(shí)返回正Exxx值。Pthread函數不設置errno。
第二個(gè)函數:
int pthread_join(pthread_t tid,void **status);
該函數等待一個(gè)線(xiàn)程終止。把線(xiàn)程和進(jìn)程相比,pthread_creat類(lèi)似于fork,而pthread_join類(lèi)似于waitpid。
我們必須要等待線(xiàn)程的tid,很可惜,我們沒(méi)有辦法等待任意一個(gè)線(xiàn)程結束。
如果status指針?lè )强?,線(xiàn)程的返回值(一個(gè)指向某個(gè)對象的指針)將存放在status指向的位置。
第三個(gè)函數;
pthread_t pthread_self(void);
線(xiàn)程都有一個(gè)ID以在給定的進(jìn)程內標識自己。線(xiàn)程ID由pthread_creat返回,我們可以pthread_self取得自己的線(xiàn)程ID。
第四個(gè)函數:
int pthread_detach(pthread_t tid);
線(xiàn)程或者是可匯合的(joinable)或者是脫離的(detached)。當可匯合的線(xiàn)程終止時(shí),其線(xiàn)程ID和退出狀態(tài)將保留,直到另外一個(gè)線(xiàn)程調用pthread_join。脫離的線(xiàn)程則像守護進(jìn)程:當它終止時(shí),所有的資源都釋放,我們不能等待它終止。如果一個(gè)線(xiàn)程需要知道另一個(gè)線(xiàn)程什么時(shí)候終止,最好保留第二個(gè)線(xiàn)程的可匯合性。
Pthread_detach函數將指定的線(xiàn)程變?yōu)槊撾x的。
該函數通常被想脫離自己的線(xiàn)程調用,如:pthread_detach (pthread_self ( ));
第五個(gè)函數:
void pthread_exit(void *status);
該函數終止線(xiàn)程。如果線(xiàn)程未脫離,其線(xiàn)程ID和退出狀態(tài)將一直保留到調用進(jìn)程中的某個(gè)其他線(xiàn)程調用pthread_join函數。
指針status不能指向局部于調用線(xiàn)程的對象,因為線(xiàn)程終止時(shí)這些對象也消失。
有兩種其他方法可使線(xiàn)程終止:
1. 啟動(dòng)線(xiàn)程的函數(pthread_creat的第3個(gè)參數)返回。既然該函數必須說(shuō)明為返回一個(gè)void指針,該返回值便是線(xiàn)程的終止狀態(tài)。
2. 如果進(jìn)程的main函數返回或者任何線(xiàn)程調用了exit,進(jìn)程將終止,線(xiàn)程將隨之終止。
以下給出一個(gè)使用線(xiàn)程的TCP回射客戶(hù)/服務(wù)器的例子,完成的功能是客戶(hù)端使用線(xiàn)程給服務(wù)器發(fā)從標準輸入得到的字符,并在主線(xiàn)程中將從服務(wù)器端返回的字符顯示到標準輸出,服務(wù)器端將客戶(hù)端發(fā)來(lái)的數據原樣返回給客戶(hù)端,每一個(gè)客戶(hù)在服務(wù)器上對應一個(gè)線(xiàn)程。利用該程序框架,通過(guò)擴展客戶(hù)端和服務(wù)器端的處理功能,可以完成多種基于多線(xiàn)程的客戶(hù)機/服務(wù)器程序。該程序在RedHat 6.0和TurboLinux4.02下調試通過(guò)。
共用頭文件如下:
(head.h)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXLINE 1024
#define SERV_PORT 8000
#define LISTENQ 1024
static int sockfd;
static FILE *fp;
公用函數如下(common.c):
/* 從一個(gè)描述字讀文本行 */
ssize_t readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr;
for (n=1; n0)
{
if ( (nwritten=write (fd, ptr, nleft ) )<=0 )
{
if (errno==EINTR )
nwritten=0;
else
return (-1);
}
nleft-=nwritten;
ptr++=nwritten;
}
客戶(hù)端主程序如下:
(client.c)
#include “head.h";
#include “common.c";
/* 在str_cli中定義的要被線(xiàn)程執行的函數 */
void *copyto (void *arg)
{
char sendline[MAXLINE];
while (fgets (sendline,MAXLINE,fp) !=NULL )
writen(sockfd,sendline,strlen(sendline));
shutdown(sockfd,SHUT_WR);
return(NULL);
}
void str_cli(FILE *fp_arg, int sockfd_arg)
{
char recvline[MAXLINE];
pthread_t tid;
sockfd=sockfd_arg;
fp=fp_arg;
pthread_creat(&tid, NULL, copyto, NULL);
while (readline (sockfd,recvline,MAXLINE) >0)
---- fputs(recvline,stdout);
}
int main ( int argc, char **argv )
{
int sockfd;
struct sockaddr_in servaddr;
if (argc!=2 )
printf ( “ usage: tcpcli " );
exit(0);
bzero(&servaddr, sizeof (servaddr)) ;
servaddr.sin_family=AF_INET;
servaddr.sinport=htons(SERV_PORT);
inet_pton (AF_INET, argv[1], &servaddr.sin_addr );
connect (sockfd, (struct sockaddr *)&servaddr,
siziof (servaddr ) );
str_cli (stdin, sockfd );
exit (0 );
}
服務(wù)器端主程序如下:
(server.c)
#include “head.h";
#include “common.c";
void str_echo (int sockfd )
{
ssize_t n;
char line[MAXLINE];
for (; ; )
{
if ( (n=readline (sockfd, line, MAXLINE) )==0)
return;
writen (sockfd, line, n);
}
}
static void *doit ( void *arg)
{
pthread_detach(pthread_self ( ) );
str_echo ( (int ) arg );
close ( (int ) arg );
return ( NULL ) ;
}
int main ( int argc, char **argv )
{
int listenfd, connfd;
socklen_t addrlen,len;
struct sockaddr_in cliaddr, servaddr;
pthread_t tid;
listenfd=socket (AF_INET, SOCK_STREAM, 0 );
bzero (&servaddr, sizeof (servaddr ) );
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=htonl (INADDR_ANY );
servaddr.sin_port=SERV_PORT;
bind (listenfd, ( struct sockaddr * )&servaddr, sizeof
(servaddr ) );
listen (listenfd, LISTENQ );
addrlen=sizeof ( cliaddr );
cliaddr=malloc(addrlen );
for ( ; ; )
{
len=addrlen;
connfd=accept(listenfd, (struct sockaddr * )&cliaddr, &len );
pthread_creat ( &tid, NULL, &doit, ( void * )connfd );
}
}