从0实现基于Linux socket聊天室-多线程服务器一个很隐晦的错误-2

<p>根据 《<a>0 基于socket和pthread实现多线程服务器模型</a>》所述,server创建子线程的时候用的是以下代码:</p><pre> pconnsocke = (int *) malloc(sizeof(int));
*pconnsocke = new_fd;

    ret&nbsp;=&nbsp;pthread_create(&amp;tid,&nbsp;NULL,&nbsp;rec_func,&nbsp;(void&nbsp;*)&nbsp;pconnsocke);
    if&nbsp;(ret&nbsp;&lt;&nbsp;0)&nbsp;
    {
        perror(&quot;pthread_create&nbsp;err&quot;);
        return&nbsp;-1;
    }   </pre><p><strong>为什么必须要malloc一块内存专门存放这个新的套接字呢?</strong></p><p>要讲清楚这个问题的原因需要一些背景知识:</p><ol><li><p>Linux创建一个新进程时,新进程会创建一个主线程;</p></li><li><p>每个用户进程有自己的地址空间,系统为每个用户进程创建一个task_struct来描述该进程,

实际上task_struct 和地址空间映射表一起用来,表示一个进程;</p></li><li><p>Linux里同样用task_struct来描述一个线程,线程和进程都参与统一的调度;</p></li><li><p>进程内的不同线程执行是同一程序的不同部分,各个线程并行执行,受操作系统异步调度;</p></li><li><p>由于进程的地址空间是私有的,因此在进程间上下文切换时,系统开销比较大;</p></li><li><p>在同一个进程中创建的线程共享该进程的地址空间。</p></li></ol><p>明白这些基础知识后,下面我来看下,当进程创建一个子线程的时候,传递的参数情况:</p><h1>直接传递栈中内存地址</h1><p class="image-package">我们首先分析下如果创建子线程传递的是局部变量new_fd的地址这种情况。<img class="uploaded-img" src="https://upload-images.jianshu.io/upload_images/23850874-6d2f53a9690756a4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="auto" height="auto"/></p><p>由上图所示:</p><ol><li><p>创建一个线程,如果我们按照图中传递参数方法,那么new_fd是在栈中的,创建子线程的时候我们把new_fd地址传递给了thread1,线程回调参数arg的地址是new_fd地址。</p></li><li><p>因为主函数会一直循环不退出,所以new_fd一直存在栈中。用这种方法的确可以把new_fd的值3传递到子线程的局部变量fd,这样子线程就可以使用这个fd与客户端通信。</p></li><li><p>但是因为我们设计的是并发服务器模型,我们没有办法预测客户端什么时候会连接我们的服务器,假设遇到一个极端情况,在同一时刻,多个客户端同时连接服务器,那么主线程是要同时创建多个子线程的。</p></li></ol><p class="image-package"><strong>多个客户端同时连接服务器</strong><img class="uploaded-img" src="https://upload-images.jianshu.io/upload_images/23850874-9d13da655775fb2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="auto" height="auto"/></p><p>如上图所示,所有新建的的thread回调函数的参数arg存放的都是new_fd的地址。如果客户端连接的时候时间间隔比较大,是没有问题的,但是在一些极端的情况下还是有可能出现由于高并发引起的错误。</p><p><strong>我们来捋一下极端的调用时序:</strong></p><p/><p class="image-package"><img class="uploaded-img" src="https://upload-images.jianshu.io/upload_images/23850874-9493e6f1b39f668a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="auto" height="auto"/></p><p>如上图所示:</p><ol><li><p>T1时刻,当客户端1连接服务器的时候,服务器的accept函数会创建新的套接字4;</p></li><li><p>T2时刻,创建了子线程thread1,同时子线程回调函数参数arg指向了栈中new_fd对应的内存。</p></li><li><p>假设,正在此时,又有一个客户端要连接服务器,而且thread1页已经用尽了时间片,那么主线程server会被调度到。</p></li></ol><p/><p class="image-package"><img class="uploaded-img" src="https://upload-images.jianshu.io/upload_images/23850874-c1a343d46f951776.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="auto" height="auto"/></p><p>如上图所示:</p><ol><li><p>T3时刻,主线程server接受了客户端的连接,accept函数会创建新的套接字5,同时创建子线程thread2,此时OS调度的thread2;</p></li><li><p class="image-package">T4时刻,thread2通过arg得到new_fd了的值5,并存入fd;</p></li><li><p>T5时刻,时间片到了,调度thread1,thread1通过arg去读取new_fd,此时栈中new_fd的值已经修5覆盖了;</p></li><li><p>所以出现了2个线程同时使用同一个fd的情况发生。</p></li></ol><p>这种情况的发生,虽然概率很低,但是并不代表不发生,该bug就是一口君在解决实际项目中遇到过的。</p><h1>传递堆内存地址</h1><p>如果采用传递堆的地址的方式,我们看下图:</p><p class="image-package"><img class="uploaded-img" src="https://upload-images.jianshu.io/upload_images/23850874-efa0a0d3c100001b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="auto" height="auto"/></p><ol><li><p>T1时刻,当客户端1连接服务器的时候,服务器的accept函数会创建新的套接字4,在堆中申请一块内存,用指针pconnsocke指向该内存,同时将4保存到堆中;</p></li><li><p>T2时刻,创建了子线程thread1,同时子线程回调函数参数arg指向了堆中pconnsocke指向的内存。</p></li><li><p>假设,正在此时,又有一个客户端要连接服务器,而且thread1页已经用尽了时间片,那么主线程server会被调度到。</p></li><li><p>T3时刻,主线程server接受了客户端的连接,accept函数会创建新的套接字5,在堆中申请一块内存,用指针pconnsocke指向该内存,同时将5保存到堆中,然后创建子线程thread2;</p></li><li><p>T4时刻,thread2通过arg指向了堆中pconnsocke指向的内存,此处值为5,并存入fd;</p></li><li><p>T5时刻,时间片到了,调度thread1,thread1通过arg去读取fd,此时堆中数据位5;</p></li><li><p>就不会出现了2个线程同时使用同一个fd的情况发生。</p></li></ol><p>这个知识点有点隐蔽,希望读者在使用的时候多加小心。
下一章,我们要讲解如何利用我们现有的代码实现登录注册的功能。</p><p>获取更多关于Linux的资料,请关注公众号「一口Linux」</p><p>
</p>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,313评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,369评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,916评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,333评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,425评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,481评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,491评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,268评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,719评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,004评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,179评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,832评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,510评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,153评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,402评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,045评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,071评论 2 352