1. 程式人生 > >9、C_鏈表 & 狀態機與多線程

9、C_鏈表 & 狀態機與多線程

ins 表頭 include 程序運行時間 urn header 碎片 null 現實

4.9.1.鏈表的引入

4.9.1.1、從數組的缺陷說起
  • 數組有2個缺陷:一個是數組中所有元素的類型必須一致;第二個是數組的元素個數必須事先制定並且一旦指定之後不能更改。
  • 如何解決數組的2個缺陷:數組的第一個缺陷靠結構體去解決。結構體允許其中的元素的類型不相同,因此解決了數組的第一個缺陷。所以說結構體是因為數組不能解決某些問題所以才發明的。
  • 如何解決數組的第二個缺陷?我們希望數組的大小能夠實時擴展。譬如我剛開始定了一個元素個數是10,後來程序運行時覺得不夠因此動態擴展為20.普通的數組顯然不行,我們可以對數組進行封裝以達到這種目的;我們還可以使用一個新的數據結構來解決,這個新的數據結構就是鏈表。
  • 總結:幾乎可以這樣理解:鏈表就是一個元素個數可以實時變大/變小的數組。
4.9.1.2、大學為什麽都有新校區?
  • 學校初建的時候(類似於變量定義並初始化時),這時候因為旁邊都是荒地而沒有建築,因此學校的校園大小由自己定的;但是學校建立了之後旁邊慢慢的也有了其他建築(類似於這個變量分配了之後,內存的相鄰區域又分配了其他變量與這個變量地址相連),這時候你的校園隨著發展感覺不夠用了想要擴展,卻發現鄰居已經住滿了,校園的四周全部都是別人的建築,這時候學校要擴展有2個辦法:第一個是拆遷,第二個是搬遷,第三個是外部擴展。
  • 拆遷基本行不通,因為成本太高了。
  • 搬遷可以行的通。程序中解決數組大小擴展的一個思路就是整體搬遷。具體步驟是:先在另外的空白內存處建立一個大的數組,然後把原來的數組中的元素的值整個復制到新數組的頭部,然後再釋放掉原來數組的內存空間,並且把新的數組去替代原來的數組。這種可變數組在C語言中不支持,但是在更高級語言如C++、Java等裏面是支持的。
  • 外部擴展的思路是最常見的,基本可以說是最合理的。它的一個思路就是化整為零,在原來的不動的前提下去外部擴展新的分基地。外部擴展在學校的例子中就是新校區;外部擴展在編程解決數組問題的點上就是鏈表。
4.9.1.3、鏈表是什麽樣的?
  • 顧名思義,鏈表就是用鎖鏈連接起來的表。這裏的表指的是一個一個的節點(一個節點就是一個校區),節點中有一些內存可以用來存儲數據(所以叫表,表就是數據表);這裏的鎖鏈指的是鏈接各個表的方法,C語言中用來連接2個表(其實就是2塊內存)的方法就是指針。
  • 鏈表是由若幹個節點組成的(鏈表的各個節點結構是完全類似的),節點由有效數據和指針組成。有效數據區域用來存儲信息完成任務的,指針區域用於指向鏈表的下一個節點從而構成鏈表。
4.9.1.4、時刻別忘了鏈表是用來幹嘛的
  • 時刻謹記:鏈表就是用來解決數組的大小不能動態擴展的問題,所以鏈表其實就是當數組用的。直白點:鏈表能完成的任務用數組也能完成,數組能完成的任務用鏈表也能完成。但是靈活性不一樣。
  • 簡單說:鏈表就是用來存儲數據的。鏈表用來存數據相對於數組來說優點就是靈活性,需要多少個動態分配多少個,不占用額外的內存。數組的優勢是使用簡單(簡單粗暴)。
4.9.2.單鏈表的實現 4.9.2.1、單鏈表的節點構成
  • 鏈表是由節點組成的,節點中包含:有效數據和指針。
  • 定義的struct link只是一個結構體,本身並沒有變量生成,也不占用內存。結構體定義相當於為鏈表節點定義了一個模板,但是還沒有一個節點,將來在實際創建鏈表時需要一個節點時用這個模板來復制一個即可。
  • struct link
  • {
  • int data; // 定義一個有效數據
  • struct link *pNext; // 指向下一個節點
  • };
