[关闭]
@lishuhuakai 2016-11-03T16:02:03.000000Z 字数 2602 阅读 1230

一起来写web server 03 -- 多线程版本


错误的代码和正确的代码总是非常相似的!

好吧,我们继续开干,这一次,我们来写一个多线程版本的web服务器.

这次代码的思想十分简单,那就是一旦从客户端来了一个连接,就生成一个线程来处理这个连接.这种想法和之前的多进程版本非常类似.但是请注意进程和线程之间的差异.

进程和线程的区别

进程这种东西,一旦父进程调用fork函数,生成了一个子进程,那么子进程基本上不和父进程共享任何东西了,当然,子进程保留了父进程打开文件的指针,复制了父进程的代码区,数据区,寄存器的值,几乎所有的东西,但是一旦fork,两者之间除了父子关系,它们两者的关系就和普通的两个进程一样了.

线程不一样,父线程创建了子线程之后,父子线程之间共享很多东西,当然,子线程有自己的堆栈(堆栈这个玩意自然不能够共享,如果共享,会造成混乱.).寄存器的值也不会共享,应该还有一些我没有提到的东西不能共享吧,不过这些足够了.除此之外,全部共享,也就是说,如果在子线程里关闭了某个文件描述符,那么这个文件描述符在父线程里面一样被关闭了,事实上:

如果你在父(子)进程中能够得到子(父)线程的堆栈的指针的话,父(子)进程也能够访问子(父)进程的堆栈空间了.

这里有一个微小的错误,你能发现吗?

在推进代码的时候,我曾经写过这样一个主函数:

  1. int main(int argc, char *argv[])
  2. {
  3. int listenfd = Open_listenfd(8080); /* 8080号端口监听 */
  4. signal(SIGPIPE, SIG_IGN); /* 忽略SIGPIPE消息 */
  5. while (true) /* 无限循环 */
  6. {
  7. struct sockaddr_in clientaddr;
  8. socklen_t len = sizeof(clientaddr);
  9. int *fdp = (int *)Malloc(sizeof(int));
  10. int connfd = Accept(listenfd, (SA*)&clientaddr, &len);
  11. pthread_t tid;
  12. Pthread_create(&tid, NULL, handle, (void *)connfd);
  13. //close(connfd);
  14. }
  15. return 0;
  16. }

你能发现哪里出错了吗?对了这是处理函数:

  1. void* handle(void* arg)
  2. {
  3. Pthread_detach(pthread_self()); // 脱离父线程
  4. int fd = (*(int *)arg);
  5. printf("%d: fd = %d\n", pthread_self(), fd);
  6. doit(fd);
  7. printf("%d: close fd = %d\n", pthread_self(), fd);
  8. close(fd);
  9. }

这个错误非常地隐蔽,下面是一次打印的结果:

  1. -142670080: fd = 4
  2. -142670080:GET /cpp/concep
  3. t.html HTTP/1.1
  4. -142670080: close fd = 4
  5. -151062784: fd = 5
  6. -151062784:GET /common/ext
  7. .css HTTP/1.1
  8. -151062784: close fd = 5
  9. -142670080: fd = 4
  10. -142670080:GET /common/sit
  11. e_modules.css HTTP/1.1
  12. -142670080: close fd = 4
  13. -142670080: fd = 4
  14. -142670080:GET /common/ski
  15. n_scripts.js HTTP/1.1
  16. -151062784: fd = 6
  17. -142670080: close fd = 4
  18. -151062784:GET /common/sit
  19. e_scripts.js HTTP/1.1
  20. -151062784: close fd = 6
  21. -159455488: fd = 6
  22. Rio_readlineb error: Bad f
  23. ile descriptor

错误的原因

好了,我这里就不卖关子了,代码错在了这里:

  1. Pthread_create(&tid, NULL, handle, (void *)connfd);

我们可以发现,这里的connfd是栈上的一个变量,当while循环了一遍之后,这个connfd的值我们就不再确定了,他有可能是原来的值,这样的话,线程运行不会出错,有可能是任何值,这个时候就会出现打印的bad file diesriptor啦.

有一点我们是要注意的,共享的资源会导致竞争状态的产生,考虑这样一种极端情况,Pthread_create函数之后,子进程并没有得到cpu的运行时间,而主线程一直在运行,很快,主线程获得了一个新的连接,connfd的值被新的文件描述符的值填充了,这个时候子线程才开始运行,子线程获得的值是新的connfd的值吗?

所以,正确的代码示例是这样的:

  1. /*-
  2. * 多线程版本的web server.
  3. */
  4. void* handle(void* arg)
  5. {
  6. Pthread_detach(pthread_self());
  7. int fd = (*(int *)arg);
  8. Free(arg); /* 防止内存泄露,释放 */
  9. printf("%d: fd = %d\n", pthread_self(), fd);
  10. doit(fd);
  11. printf("%d: close fd = %d\n", pthread_self(), fd);
  12. close(fd);
  13. }
  14. int main(int argc, char *argv[])
  15. {
  16. int listenfd = Open_listenfd(8080); /* 8080号端口监听 */
  17. signal(SIGPIPE, SIG_IGN);
  18. int connfd;
  19. while (true) /* 无限循环 */
  20. {
  21. struct sockaddr_in clientaddr;
  22. socklen_t len = sizeof(clientaddr);
  23. int *fdp = (int *)Malloc(sizeof(int)); /* 重新分配一块地址 */
  24. *fdp = Accept(listenfd, (SA*)&clientaddr, &len);
  25. pthread_t tid;
  26. Pthread_create(&tid, NULL, handle, (void *)fdp);
  27. //close(connfd);
  28. }
  29. return 0;
  30. }

存在的小bug

这里的signal(SIGPIPE, SIG_IGN);其实对于线程来说并没有起作用,以后的版本会改进.

当然,这种模式效率依旧不是很高.

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注