+ -
当前位置:首页 → 问答吧 → 多线程服务器,发现个问题,请高人解惑

多线程服务器,发现个问题,请高人解惑

时间:2010-07-05

来源:互联网

本帖最后由 yulihua49 于 2010-07-05 12:09 编辑

怪现象:当客户端密集到达连接时,accept竟然会返回重复的s(socket),导致后边若干线程使用相同的socket,通信乱套了,内存也乱套了。
为此在成功create_thread后,特意usleep(10000);有改善,没根本解决。
  1.        。。。。前边正常,略
  2. listen(sock,1000);
  3.         while(1) {
  4.                 do {
  5.                         FD_ZERO(&efds);
  6.                         FD_SET(sock, &efds);
  7. //健康检查周期5分钟
  8.                         tm.tv_sec=300;
  9.                         tm.tv_usec=0;
  10.                         ret=select(sock+1,&efds,NULL,&efds,&tm);
  11.                         if(ret==-1) {
  12.                                 ShowLog(1,"select error %s",strerror(errno));
  13.                                 close(sock);
  14.                                 quit(3);
  15.                         }
  16.                         if(ret==0 && poolchk) poolchk();
  17.                 } while(ret<=0);
  18.                 s=accept(sock,(struct sockaddr *)&cin,&leng);//就是返回的s有问题了。
  19.                 if(s<0) {
  20.                         ShowLog(1,"%s:accept err=%d,%s",__FUNCTION__,errno,strerror(errno));
  21.                         switch(errno) {
  22.                         case EMFILE:    //fd用完了,其他线程还要继续工作,主线程休息一下。
  23.                         case ENFILE:
  24.                                 sleep(60);
  25.                                 continue;
  26.                         default:break;
  27.                         }
  28.                         sleep(3);
  29.                         if(++repeat < 20) continue;
  30.                         ShowLog(1,"%s:network fail! err=%s",__FUNCTION__,strerror(errno));
  31.                         close(sock);
  32.                         quit(5);
  33.                 }
  34.                 Conn.Socket=s;
  35.                 Conn.only_do=conn_init; //借用一下
  36.                 Conn.SendLen=sizeof_gda;
  37.                 ret=pthread_create(&pthread_id,&attr,thread_work,&Conn);
  38.                 if(ret) {
  39.                         ShowLog(1,"%s:pthread_create:%s",__FUNCTION__,strerror(ret));
  40.                         close(s);
  41.                         if(ret==EAGAIN||ret==ENOMEM) {  //线程数用完了,休息一会,等一些线程退出
  42.                                 sleep(30);
  43.                         }
  44.                         continue;
  45.                 }
  46.                 usleep(10000);//歇会
  47.         }

  48.         close(sock);
  49.         quit(0);
  50. }
复制代码

作者: yulihua49   发布时间: 2010-07-05

本帖最后由 lenky0401 于 2010-07-05 12:38 编辑

select尽量不要和多并发thread用吧,nginx代码里就禁止这么使用。至于原因嘛,我也不知道,只是前些天看到nginx代码是遇到这种情况就直接返回ERR了,当时就有疑问为什么会禁止这种组合使用,但没查到什么原因,但猜想可能与线程安全有关?刚才又通过关键字“select thread-safe”google了一把,网上的讨论也有,比如:
http://bugs.python.org/issue8865
http://stackoverflow.com/questio ... -how-to-work-around
http://www.qnx.com/developers/do ... b_ref/s/select.html

Caveats:
The select() function only works with raw file descriptors; it doesn't work with file descriptors in edited mode. See the ICANON flag in the description of the tcgetattr() function.

The select() function is thread safe as long as the fd sets used by each thread point to memory that is specific to that thread.

In Neutrino, if multiple threads block in select() on the same fd for the same condition, all threads may unblock when the condition is satisfied. This may differ from other implementations where only one thread may unblock.


如上,为了避免出现乱七八糟的问题,尽量不要采用这种模型吧。

作者: lenky0401   发布时间: 2010-07-05

连接数到达MAX_FD了吧。还有用多线程时关闭socket用shutdown()不要直接close(),close()会直接减少引用计数。

作者: 没本   发布时间: 2010-07-05

好吧,期待大牛。

作者: lenky0401   发布时间: 2010-07-05

本帖最后由 yulihua49 于 2010-07-05 13:10 编辑


QUOTE:
连接数到达MAX_FD了吧。还有用多线程时关闭socket用shutdown()不要直接close(),close()会直接减少引用计数 ...
没本 发表于 2010-07-05 12:34




    MAX FD=1024,256个并发连接。
日志:
  1195 5 thrsrv:19010 07/05 11:36'35 thread_work:tid=1291950400,sock=37
   1219 5 thrsrv:19010 07/05 11:36'35 thread_work:tid=1283557696,sock=37