4.9.2.2、堆內存的申請和使用
  • 鏈表的內存要求比較靈活,不能用棧,也不能用data數據段(開機分配)。只能用堆內存。(既 內存無限多,能隨意刪除,)
  • 使用堆內存來創建一個鏈表節點的步驟:
  1. 申請堆內存,大小為一個節點的大小(檢查申請結果是否正確);
  2. 清理申請到的堆內存;
  3. 把申請到的堆內存當作一個新節點;
  4. 填充下個新節點的有效數據和指針區域。
  • struct link *pHeader = NULL; // 定義一個頭指針,指向一個鏈表
  • // 創建一個鏈表節點;每創建一個新節點,就把這個節點和上一個節點關聯起來
  • struct link *p1 = (struct link *)malloc(sizeof(struct link)); // 分配一個結構體大小的內存空間
  • if(NULL == p1) // 檢查指針的有效性
  • {
  • printf("malloc *p1 error \n");
  • return -1;
  • }
  • memset(p1, ‘\0‘, sizeof(struct link));
  • p1->data = 1; // 給一個節點填充數據
  • p1->pNext = NULL; // 指向下一個節點的首地址,,由於不知道,所以占時設為NULL,實際操作時將下一個節點 \
  • malloc的返回值賦值給這個指針
  • pHeader = p1; // 將這個節點和它前面的頭指針關聯起來
4.9.2.3、鏈表的頭指針
  • 頭指針並不是節點,而是一個普通指針,只占4字節。頭指針的類型是struct link*類型的,所以它才能指向鏈表的節點。
  • 一個典型的鏈表的實現就是:頭指針指向鏈表的第1個節點,然後第1個節點中的指針指向下一個節點,然後依次類推一直到最後一個節點。這樣就構成了一個鏈。
  • struct link *pHeader = NULL; // 定義一個頭指針,指向一個鏈表
4.9.2.4、實戰:構建一個簡單的單鏈表
  • 目標:構建一個鏈表,然後將一些數據(譬如1,2,3三個數字)存儲在鏈表中
技術分享圖片 技術分享圖片 技術分享圖片 4.9.3.單鏈表的算法之插入節點 4.9.3.1、訪問鏈表中各個節點的數據
  • 只能用頭指針,不能用各個節點自己的指針。因為在實際當中我們保存鏈表的時候是不會保存各個節點的指針的,只能通過頭指針來訪問鏈表節點。
  • 前一個節點內部的pNext指針能幫助我們找到下一個節點。
4.9.3.2、將創建節點的代碼封裝成一個函數
  • 封裝時的關鍵點就是函數的接口(函數參數和返回值)的設計
  • struct link * create_link(int data)
  • {
  • struct link *pHeader = NULL;
  • struct link *p = (struct link *)malloc(sizeof(struct link));
  • if(NULL == p)
  • {
  • printf("malloc *p error \n");
  • return NULL;
  • }
  • memset(p, ‘\0‘, sizeof(struct link));
  • p->data = data;
  • p->pNext = NULL;
  • return p;
  • }
  • int main(void)
  • {
  • struct link *pHeader = NULL; // 定義一個頭指針,指向一個鏈表
  • pHeader = create_link(12);
  • pHeader->pNext = create_link(23);
  • pHeader->pNext->pNext = create_link(34);
  • printf("node1 data: %d.\n", pHeader->data);
  • printf("node2 data: %d.\n", pHeader->pNext->data);
  • printf("node3 data: %d.\n", pHeader->pNext->pNext->data);
  • return 0;
  • }
4.9.3.3、從鏈表頭部插入新節點 技術分享圖片
  • // 在節點的末尾插入一個新的節點。 思路:由頭指針向後遍歷,直到走到最後一個節點
  • void insert_tail(struct link *pHeader, struct link *new)
  • {
  • struct link *p = pHeader;
  • while(NULL != p->pNext) // 第一步,在鏈表中找到最後一個節點
  • {
  • p = p->pNext; // 如果不是最後一個節點,級走向下一個節點
  • }
  • p->pNext = new; // 第二步,在鏈表的最後插入一個新節點
  • }
  • int main(void)
  • {
  • struct link *pHeader = create_link(11); // 沒有節點的情況下,先創建一個想新節點
  • insert_tail(pHeader, create_link(22));
  • insert_tail(pHeader, create_link(33));
  • printf("node1 data: %d.\n", pHeader->data);
  • printf("node2 data: %d.\n", pHeader->pNext->data);
  • printf("node3 data: %d.\n", pHeader->pNext->pNext->data);
  • return 0;
  • }
