<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                #### 第12章: #### 計算機系統漫游 本節以x86-64的機器代碼進行講解。 ##### 信息就是上下文 計算機系統是由硬件和系統軟件組成的,它們共同工作來運行應用程序。 系統中的所有的信息------包括磁盤文件、內存中的程序、內存中存放的用戶數據以及網絡上傳送的數據,都是由一串比特表示的。區分不同數據對象的唯一方法是我們讀到這些數據對象時的上下文。比如,在不同的上下文中,一個同樣的字節序列可能表示一個整數、浮點數、字符串或者機器指令。 :-: ![](https://img.kancloud.cn/ac/db/acdb2987305c27bde12ee4e45ac95ebb_604x530.png) ASCII碼 例如hello.c程序: ~~~ #include<stdio.h> ? int main() { ? ? ? printf("hello, world\n"); ? return 0; ? ? } ~~~ hello.c程序是以字節的方式存儲在文件中。每個字節都有一個整數值,對應于某些字符。例如,第一個字節的整數值是35,它對應字符'#'。第二個字節的整數值是105,它對應的字符是'i',以此類推。注意,每個文本行都以一個看不見的換行符'\\n'來結束,它所對應的整數值為10。 問題:中文用多少個字節存儲?中文與ASCII碼不同,數量遠遠大于ASCII碼,所以想要保存區別每一個中文字符就需要超過1字節更大的空間。 ##### 程序被其他程序翻譯成不同的格式 hello.c程序的生命周期是從一個高級C語言程序開始的,因為這種形式能夠被人讀懂。然而,為了在系統上運行hello.c程序,每條C語言都必須被其他程序轉化為一系列的`低級機器語言指令`。然后這些指令按照一種稱為可執行目標程序的格式打包,并以二進制磁盤文件的形式存放起來(持久化)。目標程序也稱為可執行目標文件。 在Unix系統上,從源文件到目標文件的轉化是由編譯器驅動程序完成的: ~~~ [root@VM-0-8-centos wwwroot]# gcc hello.c -o hello ~~~ 在這里,GCC編譯器驅動程序讀取源程序文件hello.c,并把它翻譯成一個可執行目標文件hello。這個翻譯過程可分為四個階段完成。執行這四個階段的程序(預處理器、編譯器、匯編器、鏈接器)一起構成了編譯系統。 :-: ![](https://img.kancloud.cn/56/84/56841590f7393c8787774e002f8f031a_758x132.png) 編譯系統 1. `預處理階段`:預處理器 (cpp)根據以字符#開頭的命令,修改原始的C程序。比如hello.c中第一行的#include命令告訴預處理器讀取系統頭文件stdio.h的內容,并把它直接插入程序文本中。結果就得到了另外一個C程序,通常是以.i作為文件擴展名。 2. `編譯階段`:編譯器(ccl)將文本文件hello.i翻譯成文本文件hello.s,它包含一個匯編語言程序。該程序包含函數main的定義,如下所示: ~~~ mian: subq ? %8, %rsp movl ? $.LCO, %edi call puts movl $0, %eax addq $8, %rsp ret //hello.i被編譯器處理成了hello.s匯編語言文件 ~~~ 定義中的2-7行的每條語句都以一種文本格式描述了一條低級機器語言指令。 匯編語言是非常有用的,因為它為不同的高級語言的不同編譯器提供了通用的輸出語句。例如,C編譯器和Fortran編譯器產生的輸出文件用的都是一樣的匯編語言。 3. `匯編階段`。匯編器將hello.s翻譯成機器語言指令,把這些指令打包成一種叫做`可重定位目標文件`的格式,并將結果保存在目標文件hello.o中。hello.o文件是一個二進制文件,它包含的17個字節是函數mian的指令編碼。如果我們在文本編輯器打開hello.o文件,將看到一對亂碼。 4. `鏈接階段`。請注意,hello程序調用了printf函數,它是每個C編譯器提供的標準C庫中的一個函數。printf函數存在一個名為printf.o的單獨的預編譯好的目標文件中,而這個文件必須以某種方式合并到我們的hello.o程序中。鏈接器(ld)就負責處理這種合并。結果就得到了hello文件,它是一個`可執行目標文件` ,可以被加載到內存,由系統執行。 ##### 了解編譯系統如何工作是大有益處的 對于像hello.c這樣簡單的程序,我們可以依靠編譯系統生成正確有效的機器代碼。但是有一些重要的原因促使程序員必須知道編譯系統是如何工作的。 1. `優化程序性能`:現代編譯器都是成熟的工具,通常可以生成很好的代碼。作為程序員,我么無需為了寫出高效代碼而去了解編譯器的內部工作。但是,為了在C程序中做出好的編碼選擇,我們確實需要了解一些機器代碼以及編碼器將不同的C語言轉化為機器代碼的方式。 比如: 一個switch語句是否總是比一系列的if-else語句高效得多?一個函數調用得開銷有多大?while循環比for循環更有效嗎?指針引用比數組索引更有效嗎?為什么將循環求和的結果放到一個本地變量中,會比將其放到一個通過引用傳遞過來的參數中,運行起來快得多呢?為什么我們只是簡單地重新排列以下算數表達式中地括號就能讓函數運行得更快? 2. `理解鏈接時出現的錯誤`。根據我們的經驗,一些最令人困擾i的程序錯誤往往都在與鏈接器操作有關,尤其是當你試圖構建大型的軟件系統時。比如鏈接器報告說它無法解析一個引用,這是什么意思?靜態變量和全局變量的區別是什么?如果你在不同的C文件中定義了名字相同的兩個全局變量會發生什么?靜態庫和動態庫的區別是什么?我們在命令行上排列的順序有什么影響?最嚴重的是,為什么有些鏈接知道運行時才出現。 3. `避免安全漏洞`:多年來,緩沖區溢出錯誤是造成大多數網絡和Internet服務器上安全漏洞的主要原因。存在這些錯誤是因為很少有程序員能夠理解需要限制從不受信的源接收數據的數量和格式。學習安全編程的第一步就是理解數據和控制信息存在程序上的方式會引起的后果。 ##### 高速緩存至關重要(硬件緩存) 系統總是花費大量的時間把信息從一個地方挪到另一個地方。hello程序的機器指令最初存放在磁盤,當程序加載時,他們被復制到主存;當處理器運行程序時,指令又從主存復制到處理器。相似的,數據串"hello world\\n"開始在磁盤上,然后被復制到主存,最后從主存上復制到顯示設備。從程序員的角度看,這些復制就是開銷,減慢了程序"真正"的工作。因此系統設計者的一個主要目標就是使這些復制操作盡可能快地完成。 根據機械原理,較大的存儲設備比較小的存儲設備運行得慢,而快速設備的造價遠高于同類的低速設備。比如說,一個典型的系統上的磁盤驅動器可能比主存大1000倍,但是對于處理器而言,從磁盤驅動器上讀取一個字的時間開銷要比從主存中讀取的開銷大1000萬倍。 類似的,一個寄存器文件只存儲幾百字節的信息,而主存里可存放幾十億字節。然而,處理器從寄存器文件中讀信息比從主存中取幾乎要快100倍。這些年,隨著半導體技術的進步,這種處理器與主存之間的差距還在持續增大。加快處理器的運行速度比加快主存的運行速度要容易和便宜得多。 針對這種處理器與主存之間的差異,系統設計者采用了更小更快的存儲設備,稱之為高速緩存存儲器。簡稱為cache或高速緩存,作為暫時的集結區域,存放處理器近期可能會需要的信息。 位于處理器芯片上的L1高速緩存的容量可以達到數萬字節,訪問速度幾乎和訪問寄存器文件一樣快。一個容量為數十萬到數百萬字節的更大的L2高速緩通過一條特殊的總線連接到處理器。進程訪問L2高速緩存的時間要比訪問L1高速緩存的時間長5倍,但仍然比訪問主存的時間快5-10倍。L1和L2高速緩存用的是一種叫做靜態隨機訪問存儲器的硬件技術實現的。比較新的、處理能力更強大的系統甚至有三級高速緩存:L1、L2和L3。系統可以獲得一個很大的存儲器,同時訪問速度也很快,原因是利用了高速緩存的局部性原理,即程序具有訪問局部區域里的數據和代碼的趨勢。通過讓高速緩存里存放可能經常訪問的數據,大部分的內存操作都能在快速的高速緩存中完成。 ##### 存儲設備層次結構 每個計算機系統的存儲設備都被組織成了一個存儲器層次結構。 存儲器層次結構的主要思想是上一層的存儲器作為低一層存儲器的高速緩存。 :-: ![](https://img.kancloud.cn/70/fe/70fe4b722b7713865da774cda290febc_801x501.png) ##### 操作系統管理硬件 當shell (稱之為殼子,命令解釋器)運行hello程序: ~~~ [root@VM-0-8-centos wwwroot]# ./hello hello, world [root@VM-0-8-centos wwwroot]# ~~~ 以及hello程序輸出自己的消息時,shell和hello程序都沒有直接訪問鍵盤、顯示器、磁盤或主存。取而代之的是,他們依靠`操作系統`提供的服務。我們可以把操作系統看成是應用程序和硬件之間插入的一層軟件。 :-: ![](https://img.kancloud.cn/11/87/11872395343ad7f1f8d2e14c584b520f_500x153.png) 計算機系統的分層視圖 操作系統有兩個基本功能:(1)防止硬件被失控的應用程序濫用;(2)向應用程序提供簡單一致的機制來控制復雜而又通常大不相同的低級硬件設備。 操作系統通過幾個抽象的基本概念(進程、虛擬內存、文件)來實現這兩個功能。文件是對I/O設備的抽象表示,虛擬內存是對主存和磁盤的I/O設備的抽象表示,進程則是處理器、主存和I/O設備的抽象表示。 :-: ![](https://img.kancloud.cn/22/1a/221ad59cc9573b3435f4422566a5af69_500x283.png) 操作系統提供的抽象表示 ##### 進程 程序運行再現代系統上時,操作系統會提供一種假象,就好像系統只有這個程序再運行。程序看上去是獨占地使用處理器、主存和I/O設備。處理器看上去就像不斷地一條接一條地執行程序中的指令,即該程序是計算機科學中最重要和成功的概念之一。 `進程`是操作系統對一個正在運行的程序的一種抽象。在一個系統上可以同時運行多個進程,而每個進程都好像在獨占地使用硬件。而`并發運行`,則是說一個進程的指令和另一個進程的指令是交錯執行的。在大多數系統中,需要運行的進程數是多于可以運行它們的CPU個數的。傳統系統在一個時刻只能執行一個程序,而先進的`多核處理器`同時能夠執行多個進程,這是通過處理器在進程間切換來實現的。操作系統實現這種交錯執行的機制稱為`上下文切換`。 上下文:操作系統保持跟蹤進程運行所需的所有狀態信息。包括許多信息,比如PC和寄存器文件的當前值,以及主存的內容、進程初始化時的環境變量、進程的虛擬內存空間、進程維護的數據結構、進程需要處理的事件隊列緩存等。在任何時候,單核處理器只能執行一個進程的代碼(多核在這一刻可以執行多個進程的代碼)。當系統決定把控制權從當前進程轉移到某個新進程的時候,就會進行`上下文切換`,即保存當前進程的上下文、恢復新進程的上下文,然后將控制權傳遞到新的進程。新進程就會從它上次停止的地方開始。 :-: ![](https://img.kancloud.cn/e7/f2/e7f2825c9cd3ab593650eb78c0772625_600x372.png) · 進程的上下文切換 ##### 線程 盡管通常我們認為一個進程只有單一的控制流,但是現代系統中,一個進程實際上可以由多個稱為線程的執行單元組成,每個線程都運行在進程的上下文中擁有自己的棧區,并共享同樣的代碼和全局數據(共享內存)。由于網絡服務器中對并行處理的需求,線程成為了越來越重要的模型,因為多線程之間比多進程之間更容易共享數據,也因為線程一般來說都比進程更高效。當由多處理器可用的時候,多線程也是一種使得程序可以運行得更快的方法。 ##### 線程模型 在現代計算機結構中,先后提出過兩種線程模型:用戶級線程(user-level threads)和內核級線程(kernel-level threads)。所謂用戶級線程是指,應用程序在操作系統提供的單個控制流的基礎上,通過在某些控制點(比如系統調用)上分離出一些虛擬的控制流,從而模擬多個控制流的行為。由于應用程序對指令流的控制能力相對較弱,所以,用戶級線程之間的切換往往受線程本身行為以及線程控制點選擇的影響,線程是否能公平地獲得處理器時間取決于這些線程的代碼特征。而且,支持用戶級線程的應用程序代碼很難做到跨平臺移植,以及對于多線程模型的透明。用戶級線程模型的優勢是線程切換效率高,因為它不涉及系統內核模式和用戶模式之間的切換;另一個好處是應用程序可以采用適合自己特點的線程選擇算法,可以根據應用程序的邏輯來定義線程的優先級,當線程數量很大時,這一優勢尤為明顯。但是,這同樣會增加應用程序代碼的復雜性。有一些軟件包(如 POSIXThreads 或 Pthreads 庫)可以減輕程序員的負擔。 內核級線程往往指操作系統提供的線程語義,由于操作系統對指令流有完全的控制能力,甚至可以通過硬件中斷來強迫一個進程或線程暫停執行,以便把處理器時間移交給其他的進程或線程,所以,內核級線程有可能應用各種算法來分配`處理器時間`。線程可以有優先級,高優先級的線程被優先執行,它們可以搶占正在執行的低優先級線程。在支持線程語義的操作系統中,處理器的時間通常是按線程而非進程來分配,因此,系統有必要維護一個全局的線程表,在線程表中記錄每個線程的寄存器、狀態以及其他一些信息。然后,系統在適當的時候掛起一個正在執行的線程,選擇一個新的線程在當前處理器上繼續執行。這里“適當的時候”可以有多種可能,比如:當一個線程執行某些系統調用時,例如像 sleep 這樣的放棄執行權的系統函數,或者像 wait 或 select 這樣的阻塞函數;硬中斷(interrupt)或異常(exception);線程終止時,等等。由于這些時間點的執行代碼可能分布在操作系統的不同位置,所以,在現代操作系統中,線程調度(thread scheduling)往往比較復雜,其代碼通常分布在內核模塊的各處。 `內核級線程的好處是,應用程序無須考慮是否要在適當的時候把控制權交給其他的線程,不必擔心自己霸占處理器而導致其他線程得不到處理器時間`。應用線程只要按照正常的指令流來實現自己的邏輯即可,內核會妥善地處理好線程之間共享處理器的資源分配問題。然而,這種對應用程序的便利也是有代價的,`對于在用戶模式下運行的線程來說,一個線程被切換出去,以及下次輪到它的時候再被切換進來,要涉及兩次模式切換:從用戶模式切換到內核模式,再從內核模式切換回用戶模式,此時用戶線程提供了表面抽象的邏輯控制流,實際上是內核線程分配處理器時間`(內核模式對CPU、硬件有絕對的操作權,運行在內核模式的指令有極致的速度,但是內核模式下程序異常是致命的;用戶模式對非權限的指令需要切換到內核模式運行,用如果用戶模式下的程序拋出異常程序會崩潰,但是系統不會崩潰,所以用戶模式運行保證了操作系統穩定)。在 Intel 的處理器上,這種模式切換大致需要幾百個甚至上千個處理器指令周期。但是,隨著處理器的硬件速度不斷加快,模式切換的開銷相對于現代操作系統的線程調度周期(通常幾十毫秒)的比例正在減小,所以,這部分開銷是完全可以接受的。 除了線程切換的開銷是一個考慮因素以外,線程的創建和刪除也是一個重要的考慮指標。當線程的數量較多時,這部分開銷是相當可觀的。雖然線程的創建和刪除比起進程要輕量得多,但是,在一個進程內建立起一個線程的執行環境,例如,分配線程本身的數據結構和它的調用棧,完成這些數據結構的初始化工作,以及完成與系統環境相關的一些初始化工作,這些負擔是不可避免的。另外,當線程數量較多時,伴隨而來的線程切換開銷也必然隨之增加。所以,當應用程序或系統進程需要的線程數量可能比較多時,通常可采用線程池技術作為一種優化措施,以降低創建和刪除線程以及線程頻繁切換而帶來的開銷。 在支持內核級線程的系統環境中,進程可以容納多個線程,這導致了多線程程序設計(multithreaded programming)模型。由于多個線程在同一個進程環境中,它們共享了幾乎所有的資源,所以,線程之間的通信要方便和高效得多,這往往是進程間通信(IPC,Inter-Process Communication)所無法比擬的,但是,這種便利性也很容易使線程之間因同步不正確而導致數據被破壞,而且,這種錯誤存在不確定性,因而相對來說難以發現和調試。 **線程模型小節引用author:快樂成長** ##### 虛擬內存 `虛擬內存`是一個抽象的概念,它為每個進程提供一個假象,即每個進程都獨占地使用主存。每個進程看到的內存都是一致的,稱為虛擬地址空間。在Linux中,地址空間最上面的區域是保留給操作系統中的代碼和數據的,這對所有進程來說都是一樣。地址空間的底部區域存放用戶進程定義的代碼和數據。 :-: ![](https://img.kancloud.cn/cc/4a/cc4a3982a9ed8565b25731eba4abcf47_677x602.png) 每個進程看到的虛擬地址空間由大量準確定義的區構成,每個區都有專門的功能。 1. `程序代碼和數據`: 對所有的進程來說,代碼是從同一固定地址開始,緊接著的是和C全局變量相對應的數據位置。代碼和數據區是直接按照可執行目標文件的內容初始化的。 2. `堆`:代碼和數據區后面緊隨著的是運行時堆。代碼和數據區在進程一開始運行時就被指定了大小,與此不同,當調用像malloc和free這樣的C標準庫函數時,堆可以在運行時動態擴展和收縮。 3. `共享庫`:大約在地址空間的中間部分是一塊用來存放像C標準庫和數據庫這樣的共享庫的代碼和數據的區域。共享庫的概念非常強大,也相當難懂。 4. `棧`:位于用戶虛擬地址空間頂部的是`用戶棧`,編譯器用它來實現函數調用。和堆一樣,用戶棧在程序執行期間可以動態地擴展和收縮。特別地,每當我們調用一個函數時,棧就會增長;從一個函數返回時,棧九會收縮。 5. `內核虛擬內存`:地址空間頂部的區域是為內核保留的。不允許應用程序讀寫這個區域的內容或者直接調用內核代碼定義的函數。相反它們必須調用內核來執行這些操作。 ##### 文件 文件就是字節序列,僅此而已。每個I/O設備,包括磁盤、鍵盤、顯示器,甚至網絡,都可以看成文件。系統中的所有輸入輸出都是通過使用一組稱為Unix I/O的系統函數調用讀寫文件來實現的。 文件這個簡單而精致的概念是非常強大的,因為它向應用程序提供了一個統一的視圖,來看待系統中可能含有的所有各式各樣的 I/O設備。例如:同一個程序可以在不同磁盤技術的不同系統上運行。 ##### 并發與并行 `并發`:同一時具有多個活動的系統。 `并行`:用并行來使一個系統運行得更快。 1. 線程級并發 構建在進程的抽象之上,我們能夠設計出同時有多個程序執行的系統,這就導致了并發。使用線程,我們甚至能在一個進程中執行多個控制流(同一刻單核系統只能執行一個控制流)。不過這種并發只是模擬出來,是通過使一臺計算機在它正在執行的進程間快速切換來實現的。在以前,即使處理器必須在多個任務之間切換,大多數實際的計算也都是由一個處理器來完成的。這種配置稱為`單處理器系統`。 當構建一個由單操作系統內核控制的多處理器組成的系統時,我們就得到一個`多處理器系統`。 隨著`多核處理器`和`超線程`的出現,大規模計算系統變得常見。 `多核處理器`是將多個CPU(核)集成到一個集成電路芯片上。 `超線程`,有時稱`同時多線程`,是一項允許一個CPU執行多個控制流的技術(一個進程里的多個線程系統同時執行)。 2. 指令級并行 在較低的抽象層次上,現代處理器可以同時執行多條指令的屬性稱為指令級并行。 3. 單指令、多數據并行 在低層次上,許多現代處理器擁有特殊的硬件,允許一條指令產生多個可以并行執行的操作,這種方式稱為單指令、多數據。 #### I/O是什么 I/O是在主存和外部設備(例如磁盤驅動器、終端核網絡)之間復制數據的過程。 ##### 協程 什么是協同式和搶占式? 許多協同式多任務操作系統,也可以看成協程運行系統。說到協同式多任務系統,一個常見的誤區是認為協同式調度比搶占式調度“低級”,因為我們所熟悉的桌面操作系統,都是從協同式調度(如 Windows 3.2, Mac OS 9 等)過渡到搶占式多任務系統的。實際上,調度方式并無高下,完全取決于應用場景。搶占式系統允許操作系統剝奪進程執行權限,搶占控制流,因而天然適合服務器和圖形操作系統,因為調度器可以優先保證對用戶交互和網絡事件的快速響應(打開某程序時立刻進行搶占)。當年 Windows 95 剛剛推出的時候,搶占式多任務就被作為一大買點大加宣傳。協同式調度則等到進程時間片用完或系統調用時轉移執行權限,因此適合實時或分時等等對運行時間有保障的系統。 另外,搶占式系統依賴于 CPU 的硬件支持。 因為調度器需要“剝奪”進程的執行權,就意味著調度器需要運行在比普通進程高的權限上,否則任何“流氓(rogue)”進程都可以去剝奪其他進程了。只有 CPU 支持了執行權限后,搶占式調度才成為可能。x86 系統從 80386 處理器開始引入 Ring 機制支持執行權限,這也是為何 Windows 95 和 Linux 其實只能運行在 80386 之后的 x86 處理器上的原因。而協同式多任務適用于那些沒有處理器權限支持的場景,這些場景包含資源受限的嵌入式系統和實時系統。在這些系統中,程序均以協程的方式運行。調度器負責控制流的讓出和恢復。通過協程的模型,無需硬件支持,我們就可以在一個“簡陋”的處理器上實現一個多任務的系統。我們見到的許多智能設備,如運動手環,基于硬件限制,都是采用協同調度的架構。 協程基本概念 “協程”(Coroutine)概念最早由 Melvin Conway 于 1958 年提出。協程可以理解為純用戶態的線程,其通過協作而不是搶占來進行切換。相對于進程或者線程,協程所有的操作都可以在用戶態完成,創建和切換的消耗更低。總的來說,協程為協同任務提供了一種運行時抽象,這種抽象非常適合于協同多任務調度和數據流處理。在現代操作系統和編程語言中,因為用戶態線程切換代價比內核態線程小,協程成為了一種輕量級的多任務模型。 從編程角度上看,協程的思想本質上就是控制流的主動讓出(yield)和恢復(resume)機制,迭代器常被用來實現協程,所以大部分的語言實現的協程中都有 yield 關鍵字,比如 Python、PHP、Lua。但也有特殊比如 Go 就使用的是通道來通信。 協程的歷史其實要早于線程。 **本小節引用author:快樂成長** ##### 進程、線程、協程的特點及區別: 進程(process) * 進程是資源分配的最小單位 * 進程間不共享內存,每個進程擁有自己獨立的內存 * 進程間可以通過信號、信號量、共享內存、管道、隊列等來通信 * 新開進程開銷大,并且 CPU 切換進程成本也大 * 進程由操作系統調度 * 多進程方式比多線程更加穩定 線程(thread) * 線程是程序執行流的最小單位 * 線程是來自于進程的,一個進程下面可以創建多個線程 * 每個線程都有自己一個棧,不共享棧,但多個線程能共享同一個屬于進程的堆 * 線程因為是在同一個進程內的,可以共享內存 * 線程也是由操作系統調度,線程是 CPU 調度的最小單位 * 新開線程開銷小于進程,CPU 在切換線程成本也小于進程 * 某個線程發生致命錯誤會導致整個進程崩潰 * 線程間讀寫變量存在鎖的問題處理起來相對麻煩 協程(coroutine) * 對于操作系統來說只有進程和線程,協程的控制由應用程序顯式調度,非搶占式的 * 協程的執行最終靠的還是線程,應用程序來調度協程選擇合適的線程來獲取執行權 * 切換非常快,成本低。一般占用棧大小遠小于線程(協程 KB 級別,線程 MB 級別),所以可以開更多的協程 * 協程比線程更輕量級 **本小節引用author:快樂成長**
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看