多线程服务器,发现个问题,请高人解惑
时间:2010-07-05
来源:互联网
怪现象:当客户端密集到达连接时,accept竟然会返回重复的s(socket),导致后边若干线程使用相同的socket,通信乱套了,内存也乱套了。
为此在成功create_thread后,特意usleep(10000);有改善,没根本解决。
- 。。。。前边正常,略
- listen(sock,1000);
- while(1) {
- do {
- FD_ZERO(&efds);
- FD_SET(sock, &efds);
- //健康检查周期5分钟
- tm.tv_sec=300;
- tm.tv_usec=0;
- ret=select(sock+1,&efds,NULL,&efds,&tm);
- if(ret==-1) {
- ShowLog(1,"select error %s",strerror(errno));
- close(sock);
- quit(3);
- }
- if(ret==0 && poolchk) poolchk();
- } while(ret<=0);
- s=accept(sock,(struct sockaddr *)&cin,&leng);//就是返回的s有问题了。
- if(s<0) {
- ShowLog(1,"%s:accept err=%d,%s",__FUNCTION__,errno,strerror(errno));
- switch(errno) {
- case EMFILE: //fd用完了,其他线程还要继续工作,主线程休息一下。
- case ENFILE:
- sleep(60);
- continue;
- default:break;
- }
- sleep(3);
- if(++repeat < 20) continue;
- ShowLog(1,"%s:network fail! err=%s",__FUNCTION__,strerror(errno));
- close(sock);
- quit(5);
- }
- Conn.Socket=s;
- Conn.only_do=conn_init; //借用一下
- Conn.SendLen=sizeof_gda;
- ret=pthread_create(&pthread_id,&attr,thread_work,&Conn);
- if(ret) {
- ShowLog(1,"%s:pthread_create:%s",__FUNCTION__,strerror(ret));
- close(s);
- if(ret==EAGAIN||ret==ENOMEM) { //线程数用完了,休息一会,等一些线程退出
- sleep(30);
- }
- continue;
- }
- usleep(10000);//歇会
- }
-
- close(sock);
- quit(0);
- }
作者: yulihua49 发布时间: 2010-07-05
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
作者: 没本 发布时间: 2010-07-05

作者: lenky0401 发布时间: 2010-07-05
没本 发表于 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
switch(errno) {
后面switch里面的errno很可能已经因调用ShowLog而被改了
作者: hellioncu 发布时间: 2010-07-05
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
lenky0401 发表于 2010-07-05 12:23
1.select在主线程用,并未涉及子线程。
2.光使用accept也出现过这问题,先是usleep(1000),后来改10000.
实际上,usleep至少休息4000微秒。
作者: yulihua49 发布时间: 2010-07-05
作者: samlumengjun 发布时间: 2010-07-05
samlumengjun 发表于 2010-07-05 13:45
conn的内容在子线程被拷贝出来。
但有可能被共用,我看看。
static void * thread_work(void *param)
{
T_Connect Conn=*(T_Connect *)param;
。。。。。。。
是否拷贝时就冲突了???
作者: yulihua49 发布时间: 2010-07-05
虽然可能性很小,但是是存在的.这也解释了为什么你usleep以后情况会有所好转.最好的办法是在主线程里malloc一个Conn结构传给线程函数,然后在线程函数结束时free掉.另外,像这样的原形如果事先没有设置线程detach的话要在线程处理函数里加上pthread_detach(pthread_self());这么一句,要不然随着连接数的上升.会资源耗尽.
作者: samlumengjun 发布时间: 2010-07-05
samlumengjun 发表于 2010-07-05 13:45
感谢哦,找到了。修改如下:
子线程:
- static void * thread_work(void *param)
- {
- T_Connect Conn=*(T_Connect *)param;
- T_NetHead Head;
- int ret,logined=0;
- T_SRV_Var ctx;
- srvfunc *fp;
- int svcnum=0;
-
- #ifdef __GNUC__
- char gda[Conn.SendLen+1];//本线程的全局数据区必须在此分配。
- #else
- char *gda=alloca(Conn.SendLen+1);
- #endif
-
- if(Conn.SendLen>0) ctx.var=gda;
- else ctx.var=0;
- Conn.SendLen=0;
- ctx.tid=pthread_self();//标志多线程服务
- ctx.poolno=0;
- ___SQL_Init_SQL_Connect(&ctx.SQL_Connect);
- Conn.Var=&ctx;
- Conn.only_do=0;
- //借用only_do存放函数地址 conn_init
- if(((T_Connect *)param)->only_do) ((T_Connect *)param)->only_do(&Conn,&Head);
- ((T_Connect *)param)->Socket=-1;//通知主线程,param可以重用了。
- ret=pthread_create(&pthread_id,&attr,thread_work,&Conn);
- if(ret) {
- ShowLog(1,"%s:pthread_create:%s",__FUNCTION__,strerror(ret));
- close(s);
- if(ret==EAGAIN||ret==ENOMEM) { //线程数用完了,休息一会,等一些线程退出
- sleep(30);
- }
- continue;
- }
- 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
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
虽然可能性很小,但是是存在的.这也解释了为什么你usleep以后情况会有所好转.最好 ...
samlumengjun 发表于 2010-07-05 14:41
感谢支持。当初用你这办法也可能不错,现在Conn的使用串行化了,每收到一个连接都得等等,也能解决问题。就是需要等等,你的办法可能不需要等。
注意我子线程函数,发通知的时候,服务还未开始,只进行了初始化。线程还是并行的,只接受连接串行了。
作者: yulihua49 发布时间: 2010-07-05
作者: ssuclinux 发布时间: 2010-07-05
虽然可能性很小,但是是存在的.这也解释了为什么你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
为什么不看清楚我的回复?都和你说了像这样的原形如果事先没有设置线程detach的话要在线程处理函数里加上pthread_detach(pthread_self());这么一句,要不然随着连接数的上升.会资源耗尽.
作者: samlumengjun 发布时间: 2010-07-05
作者: samlumengjun 发布时间: 2010-07-05
热门阅读
-
office 2019专业增强版最新2021版激活秘钥/序列号/激活码推荐 附激活工具
阅读:74
-
如何安装mysql8.0
阅读:31
-
Word快速设置标题样式步骤详解
阅读:28
-
20+道必知必会的Vue面试题(附答案解析)
阅读:37
-
HTML如何制作表单
阅读:22
-
百词斩可以改天数吗?当然可以,4个步骤轻松修改天数!
阅读:31
-
ET文件格式和XLS格式文件之间如何转化?
阅读:24
-
react和vue的区别及优缺点是什么
阅读:121
-
支付宝人脸识别如何关闭?
阅读:21
-
腾讯微云怎么修改照片或视频备份路径?
阅读:28