4.9.4.單鏈表的算法之插入節點續 4.9.4.1、詳解鏈表頭部插入函數 4.9.4.2、什麽是頭節點
  • 問題:因為我們在insert_tail中直接默認了頭指針指向的有一個節點,因此如果程序中直接定義了頭指針後就直接insert_tail就會報段錯誤。我們不得不在定義頭指針之後先create_link創建一個新節點給頭指針初始化,否則不能避免這個錯誤;但是這樣解決讓程序看起來邏輯有點不太順,因為看起來第一個節點和後面的節點的創建、添加方式有點不同。
  • 鏈表還有另外一種用法,就是把頭指針指向的第一個節點作為頭節點使用。頭節點的特點是:第一,它緊跟在頭指針後面。第二,頭節點的數據部分是空的(有時候不是空的,而是存儲整個鏈表的節點數),指針部分指向下一個節點,也就是第一個節點。
技術分享圖片
  • 這樣看來,頭節點確實和其他節點不同。我們在創建一個鏈表時添加節點的方法也不同。頭節點在創建頭指針時一並創建並且和頭指針關聯起來;後面的真正的存儲數據的節點用節點添加的函數來完成,譬如insert_tail.
  • // 在節點的末尾插入一個新的節點
  • void insert_tail(struct link *pHeader, struct link *new)
  • {
  • int count = 0; // 計算添加節點的個數
  • struct link *p = pHeader;
  • while(NULL != p->pNext) // 第一步,在鏈表中找到最後一個節點
  • {
  • p = p->pNext; // 如果不是最後一個節點,級走向下一個節點
  • count ++;
  • }
  • p->pNext = new; // 第二步,在鏈表的最後插入一個新節點
  • pHeader->data = count+1; // 因為在插入前已經有一個空節點,所以+1
  • }
  • int main(void)
  • {
  • struct link *pHeader = create_link(0); // 創建一個空節點,假設為0
  • insert_tail(pHeader, create_link(11));
  • insert_tail(pHeader, create_link(22));
  • insert_tail(pHeader, create_link(33));
  • printf("node0 data: %d.\n", pHeader->data); // node0 data: 3.
  • printf("node1 data: %d.\n", pHeader->pNext->data); // node1 data: 11.
  • printf("node2 data: %d.\n", pHeader->pNext->pNext->data); // node2 data: 22.
  • printf("node3 data: %d.\n", pHeader->pNext->pNext->pNext->data); // node3 data: 33.
  • return 0;
  • }
  • 鏈表有沒有頭節點是不同的。體現在鏈表的插入節點、刪除節點、遍歷節點、解析鏈表的各個算法函數都不同。所以如果一個鏈表設計的時候就有頭節點那麽後面的所有算法都應該這樣來處理;如果設計時就沒有頭節點,那麽後面的所有算法都應該按照沒有頭節點來做。實際編程中兩種鏈表都有人用,所以大家在看別人寫的代碼時一定要註意看它有沒有頭節點。
4.9.5.從鏈表頭部插入新節點
  • 註意寫代碼過程中的箭頭符號,和說話過程中的指針指向。這是兩碼事,容易搞混。箭頭符號實際上是用指針方式來訪問結構體,所以箭頭符號的實質是訪問結構體中的成員。更清楚一點說程序中的箭頭和鏈表的連接沒有任何關系;鏈表中的節點通過指針指向來連接,編程中表現為一個賦值語句(用=來進行連接),實質是把後一個節點的首地址,賦值給前一個節點中的pNext元素做為值。
  • 鏈表可以從頭部插入,也可以從尾部插入。也可以兩頭插入。頭部插入和尾部插入對鏈表來說幾乎沒有差別。對鏈表本身無差別,但是有時候對業務邏輯有差別。
技術分享圖片 技術分享圖片 4.9.6.單鏈表的算法之遍歷節點 4.9.6.1、什麽是遍歷
  • 遍歷就是把單鏈表中的各個節點挨個拿出來,就叫遍歷。
  • 遍歷的要點:一是不能遺漏、二是不能重復、追求效率。
