<p>根據(jù) 《<a>0 基于socket和pthread實(shí)現(xiàn)多線程服務(wù)器模型</a>》所述,server創(chuàng)建子線程的時(shí)候用的是以下代碼:</p><pre> pconnsocke?=?(int?*)?malloc(sizeof(int));
*pconnsocke?=?new_fd;
ret = pthread_create(&tid, NULL, rec_func, (void *) pconnsocke);
if (ret < 0)
{
perror("pthread_create err");
return -1;
} </pre><p><strong>為什么必須要malloc一塊內(nèi)存專門(mén)存放這個(gè)新的套接字呢产舞?</strong></p><p>要講清楚這個(gè)問(wèn)題的原因需要一些背景知識(shí):</p><ol><li><p>Linux創(chuàng)建一個(gè)新進(jìn)程時(shí)店雅,新進(jìn)程會(huì)創(chuàng)建一個(gè)主線程睹耐;</p></li><li><p>每個(gè)用戶進(jìn)程有自己的地址空間压储,系統(tǒng)為每個(gè)用戶進(jìn)程創(chuàng)建一個(gè)task_struct來(lái)描述該進(jìn)程逝她,
實(shí)際上task_struct 和地址空間映射表一起用來(lái)技竟,表示一個(gè)進(jìn)程局劲;</p></li><li><p>Linux里同樣用task_struct來(lái)描述一個(gè)線程勺拣,線程和進(jìn)程都參與統(tǒng)一的調(diào)度;</p></li><li><p>進(jìn)程內(nèi)的不同線程執(zhí)行是同一程序的不同部分鱼填,各個(gè)線程并行執(zhí)行药有,受操作系統(tǒng)異步調(diào)度;</p></li><li><p>由于進(jìn)程的地址空間是私有的苹丸,因此在進(jìn)程間上下文切換時(shí)愤惰,系統(tǒng)開(kāi)銷比較大;</p></li><li><p>在同一個(gè)進(jìn)程中創(chuàng)建的線程共享該進(jìn)程的地址空間赘理。</p></li></ol><p>明白這些基礎(chǔ)知識(shí)后宦言,下面我來(lái)看下,當(dāng)進(jìn)程創(chuàng)建一個(gè)子線程的時(shí)候商模,傳遞的參數(shù)情況:</p><h1>直接傳遞棧中內(nèi)存地址</h1><p class="image-package">我們首先分析下如果創(chuàng)建子線程傳遞的是局部變量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>創(chuàng)建一個(gè)線程,如果我們按照?qǐng)D中傳遞參數(shù)方法施流,那么new_fd是在棧中的响疚,創(chuàng)建子線程的時(shí)候我們把new_fd地址傳遞給了thread1,線程回調(diào)參數(shù)arg的地址是new_fd地址瞪醋。</p></li><li><p>因?yàn)橹骱瘮?shù)會(huì)一直循環(huán)不退出忿晕,所以new_fd一直存在棧中。用這種方法的確可以把new_fd的值3傳遞到子線程的局部變量fd银受,這樣子線程就可以使用這個(gè)fd與客戶端通信践盼。</p></li><li><p>但是因?yàn)槲覀冊(cè)O(shè)計(jì)的是并發(fā)服務(wù)器模型,我們沒(méi)有辦法預(yù)測(cè)客戶端什么時(shí)候會(huì)連接我們的服務(wù)器蚓土,假設(shè)遇到一個(gè)極端情況宏侍,在同一時(shí)刻,多個(gè)客戶端同時(shí)連接服務(wù)器蜀漆,那么主線程是要同時(shí)創(chuàng)建多個(gè)子線程的。</p></li></ol><p class="image-package"><strong>多個(gè)客戶端同時(shí)連接服務(wù)器</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回調(diào)函數(shù)的參數(shù)arg存放的都是new_fd的地址确丢。如果客戶端連接的時(shí)候時(shí)間間隔比較大绷耍,是沒(méi)有問(wèn)題的,但是在一些極端的情況下還是有可能出現(xiàn)由于高并發(fā)引起的錯(cuò)誤鲜侥。</p><p><strong>我們來(lái)捋一下極端的調(diào)用時(shí)序:</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時(shí)刻褂始,當(dāng)客戶端1連接服務(wù)器的時(shí)候,服務(wù)器的accept函數(shù)會(huì)創(chuàng)建新的套接字4描函;</p></li><li><p>T2時(shí)刻崎苗,創(chuàng)建了子線程thread1,同時(shí)子線程回調(diào)函數(shù)參數(shù)arg指向了棧中new_fd對(duì)應(yīng)的內(nèi)存舀寓。</p></li><li><p>假設(shè)胆数,正在此時(shí),又有一個(gè)客戶端要連接服務(wù)器互墓,而且thread1頁(yè)已經(jīng)用盡了時(shí)間片必尼,那么主線程server會(huì)被調(diào)度到。</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時(shí)刻篡撵,主線程server接受了客戶端的連接判莉,accept函數(shù)會(huì)創(chuàng)建新的套接字5,同時(shí)創(chuàng)建子線程thread2育谬,此時(shí)OS調(diào)度的thread2券盅;</p></li><li><p class="image-package">T4時(shí)刻,thread2通過(guò)arg得到new_fd了的值5,并存入fd膛檀;</p></li><li><p>T5時(shí)刻锰镀,時(shí)間片到了,調(diào)度thread1宿刮,thread1通過(guò)arg去讀取new_fd互站,此時(shí)棧中new_fd的值已經(jīng)修5覆蓋了;</p></li><li><p>所以出現(xiàn)了2個(gè)線程同時(shí)使用同一個(gè)fd的情況發(fā)生僵缺。</p></li></ol><p>這種情況的發(fā)生胡桃,雖然概率很低,但是并不代表不發(fā)生磕潮,該bug就是一口君在解決實(shí)際項(xiàng)目中遇到過(guò)的翠胰。</p><h1>傳遞堆內(nèi)存地址</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時(shí)刻自脯,當(dāng)客戶端1連接服務(wù)器的時(shí)候之景,服務(wù)器的accept函數(shù)會(huì)創(chuàng)建新的套接字4,在堆中申請(qǐng)一塊內(nèi)存膏潮,用指針pconnsocke指向該內(nèi)存锻狗,同時(shí)將4保存到堆中;</p></li><li><p>T2時(shí)刻,創(chuàng)建了子線程thread1轻纪,同時(shí)子線程回調(diào)函數(shù)參數(shù)arg指向了堆中pconnsocke指向的內(nèi)存油额。</p></li><li><p>假設(shè),正在此時(shí)刻帚,又有一個(gè)客戶端要連接服務(wù)器潦嘶,而且thread1頁(yè)已經(jīng)用盡了時(shí)間片,那么主線程server會(huì)被調(diào)度到崇众。</p></li><li><p>T3時(shí)刻掂僵,主線程server接受了客戶端的連接,accept函數(shù)會(huì)創(chuàng)建新的套接字5顷歌,在堆中申請(qǐng)一塊內(nèi)存锰蓬,用指針pconnsocke指向該內(nèi)存,同時(shí)將5保存到堆中衙吩,然后創(chuàng)建子線程thread2互妓;</p></li><li><p>T4時(shí)刻,thread2通過(guò)arg指向了堆中pconnsocke指向的內(nèi)存坤塞,此處值為5,并存入fd冯勉;</p></li><li><p>T5時(shí)刻,時(shí)間片到了摹芙,調(diào)度thread1灼狰,thread1通過(guò)arg去讀取fd,此時(shí)堆中數(shù)據(jù)位5浮禾;</p></li><li><p>就不會(huì)出現(xiàn)了2個(gè)線程同時(shí)使用同一個(gè)fd的情況發(fā)生交胚。</p></li></ol><p>這個(gè)知識(shí)點(diǎn)有點(diǎn)隱蔽,希望讀者在使用的時(shí)候多加小心盈电。
下一章蝴簇,我們要講解如何利用我們現(xiàn)有的代碼實(shí)現(xiàn)登錄注冊(cè)的功能。</p><p>獲取更多關(guān)于Linux的資料匆帚,請(qǐng)關(guān)注公眾號(hào)「一口Linux」</p><p>
</p>