这里57696线程乱了:
  1226 1 thrsrv:19010 07/05 11:36'35 aft NetHeadDispack len=24,PKGERR 127.0.0.1:02u0000000000000002oMG00| !9穨U皛@~`撮y]C!^C        c~N.6膈~B~P
   1227 1 thrsrv:19010 07/05 11:36'35 30 32 75 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 32 6F 4D 47 30 30 A0 21 39 B7 95         B0 80 7E 60 B4 E9 79 5D 43 21 03 63 8E 2E 36 EB F5 82 90   1228 1 thrsrv:19010 07/05 11:36'35 thread_work:tid=1283557696,接收结束,status=84,Invalid or incomplete multibyte or wide c        haracter
50400线程退出:
328551 1  07/05 12:06'35 RecvPack:head TIMEOUT 1800 second's
328552 1  07/05 12:06'35 thread_work:tid=1291950400,接收结束,status=84,Invalid or incomplete multibyte or wide character

以上时间内50400和57696共用37socket,互相扰乱了。

作者: yulihua49   发布时间: 2010-07-05

ShowLog(1,"%s:accept err=%d,%s",__FUNCTION__,errno,strerror(errno));

                        switch(errno) {

后面switch里面的errno很可能已经因调用ShowLog而被改了

作者: hellioncu   发布时间: 2010-07-05

本帖最后由 yulihua49 于 2010-07-05 13:15 编辑


QUOTE:
ShowLog(1,"%s:accept err=%d,%s",__FUNCTION__,errno,strerror(errno));

                        swit ...
hellioncu 发表于 2010-07-05 13:09




   s=37, 不小于0, 没走到那里,ShowLog用的srderr ,fd=2;

之前有这么一句,有关系吗?
leng=1;
        setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&leng,sizeof(leng));

作者: yulihua49   发布时间: 2010-07-05



QUOTE:
select尽量不要和多并发thread用吧,nginx代码里就禁止这么使用。至于原因嘛,我也不知道,只是前些天看到n ...
lenky0401 发表于 2010-07-05 12:23




    1.select在主线程用,并未涉及子线程。
   2.光使用accept也出现过这问题,先是usleep(1000),后来改10000.
实际上,usleep至少休息4000微秒。

作者: yulihua49   发布时间: 2010-07-05

Conn里的内容在创建线程的时候会被共用.比如你要创建两个线程,线程1里的Conn内容会被主线程改变.简单点说主线程先创建线程1,当要创建线程2的时候,Conn的内容已经变了,这时如果线程1还没结束,那么引用的Conn的地方就会混乱.

作者: samlumengjun   发布时间: 2010-07-05

本帖最后由 yulihua49 于 2010-07-05 14:31 编辑


QUOTE:
Conn里的内容在创建线程的时候会被共用.比如你要创建两个线程,线程1里的Conn内容会被主线程改变.简单点说主 ...
samlumengjun 发表于 2010-07-05 13:45




    conn的内容在子线程被拷贝出来。
但有可能被共用,我看看。