4.9.6.2、如何遍歷單鏈表
  • 分析一個數據結構如何遍歷,關鍵是分析這個數據結構本身的特點。然後根據本身特點來制定它的遍歷算法。
  • 單鏈表的特點就是由很多個節點組成,頭指針+頭節點為整個鏈表的起始,最後一個節點的特征是它內部的pNext指針值為NULL。從起始到結尾中間由各個節點內部的pNext指針來掛接。由起始到結尾的路徑有且只有一條。單鏈表的這些特點就決定了它的遍歷算法。
  • 遍歷方法:從頭指針+頭節點開始,順著鏈表掛接指針依次訪問鏈表的各個節點,取出這個節點的數據,然後再往下一個節點,直到最後一個節點,結束返回。
4.9.6.3、編程實戰
  • 寫一個鏈表遍歷的函數,void ergodic(struct link *pHeader);
技術分享圖片 4.9.7.單鏈表的算法之刪除節點 4.9.7.1、為什麽要刪除節點
  • 一直在強調,鏈表到底用來幹嘛的?鏈表使用來存數據的
  • 有時候鏈表節點中的數據不想要了,因此要刪掉這個節點。
4.9.7.2、刪除節點的2個步驟
  • 第一步:找到要刪除的節點;第二步:刪除這個節點。
4.9.7.3、如何找到待刪除的節點
  • 通過遍歷來查找節點。從頭指針+頭節點開始,順著鏈表依次將各個節點拿出來,按照一定的方法比對,找到我們要刪除的那個節點。
4.9.7.4、如何刪除一個節點
  • 待刪除的節點不是尾節點的情況:首先把待刪除的節點的前一個節點的pNext指針指向待刪除的節點的後一個節點的首地址(這樣就把這個節點從鏈表中摘出來了),然後再將這個摘出來的節點free掉接口。
技術分享圖片
  • 待刪除的節點是尾節點的情況:首先把待刪除的尾節點的前一個節點的pNext指針指向null(這時候就相當於原來尾節點前面的一個節點變成了新的尾節點),然後將摘出來的節點free掉。
技術分享圖片 4.9.7.5、註意堆內存的釋放
  • 前面幾節課我們寫的代碼最終都沒有釋放堆內存。當程序都結束了的情況下那些沒有free的堆內存也被釋放了。
  • 有時候我們的程序運行時間很久,這時候malloc的內存如果沒有free會一直被占用直到你free釋放它或者整個程序終止。
技術分享圖片 4.9.8.單鏈表的算法之逆序 4.9.8.1、什麽是鏈表的逆序
  • (1)鏈表的逆序又叫反向,意思就是把鏈表中所有的有效節點在鏈表中的順序給反過來。
4.9.8.2、單鏈表逆序算法分析
  • (1)當我們對一個數據結構進行一個操作時,我們就需要一套算法。這就是數據結構和算法的關系。
  • (2)我總結:算法有2個層次。第一個層次是數學和邏輯上的算法;第二次個層次是用編程語言來實現算法。
  • (3)從邏輯上來講,鏈表的逆序有很多種方法。這些方法都能實現最終的需要,但是效率是不一樣的。彼此的可擴展性、容錯性等不同。
  • (4)思路:首先遍歷原鏈表,然後將原鏈表中的頭指針和頭節點作為新鏈表的頭指針和頭節點,原鏈表中的有效節點挨個依次取出來,采用頭插入的方法插入新鏈表中即可。
  • (5)鏈表逆序 = 遍歷 + 頭插入
4.9.8.3、編程實戰 方法一: 技術分享圖片 技術分享圖片 方法二: 技術分享圖片 技術分享圖片 4.9.9.雙鏈表的引入和基本實現 4.9.9.1、單鏈表的局限性
  • (1)單鏈表是對數組的一個擴展,解決了數組的大小比較死板不容易擴展的問題。使用堆內存來存儲數據,將數據分散到各個節點之間,其各個節點在內存中可以不相連,節點之間通過指針進行單向鏈接。鏈表中的各個節點內存不相連,有利於利用碎片化的內存。
  • (2)單鏈表各個節點之間只由一個指針單向鏈接,這樣實現有一些局限性。局限性主要體現在單鏈表只能經由指針單向移動(一旦指針移動過某個節點就無法再回來,如果要再次操作這個節點除非從頭指針開始再次遍歷一次),因此單鏈表的某些操作就比較麻煩(算法比較有局限)。回憶之前單鏈表的所有操作(插入、刪除節點、 遍歷、從單鏈表中取某個節點的數·····),因為單鏈表的單向移動性導致了不少麻煩。
  • 總結:單鏈表的單向移動性導致我們在操作單鏈表時,當前節點只能向後移動不能向前移動,因此不自由,不利於解決更復雜的算法。
