@lishuhuakai
2016-11-03T16:02:03.000000Z
字数 2602
阅读 1566
错误的代码和正确的代码总是非常相似的!
好吧,我们继续开干,这一次,我们来写一个多线程版本的web服务器.
这次代码的思想十分简单,那就是一旦从客户端来了一个连接,就生成一个线程来处理这个连接.这种想法和之前的多进程版本非常类似.但是请注意进程和线程之间的差异.
进程这种东西,一旦父进程调用fork函数,生成了一个子进程,那么子进程基本上不和父进程共享任何东西了,当然,子进程保留了父进程打开文件的指针,复制了父进程的代码区,数据区,寄存器的值,几乎所有的东西,但是一旦fork,两者之间除了父子关系,它们两者的关系就和普通的两个进程一样了.
线程不一样,父线程创建了子线程之后,父子线程之间共享很多东西,当然,子线程有自己的堆栈(堆栈这个玩意自然不能够共享,如果共享,会造成混乱.).寄存器的值也不会共享,应该还有一些我没有提到的东西不能共享吧,不过这些足够了.除此之外,全部共享,也就是说,如果在子线程里关闭了某个文件描述符,那么这个文件描述符在父线程里面一样被关闭了,事实上:
如果你在父(子)进程中能够得到子(父)线程的堆栈的指针的话,父(子)进程也能够访问子(父)进程的堆栈空间了.
在推进代码的时候,我曾经写过这样一个主函数:
int main(int argc, char *argv[]){int listenfd = Open_listenfd(8080); /* 8080号端口监听 */signal(SIGPIPE, SIG_IGN); /* 忽略SIGPIPE消息 */while (true) /* 无限循环 */{struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);int *fdp = (int *)Malloc(sizeof(int));int connfd = Accept(listenfd, (SA*)&clientaddr, &len);pthread_t tid;Pthread_create(&tid, NULL, handle, (void *)connfd);//close(connfd);}return 0;}
你能发现哪里出错了吗?对了这是处理函数:
void* handle(void* arg){Pthread_detach(pthread_self()); // 脱离父线程int fd = (*(int *)arg);printf("%d: fd = %d\n", pthread_self(), fd);doit(fd);printf("%d: close fd = %d\n", pthread_self(), fd);close(fd);}
这个错误非常地隐蔽,下面是一次打印的结果:
-142670080: fd = 4-142670080:GET /cpp/concept.html HTTP/1.1-142670080: close fd = 4-151062784: fd = 5-151062784:GET /common/ext.css HTTP/1.1-151062784: close fd = 5-142670080: fd = 4-142670080:GET /common/site_modules.css HTTP/1.1-142670080: close fd = 4-142670080: fd = 4-142670080:GET /common/skin_scripts.js HTTP/1.1-151062784: fd = 6-142670080: close fd = 4-151062784:GET /common/site_scripts.js HTTP/1.1-151062784: close fd = 6-159455488: fd = 6Rio_readlineb error: Bad file descriptor
好了,我这里就不卖关子了,代码错在了这里:
Pthread_create(&tid, NULL, handle, (void *)connfd);
我们可以发现,这里的connfd是栈上的一个变量,当while循环了一遍之后,这个connfd的值我们就不再确定了,他有可能是原来的值,这样的话,线程运行不会出错,有可能是任何值,这个时候就会出现打印的bad file diesriptor啦.
有一点我们是要注意的,共享的资源会导致竞争状态的产生,考虑这样一种极端情况,Pthread_create函数之后,子进程并没有得到cpu的运行时间,而主线程一直在运行,很快,主线程获得了一个新的连接,connfd的值被新的文件描述符的值填充了,这个时候子线程才开始运行,子线程获得的值是新的connfd的值吗?
所以,正确的代码示例是这样的:
/*-* 多线程版本的web server.*/void* handle(void* arg){Pthread_detach(pthread_self());int fd = (*(int *)arg);Free(arg); /* 防止内存泄露,释放 */printf("%d: fd = %d\n", pthread_self(), fd);doit(fd);printf("%d: close fd = %d\n", pthread_self(), fd);close(fd);}int main(int argc, char *argv[]){int listenfd = Open_listenfd(8080); /* 8080号端口监听 */signal(SIGPIPE, SIG_IGN);int connfd;while (true) /* 无限循环 */{struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);int *fdp = (int *)Malloc(sizeof(int)); /* 重新分配一块地址 */*fdp = Accept(listenfd, (SA*)&clientaddr, &len);pthread_t tid;Pthread_create(&tid, NULL, handle, (void *)fdp);//close(connfd);}return 0;}
这里的signal(SIGPIPE, SIG_IGN);其实对于线程来说并没有起作用,以后的版本会改进.
当然,这种模式效率依旧不是很高.