static void * thread_work(void *param)
{
T_Connect Conn=*(T_Connect *)param;
。。。。。。。
是否拷贝时就冲突了???

作者: yulihua49   发布时间: 2010-07-05

回复 yulihua49


    虽然可能性很小,但是是存在的.这也解释了为什么你usleep以后情况会有所好转.最好的办法是在主线程里malloc一个Conn结构传给线程函数,然后在线程函数结束时free掉.另外,像这样的原形如果事先没有设置线程detach的话要在线程处理函数里加上pthread_detach(pthread_self());这么一句,要不然随着连接数的上升.会资源耗尽.

作者: samlumengjun   发布时间: 2010-07-05

本帖最后由 yulihua49 于 2010-07-05 14:54 编辑


QUOTE:
Conn里的内容在创建线程的时候会被共用.比如你要创建两个线程,线程1里的Conn内容会被主线程改变.简单点说主 ...
samlumengjun 发表于 2010-07-05 13:45




   感谢哦,找到了。修改如下:
子线程:
  1. static void * thread_work(void *param)
  2. {
  3. T_Connect Conn=*(T_Connect *)param;
  4. T_NetHead Head;
  5. int ret,logined=0;
  6. T_SRV_Var ctx;
  7. srvfunc *fp;
  8. int svcnum=0;

  9. #ifdef __GNUC__
  10. char gda[Conn.SendLen+1];//本线程的全局数据区必须在此分配。
  11. #else
  12. char *gda=alloca(Conn.SendLen+1);
  13. #endif

  14.         if(Conn.SendLen>0) ctx.var=gda;
  15.         else ctx.var=0;
  16.         Conn.SendLen=0;
  17.         ctx.tid=pthread_self();//标志多线程服务
  18.         ctx.poolno=0;
  19.         ___SQL_Init_SQL_Connect(&ctx.SQL_Connect);
  20.         Conn.Var=&ctx;
  21.         Conn.only_do=0;
  22. //借用only_do存放函数地址 conn_init
  23.         if(((T_Connect *)param)->only_do) ((T_Connect *)param)->only_do(&Conn,&Head);
  24.         ((T_Connect *)param)->Socket=-1;//通知主线程,param可以重用了。
复制代码
主线程:
  1.   ret=pthread_create(&pthread_id,&attr,thread_work,&Conn);
  2.                 if(ret) {
  3.                         ShowLog(1,"%s:pthread_create:%s",__FUNCTION__,strerror(ret));
  4.                         close(s);
  5.                         if(ret==EAGAIN||ret==ENOMEM) {  //线程数用完了,休息一会,等一些线程退出
  6.                                 sleep(30);
  7.                         }
  8.                         continue;
  9.                 }
  10.                 while(Conn.Socket != -1) usleep(1000);//等子线程通知。
复制代码

作者: yulihua49   发布时间: 2010-07-05

当然乱了,

s=accept(sock,(struct sockaddr *)&cin,&leng);

这个s都是每个线程公用的

                Conn.Socket=s;

                Conn.only_do=conn_init; //借用一下

                Conn.SendLen=sizeof_gda;

                ret=pthread_create(&pthread_id,&attr,thread_work,&Conn);、

可以改为

              int fd=s;

                Conn.Socket=fd;

                Conn.only_do=conn_init; //借用一下

                Conn.SendLen=sizeof_gda;

                ret=pthread_create(&pthread_id,&attr,thread_work,&Conn);、

作者: chenzhanyiczy   发布时间: 2010-07-05

本帖最后由 yulihua49 于 2010-07-05 15:34 编辑


QUOTE:
当然乱了,

s=accept(sock,(struct sockaddr *)&cin,&leng);

这个s都是每个线程公用的

           ...
chenzhanyiczy 发表于 2010-07-05 15:11




  谢谢,如上贴,每个线程用完了Conn,通知主线程,主线程收到通知再进行下一轮accept,就没问题了。
关键不是s共用,而是Conn共用了。s放到Conn里边跟你放到fd里边是一个意思。
s这个地址不会交给子线程,所以没必要fd,只是把s的值放到Conn,而Conn的地址交到子线程,就出现问题了,这个地址里的内容在一个时间里只能由一个线程使用。

作者: yulihua49   发布时间: 2010-07-05

本帖最后由 yulihua49 于 2010-07-05 15:44 编辑


QUOTE:
回复  yulihua49


    虽然可能性很小,但是是存在的.这也解释了为什么你usleep以后情况会有所好转.最好 ...
samlumengjun 发表于 2010-07-05 14:41




    感谢支持。当初用你这办法也可能不错,现在Conn的使用串行化了,每收到一个连接都得等等,也能解决问题。就是需要等等,你的办法可能不需要等。
注意我子线程函数,发通知的时候,服务还未开始,只进行了初始化。线程还是并行的,只接受连接串行了。

作者: yulihua49   发布时间: 2010-07-05

传值 和 传址

作者: ssuclinux   发布时间: 2010-07-05

本帖最后由 yulihua49 于 2010-07-05 17:11 编辑


QUOTE:
回复  yulihua49


    虽然可能性很小,但是是存在的.这也解释了为什么你usleep以后情况会有所好转.最好 ...
samlumengjun 发表于 2010-07-05 14:41




    又出现新问题:
大量客户端同时传送数据时,个别线程会出现:
1 thrsrv:8942 07/05 16:17'07 thread_work:tid=46914753890624,接收结束,status=84,Invalid or incomplete multibyte or wide character
此时服务器端关闭连接,退出线程。服务器出现内存泄漏(占用内存增加,服务器不会死掉),客户端进程死等。大约500个链接里出现3-5个。

每出现一个这样的错误就会挂住一个客户端进程。

客户端的状态是,成功连接,与服务器协商密钥完成,发出登录包,等回答。
服务器未收到登录包。84号错误返回。

作者: yulihua49   发布时间: 2010-07-05

回复 yulihua49


    为什么不看清楚我的回复?都和你说了像这样的原形如果事先没有设置线程detach的话要在线程处理函数里加上pthread_detach(pthread_self());这么一句,要不然随着连接数的上升.会资源耗尽.

作者: samlumengjun   发布时间: 2010-07-05

另外你的情况应该有两个问题,第一个问题就是我说的你没有设置线程属性,自行释放资源.还有一个问题是在客户端上,server这里close socket的话,客户端那里read到-1,应该也自己close socket.你客户端代码是怎么处理的?

作者: samlumengjun   发布时间: 2010-07-05