4.9.9.2、解決思路:有效數據+2個指針的節點(雙鏈表)
  • (1)單鏈表的節點 = 有效數據 + 指針(指針指向後一個節點)
  • (2)雙向鏈表的節點 = 有效數據 + 2個指針(一個指向後一個節點,另一個指向前一個節點)
4.9.9.3、雙鏈表的封裝和編程實現 技術分享圖片 4.9.10.雙鏈表的算法之插入節點 技術分享圖片 4.9.10.1、尾部插入 技術分享圖片 4.9.10.2、頭部插入 技術分享圖片 4.9.11.雙鏈表的算法之遍歷節點
  • (1)雙鏈表是單鏈表的一個父集。雙鏈表中如何完全無視pPrev指針,則雙鏈表就變成了單鏈表。這就決定了雙鏈表的正向遍歷(後向遍歷)和單鏈表是完全相同的。
  • (2)雙鏈表中因為多了pPrev指針,因此雙鏈表還可以前向遍歷(從鏈表的尾節點向前面依次遍歷直到頭節點)。但是前向遍歷的意義並不大,主要是因為很少有當前當了尾節點需要前向遍歷的情況。
  • (3)總結:雙鏈表是對單鏈表的一種有成本的擴展,但是這個擴展在有些時候意義不大,在另一些時候意義就比較大。因此在實踐用途中要根據業務要求選擇適合的鏈表。
技術分享圖片 技術分享圖片 4.9.12.雙鏈表的算法之刪除節點 技術分享圖片 技術分享圖片 4.9.13.linux內核鏈表 4.9.13.1、前述鏈表數據區域的局限性
  • 之前定義數據區域時直接int data;我們認為我們的鏈表中需要存儲的是一個int類型的數。但是實際上現實編程中鏈接中的節點不可能這麽簡單,而是多種多樣的。
  • 一般實際項目中的鏈表,節點中存儲的數據其實是一個結構體,這個結構體中包含若幹的成員,這些成員加起來構成了我們的節點數據區域。
4.9.13.2、一般性解決思路:數據區封裝為一個結構體
  • (1)因為鏈表實際解決的問題是多種多樣的,所以內部數據區域的結構體構成也是多種多樣的。這樣也導致了不同程序當中的鏈表總體構成是多種多樣的。導致的問題是:我們無法通過一個泛性的、普遍適用的操作函數來訪問所有的鏈表。這就意味著我們設計一個鏈表就得寫一套鏈表的操作函數(節點創建、插入、刪除、遍歷······)
  • (2)實際上深層次分析會發現:不同的鏈表雖然這些方法不能通用需要單獨寫,但是實際上內部的思路和方法是相同的,只是函數的局部地區有不同。(實際上鏈表操作是相同的,而涉及到數據區域的操作就有不同)
  • (3)鑒於以上2點:我們的理念就是,能不能有一種辦法把所有鏈表中操作方法裏共同的部分提取出來用一套標準方法實現,然後把不同的部分留著讓具體鏈表的實現者自己去處理。
4.9.13.3、內核鏈表的設計思路
  • (1)內核鏈表中自己實現了一個純鏈表(純鏈表就是沒有數據區域,只有前後向指針)的封裝,以及純鏈表的各種操作函數(節點創建、插入、刪除、遍歷······)。這個純鏈表本身自己沒有任何用處,它的用法是給我們具體鏈表作為核心來調用。
4.9.13.4、list.h文件簡介
  • (1)內核中核心純鏈表的實現在include/linux/list.h文件中
  • (2)list.h中就是一個純鏈表的完整封裝,包含節點定義和各種鏈表操作方法。
4.9.14.內核鏈表的基本算法和使用簡介 4.9.14.1、內核鏈表的節點創建、刪除、遍歷等 4.9.14.2、內核鏈表的使用實踐
  • (1)問題:內核鏈表只有純鏈表,沒有數據區域,怎麽使用?
  • (2)設計的使用方法是將內核鏈表作為將來整個數據結構的結構體的一個成員內嵌進去。
4.9.15.什麽是狀態機 4.9.15.1、有限狀態機
  • (1)常說的狀態機是有限狀態機FSM。FSM指的是有有限個狀態(一般是一個狀態變量的值),這個機器同時能夠從外部接收信號和信息輸入,機器在接收到外部輸入的信號後會綜合考慮當前自己的狀態和用戶輸入的信息,然後機器做出動作:跳轉到另一個狀態。
  • (2)考慮狀態機的關鍵點:當前狀態、外部輸入、下一個狀態
4.9.15.2、兩種狀態機:Moore型和Mealy型
  • (1)Moore型狀態機特點是:輸出只與當前狀態有關(與輸入信號無關)。相對簡單,考慮狀態機的下一個狀態時只需要考慮它的當前狀態就行了。
  • (2)Mealy型狀態機的特點是:輸出不只和當前狀態有關,還與輸入信號有關。狀態機接收到一個輸入信號需要跳轉到下一個狀態時,狀態機綜合考慮2個條件(當前狀態、輸入值)後才決定跳轉到哪個狀態。
4.9.15.3、狀態機的主要用途:電路設計、FPGA程序設計、軟件設計
  • (1)電路設計中廣泛使用了狀態機思想
  • (2)FPGA程序設計
  • (3)軟件設計(框架類型的設計,譬如操作系統的GUI系統、消息機制)
4.9.15.4、狀態機解決了什麽問題
  • (1)我們平時寫程序都是順序執行的,這種程序有個特點:程序的大體執行流程是既定的,程序的執行是遵照一定的大的方向有跡可尋的。
  • (2)但是偶爾會碰到這樣的程序:外部不一定會按照既定流程來給程序輸入信息,而程序還需要完全能夠接收並響應外部的這些輸入信號,還要能做出符合邏輯的輸出。
4.9.16.C語言實現簡單的狀態機
  • 4.9.16.1、題目:開鎖狀態機。功能描述:用戶連續輸入正確的密碼則會開鎖,如果密碼輸入過程錯誤則鎖會退回到初始狀態重新計入密碼,即:用戶只需要連續輸入出正確的密碼即可開鎖(輸入錯誤不用撤銷、也不用刪除)
4.9.16.2、題目分析 4.9.17.多線程簡介 4.9.17.1、操作系統下的並行執行機制
  • (1)並行就是說多個任務同時被執行。並行分微觀上的並行和宏觀上的並行。
  • (2)宏觀上的並行就是從長時間段(相對於人來說)來看,多個任務是同時進行的;微觀上的並行就是真的在並行執行。
  • (3)操作系統要求實現宏觀上的並行。宏觀上的並行有2種情況:第一種是微觀上的串行,第二種是微觀上的並行。
  • (4)理論來說,單核CPU本身只有一個核心,同時只能執行一條指令,這種CPU只能實現宏觀上的並行,微觀上一定是串行的。微觀上的並行要求多核心CPU。多核CPU中的多個核心可以同時微觀上執行多個指令,因此可以達到微觀上的並行,從而提升宏觀上的並行度。
4.9.17.2、進程和線程的區別和聯系
  • (1)進程和線程是操作系統的兩種不同軟件技術,目的是實現宏觀上的並行(通俗一點就是讓多個程序同時在一個機器上運行,達到宏觀上看起來並行執行的效果)。
  • (2)進程和線程在實現並行效果的原理上不同。而且這個差異和操作系統有關。譬如windows中進程和線程差異比較大,在linux中進程和線程差異不大(linux中線程就是輕量級的進程)。
  • (3)不管是多進程還是多線程,最終目標都是實現並行執行。
4.9.17.3、多線程的優勢
  • (1)前些年多進程多一些,近些年多線程開始用得多。
  • (2)現代操作系統設計時考慮到了多核心CPU的優化問題,保證了:多線程程序在運行的時候,操作系統會優先將多個線程放在多個核心中分別單獨運行。所以說多核心CPU給多線程程序提供了完美的運行環境。所以在多核心CPU上使用多線程程序有極大的好處。
4.9.17.4、線程同步和鎖
  • (1)多線程程序運行時要註意線程之間的同步。
  • 4.9.17.5、詳細情況敬請期待:《3.linux應用編程和網絡編程》

9、C_鏈表 & 狀態機與多線程