• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            posts - 200, comments - 8, trackbacks - 0, articles - 0

            當 Linux 最初開發時,在內核中并不能真正支持線程。但是它的確可以通過 clone() 系統調用將進程作為可調度的實體。這個調用創建了調用進程(calling process)的一個拷貝,這個拷貝與調用進程共享相同的地址空間。LinuxThreads 項目使用這個調用來完全在用戶空間模擬對線程的支持。不幸的是,這種方法有一些缺點,尤其是在信號處理、調度和進程間同步原語方面都存在問題。另外,這個 線程模型也不符合 POSIX 的要求。

            要改進 LinuxThreads,非常明顯我們需要內核的支持,并且需要重寫線程庫。有兩個相互競爭的項目開始來滿足這些要求。一個包括 IBM 的開發人員的團隊開展了 NGPT(Next-Generation POSIX Threads)項目。同時,Red Hat 的一些開發人員開展了 NPTL 項目。NGPT 在 2003 年中期被放棄了,把這個領域完全留給了 NPTL。

            盡管從 LinuxThreads 到 NPTL 看起來似乎是一個必然的過程,但是如果您正在為一個歷史悠久的 Linux 發行版維護一些應用程序,并且計劃很快就要進行升級,那么如何遷移到 NPTL 上就會變成整個移植過程中重要的一個部分。另外,我們可能會希望了解二者之間的區別,這樣就可以對自己的應用程序進行設計,使其能夠更好地利用這兩種技 術。

            本文詳細介紹了這些線程模型分別是在哪些發行版上實現的。

            LinuxThreads 設計細節

            線程 將應用程序劃分成一個或多個同時運行的任務。線程與傳統的多任務進程 之間的區別在于:線程共享的是單個進程的狀態信息,并會直接共享內存和其他資源。同一個進程中線程之間的上下文切換通常要比進程之間的上下文切換速度更 快。因此,多線程程序的優點就是它可以比多進程應用程序的執行速度更快。另外,使用線程我們可以實現并行處理。這些相對于基于進程的方法所具有的優點推動 了 LinuxThreads 的實現。

            LinuxThreads 最初的設計相信相關進程之間的上下文切換速度很快,因此每個內核線程足以處理很多相關的用戶級線程。這就導致了一對一 線程模型的革命。

            讓我們來回顧一下 LinuxThreads 設計細節的一些基本理念:

            • LinuxThreads 非常出名的一個特性就是管理線程(manager thread)。管理線程可以滿足以下要求:

              • 系統必須能夠響應終止信號并殺死整個進程。
              • 以堆棧形式使用的內存回收必須在線程完成之后進行。因此,線程無法自行完成這個過程。
              • 終止線程必須進行等待,這樣它們才不會進入僵尸狀態。
              • 線程本地數據的回收需要對所有線程進行遍歷;這必須由管理線程來進行。
              • 如果主線程需要調用 pthread_exit(),那么這個線程就無法結束。主線程要進入睡眠狀態,而管理線程的工作就是在所有線程都被殺死之后來喚醒這個主線程。

            • 為了維護線程本地數據和內存,LinuxThreads 使用了進程地址空間的高位內存(就在堆棧地址之下)。

            • 原語的同步是使用信號 來實現的。例如,線程會一直阻塞,直到被信號喚醒為止。

            • 在克隆系統的最初設計之下,LinuxThreads 將每個線程都是作為一個具有惟一進程 ID 的進程實現的。

            • 終止信號可以殺死所有的線程。LinuxThreads 接收到終止信號之后,管理線程就會使用相同的信號殺死所有其他線程(進程)。

            • 根據 LinuxThreads 的設計,如果一個異步信號被發送了,那么管理線程就會將這個信號發送給一個線程。如果這個線程現在阻塞了這個信號,那么這個信號也就會被掛起。這是因為管理線程無法將這個信號發送給進程;相反,每個線程都是作為一個進程在執行。

            • 線程之間的調度是由內核調度器來處理的。

            LinuxThreads 及其局限性

            LinuxThreads 的設計通常都可以很好地工作;但是在壓力很大的應用程序中,它的性能、可伸縮性和可用性都會存在問題。下面讓我們來看一下 LinuxThreads 設計的一些局限性:

            • 它使用管理線程來創建線程,并對每個進程所擁有的所有線程進行協調。這增加了創建和銷毀線程所需要的開銷。

            • 由于它是圍繞一個管理線程來設計的,因此會導致很多的上下文切換的開銷,這可能會妨礙系統的可伸縮性和性能。

            • 由于管理線程只能在一個 CPU 上運行,因此所執行的同步操作在 SMP 或 NUMA 系統上可能會產生可伸縮性的問題。

            • 由于線程的管理方式,以及每個線程都使用了一個不同的進程 ID,因此 LinuxThreads 與其他與 POSIX 相關的線程庫并不兼容。

            • 信號用來實現同步原語,這會影響操作的響應時間。另外,將信號發送到主進程的概念也并不存在。因此,這并不遵守 POSIX 中處理信號的方法。

            • LinuxThreads 中對信號的處理是按照每線程的原則建立的,而不是按照每進程的原則建立的,這是因為每個線程都有一個獨立的進程 ID。由于信號被發送給了一個專用的線程,因此信號是串行化的 —— 也就是說,信號是透過這個線程再傳遞給其他線程的。這與 POSIX 標準對線程進行并行處理的要求形成了鮮明的對比。例如,在 LinuxThreads 中,通過 kill() 所發送的信號被傳遞到一些單獨的線程,而不是集中整體進行處理。這意味著如果有線程阻塞了這個信號,那么 LinuxThreads 就只能對這個線程進行排隊,并在線程開放這個信號時在執行處理,而不是像其他沒有阻塞信號的線程中一樣立即處理這個信號。

            • 由于 LinuxThreads 中的每個線程都是一個進程,因此用戶和組 ID 的信息可能對單個進程中的所有線程來說都不是通用的。例如,一個多線程的 setuid()/setgid() 進程對于不同的線程來說可能都是不同的。

            • 有一些情況下,所創建的多線程核心轉儲中并沒有包含所有的線程信息。同樣,這種行為也是每個線程都是一個進程這個事實所導致的結果。如果任何線程 發生了問題,我們在系統的核心文件中只能看到這個線程的信息。不過,這種行為主要適用于早期版本的 LinuxThreads 實現。

            • 由于每個線程都是一個單獨的進程,因此 /proc 目錄中會充滿眾多的進程項,而這實際上應該是線程。

            • 由于每個線程都是一個進程,因此對每個應用程序只能創建有限數目的線程。例如,在 IA32 系統上,可用進程總數 —— 也就是可以創建的線程總數 —— 是 4,090。

            • 由于計算線程本地數據的方法是基于堆棧地址的位置的,因此對于這些數據的訪問速度都很慢。另外一個缺點是用戶無法可信地指定堆棧的大小,因為用戶可能會意外地將堆棧地址映射到本來要為其他目的所使用的區域上了。按需增長(grow on demand) 的概念(也稱為浮動堆棧 的概念)是在 2.4.10 版本的 Linux 內核中實現的。在此之前,LinuxThreads 使用的是固定堆棧。

            關于 NPTL

            NPTL,或稱為 Native POSIX Thread Library,是 Linux 線程的一個新實現,它克服了 LinuxThreads 的缺點,同時也符合 POSIX 的需求。與 LinuxThreads 相比,它在性能和穩定性方面都提供了重大的改進。與 LinuxThreads 一樣,NPTL 也實現了一對一的模型。

            Ulrich Drepper 和 Ingo Molnar 是 Red Hat 參與 NPTL 設計的兩名員工。他們的總體設計目標如下:

            • 這個新線程庫應該兼容 POSIX 標準。

            • 這個線程實現應該在具有很多處理器的系統上也能很好地工作。

            • 為一小段任務創建新線程應該具有很低的啟動成本。

            • NPTL 線程庫應該與 LinuxThreads 是二進制兼容的。注意,為此我們可以使用 LD_ASSUME_KERNEL,這會在本文稍后進行討論。

            • 這個新線程庫應該可以利用 NUMA 支持的優點。

            NPTL 的優點

            與 LinuxThreads 相比,NPTL 具有很多優點:

            • NPTL 沒有使用管理線程。管理線程的一些需求,例如向作為進程一部分的所有線程發送終止信號,是并不需要的;因為內核本身就可以實現這些功能。內核還會處理每個 線程堆棧所使用的內存的回收工作。它甚至還通過在清除父線程之前進行等待,從而實現對所有線程結束的管理,這樣可以避免僵尸進程的問題。

            • 由于 NPTL 沒有使用管理線程,因此其線程模型在 NUMA 和 SMP 系統上具有更好的可伸縮性和同步機制。

            • 使用 NPTL 線程庫與新內核實現,就可以避免使用信號來對線程進行同步了。為了這個目的,NPTL 引入了一種名為futex 的新機制。futex 在共享內存區域上進行工作,因此可以在進程之間進行共享,這樣就可以提供進程間 POSIX 同步機制。我們也可以在進程之間共享一個 futex。這種行為使得進程間同步成為可能。實際上,NPTL 包含了一個 PTHREAD_PROCESS_SHARED 宏,使得開發人員可以讓用戶級進程在不同進程的線程之間共享互斥鎖。

            • 由于 NPTL 是 POSIX 兼容的,因此它對信號的處理是按照每進程的原則進行的;getpid() 會為所有的線程返回相同的進程 ID。例如,如果發送了 SIGSTOP 信號,那么整個進程都會停止;使用 LinuxThreads,只有接收到這個信號的線程才會停止。這樣可以在基于 NPTL 的應用程序上更好地利用調試器,例如 GDB。

            • 由于在 NPTL 中所有線程都具有一個父進程,因此對父進程匯報的資源使用情況(例如 CPU 和內存百分比)都是對整個進程進行統計的,而不是對一個線程進行統計的。

            • NPTL 線程庫所引入的一個實現特性是對 ABI(應用程序二進制接口)的支持。這幫助實現了與 LinuxThreads 的向后兼容性。這個特性是通過使用 LD_ASSUME_KERNEL 實現的,下面就來介紹這個特性。

            LD_ASSUME_KERNEL 環境變量

            正如上面介紹的一樣,ABI 的引入使得可以同時支持 NPTL 和 LinuxThreads 模型。基本上來說,這是通過 ld (一個動態鏈接器/加載器)來進行處理的,它會決定動態鏈接到哪個運行時線程庫上。

            舉例來說,下面是 WebSphere® Application Server 對這個變量所使用的一些通用設置;您可以根據自己的需要進行適當的設置:

            • LD_ASSUME_KERNEL=2.4.19:這會覆蓋 NPTL 的實現。這種實現通常都表示使用標準的 LinuxThreads 模型,并啟用浮動堆棧的特性。
            • LD_ASSUME_KERNEL=2.2.5:這會覆蓋 NPTL 的實現。這種實現通常都表示使用 LinuxThreads 模型,同時使用固定堆棧大小。

            我們可以使用下面的命令來設置這個變量:

            export LD_ASSUME_KERNEL=2.4.19

            注意,對于任何 LD_ASSUME_KERNEL 設置的支持都取決于目前所支持的線程庫的 ABI 版本。例如,如果線程庫并不支持 2.2.5 版本的 ABI,那么用戶就不能將 LD_ASSUME_KERNEL 設置為 2.2.5。通常,NPTL 需要 2.4.20,而 LinuxThreads 則需要 2.4.1。

            如果您正運行的是一個啟用了 NPTL 的 Linux 發行版,但是應用程序卻是基于 LinuxThreads 模型來設計的,那么所有這些設置通常都可以使用。

            GNU_LIBPTHREAD_VERSION 宏

            大部分現代 Linux 發行版都預裝了 LinuxThreads 和 NPTL,因此它們提供了一種機制來在二者之間進行切換。要查看您的系統上正在使用的是哪個線程庫,請運行下面的命令:

            $ getconf GNU_LIBPTHREAD_VERSION

            這會產生類似于下面的輸出結果:

            NPTL 0.34

            或者:

            linuxthreads-0.10

            Linux 發行版所使用的線程模型、glibc 版本和內核版本

            表 1 列出了一些流行的 Linux 發行版,以及它們所采用的線程實現的類型、glibc 庫和內核版本。

            表 1. Linux 發行版及其線程實現
            線程實現C 庫發行版內核
            LinuxThreads 0.7, 0.71 (for libc5)libc 5.xRed Hat 4.2
            LinuxThreads 0.7, 0.71 (for glibc 2)glibc 2.0.xRed Hat 5.x
            LinuxThreads 0.8glibc 2.1.1Red Hat 6.0
            LinuxThreads 0.8glibc 2.1.2Red Hat 6.1 and 6.2
            LinuxThreads 0.9
            Red Hat 7.22.4.7
            LinuxThreads 0.9glibc 2.2.4Red Hat 2.1 AS2.4.9
            LinuxThreads 0.10glibc 2.2.93Red Hat 8.02.4.18
            NPTL 0.6glibc 2.3Red Hat 9.02.4.20
            NPTL 0.61glibc 2.3.2Red Hat 3.0 EL2.4.21
            NPTL 2.3.4glibc 2.3.4Red Hat 4.02.6.9
            LinuxThreads 0.9glibc 2.2SUSE Linux Enterprise Server 7.12.4.18
            LinuxThreads 0.9glibc 2.2.5SUSE Linux Enterprise Server 82.4.21
            LinuxThreads 0.9glibc 2.2.5United Linux2.4.21
            NPTL 2.3.5glibc 2.3.3SUSE Linux Enterprise Server 92.6.5

            注意,從 2.6.x 版本的內核和 glibc 2.3.3 開始,NPTL 所采用的版本號命名約定發生了變化:這個庫現在是根據所使用的 glibc 的版本進行編號的。

            Java™ 虛擬機(JVM)的支持可能會稍有不同。IBM 的 JVM 可以支持表 1 中 glibc 版本高于 2.1 的大部分發行版。

            結束語

            LinuxThreads 的限制已經在 NPTL 以及 LinuxThreads 后期的一些版本中得到了克服。例如,最新的 LinuxThreads 實現使用了線程注冊來定位線程本地數據;例如在 Intel® 處理器上,它就使用了 %fs 和 %gs 段寄存器來定位訪問線程本地數據所使用的虛擬地址。盡管這個結果展示了 LinuxThreads 所采納的一些修改的改進結果,但是它在更高負載和壓力測試中,依然存在很多問題,因為它過分地依賴于一個管理線程,使用它來進行信號處理等操作。

            您應該記住,在使用 LinuxThreads 構建庫時,需要使用 -D_REENTRANT 編譯時標志。這使得庫線程是安全的。

            最后,也許是最重要的事情,請記住 LinuxThreads 項目的創建者已經不再積極更新它了,他們認為 NPTL 會取代 LinuxThreads。

            LinuxThreads 的缺點并不意味著 NPTL 就沒有錯誤。作為一個面向 SMP 的設計,NPTL 也有一些缺點。我曾經看到過在最近的 Red Hat 內核上出現過這樣的問題:一個簡單線程在單處理器的機器上運行良好,但在 SMP 機器上卻掛起了。我相信在 Linux 上還有更多工作要做才能使它具有更好的可伸縮性,從而滿足高端應用程序的需求。


            參考資料

            學習

            • 您可以參閱本文在 developerWorks 全球站點上的 英文原文 。

            • Ulrich Drepper 和 Ingo Molnar 編寫的 “The Native POSIX Thread Library for Linux”(PDF)介紹了設計 NPTL 的原因和目標,其中包括了 LinuxThreads 的缺點和 NPTL 的優點。 

            • LinuxThreads FAQ 包含了有關 LinuxThreads 和 NPTL 的常見問題。這對于了解早期的 LinuxThreads 實現的缺點來說是一個很好的資源。 

            • Ulrich Drepper 撰寫的 “Explaining LD_ASSUME_KERNEL” 提供了有關這個環境變量的詳細介紹。 

            • “Native POSIX Threading Library (NPTL) support” 從 WebSphere 的視角介紹了 LinuxThreads 和 NPTL 之間的區別,并解釋了 WebSphere Application Server 如何支持這兩種不同的線程模型。 

            • Diagnosis documentation for IBM ports of the JVM 定義了 Java 應用程序在 Linux 上運行時面臨問題時所要搜集的診斷信息。 

            • 在 developerWorks Linux 專區 中可以找到為 Linux 開發人員準備的更多資源。 

            • 隨時關注 developerWorks 技術事件和網絡廣播。 

            獲得產品和技術

            • LinuxThreads README 對 LinuxThreads 概要進行了介紹。 

            • 在您的下一個開發項目中采用 IBM 試用軟件,這可以從 developerWorks 上直接下載。 

            討論

            • 通過參與 developerWorks blogs 加入 developerWorks 社區。 

            關于作者

            Vikram Shukla 具有 6 年使用面向對象語言進行開發和設計的經驗,目前是位于印度 Banglore 的 IBM Java Technology Center 的一名資深軟件工程師,負責對 IBM JVM on Linux 進行支持。

            轉自:http://blog.chinaunix.net/uid-20556054-id-3068081.html

            posted @ 2012-12-18 13:52 鑫龍 閱讀(431) | 評論 (0)編輯 收藏

            Native POSIX Thread Library

            維基百科,自由的百科全書

            Native POSIX Thread LibraryNPTL)是一個能夠使使用POSIX Threads編寫的程序在Linux內核上更有效地運行的軟件。

            測試表明,NPTL能夠成功地在IA-32平臺上在兩秒種內生成100,000個線程;相應的沒有NPTL的內核將耗費15分鐘左右。

            歷史

            Linux內核2.6出現之前進程是(最小)可調度的對象,當時的Linux不真正支持線程。但是Linux內核有一個系統調用指令clone(),這個指令產生一個呼叫調用的進程的復件,而且這個復件與原進程使用同一地址空間。LinuxThreads計劃使用這個系統調用來提供一個內核級的線程支持。但是這個解決方法與真正的POSIX標準有一些不相容的地方,尤其是在信號處理、進程調度和進程間同步原語方面。

            要提高LinuxThreads的效應很明顯需要提供內核支持以及必須重寫線程函式庫。為了解決這個問題出現了兩個互相競爭的項目:一個IBM的組的項目叫做NGPTNext Generation POSIX Threads,下一代POSIX線程),另一個組是由Red Hat程序員組成的。2003年中NGPT被放棄,幾乎與此同時NPTL公布了。

            NPTL首次是隨Red Hat Linux 9發表的。此前老式的Linux POSIX線程偶爾會發生系統無法產生線程的毛病,這個毛病的原因是因為在新線程開始的時候系統沒有借機先占。當時的Windows系統對這個問題的解決比較好。Red Hat在關于Red Hat Linux 9上的Java的網頁上發表了一篇文章稱NPTL解決了這個問題。

            從第3版開始NPTLRed Hat Enterprise Linux的一部分,從Linux內核2.6開始它被納入內核。目前它完全被結合入GNU C 函式庫。

            設計

            NPTL的解決方法與LinuxThreads類似,內核看到的首要抽象依然是一個進程,新線程是通過clone()系統調用產生的。但是NPTL需要特殊的內核支持來解決同步的原始類型之間互相競爭的狀況。在這種情況下線程必須能夠入眠和再復蘇。用來完成這個任務的原始類型叫做futex

            NPTL是一個所謂的1×1線程函式庫。用戶產生的線程與內核能夠分配的物件之間的聯系是一對一的。這是所有線程程式中最簡單的。

            轉自http://zh.wikipedia.org/wiki/Native_POSIX_Thread_Library#cite_note-1

            轉自:http://blog.chinaunix.net/uid-20556054-id-3068071.html

            posted @ 2012-12-18 13:50 鑫龍 閱讀(489) | 評論 (0)編輯 收藏

                 摘要: (轉自http://programmerdigest.cn/2010/08/1096.html,其中的實驗數據重新做過測試,在語言上也有所修改)在Unix上編程采用多線程還是多進程的爭執由來已久,這種爭執最常見到在B/S通訊中服務端并發技術 的選型上,比如WEB服務器技術中,Apache是采用多進程的(perfork模式,每客戶連接對應一個進程,每進程中只存在唯一一個執行線 程),Java的Web...  閱讀全文

            posted @ 2012-12-18 13:49 鑫龍 閱讀(650) | 評論 (0)編輯 收藏

            關于多進程和多線程,教科書上最經典的一句話是“進程是資源分配的最小單位,線程是CPU調度的最小單位”。這句話應付考試基本上夠了,但如果在工作中遇到類似的選擇問題,那就沒有那么簡單了,選的不好,會讓你深受其害。
            經常在網絡上看到有XDJM問“多進程好還是多線程好?”、"Linux下用多進程還是多線程?"等等期望一勞永逸的問題,只能說:沒有最好,只有更好,根據實際情況來判斷,哪個更加合適就是哪個好。
            我們按照多個不同的維度,來看看多進程和多線程的對比(注:因為是感性的比較,因此都是相對的,不是說一個好得不得了,另一個差的無法忍受)

            維度

            多進程

            多線程

            總結

            數據共享、同步

            數據是分開的:共享復雜,需要用IPC;同步簡單

            多線程共享進程數據:共享簡單;同步復雜

            各有優勢

            內存、CPU

            占用內存多,切換復雜,CPU利用率低

            占用內存少,切換簡單,CPU利用率高

            線程占優

            創建銷毀、切換

            創建銷毀、切換復雜,速度慢 

            創建銷毀、切換簡單,速度快 

            線程占優 

            編程調試

            編程簡單,調試簡單

            編程復雜,調試復雜

            進程占優 

            可靠性

            進程間不會相互影響 

            一個線程掛掉將導致整個進程掛掉

            進程占優

            分布式 

            適應于多核、多機分布 ;如果一臺機器不夠,擴展到多臺機器比較簡單

            適應于多核分布

            進程占優



            1)需要頻繁創建銷毀的優先用線程。
               實例:web服務器。來一個建立一個線程,斷了就銷毀線程。要是用進程,創建和銷毀的代價是很難承受的。
            2)需要進行大量計算的優先使用線程。
               所謂大量計算,當然就是要消耗很多cpu,切換頻繁了,這種情況先線程是最合適的。
               實例:圖像處理、算法處理
            3)強相關的處理用線程,若相關的處理用進程。
               什么叫強相關、弱相關?理論上很難定義,給個簡單的例子就明白了。
               一般的server需要完成如下任務:消息收發和消息處理。消息收發和消息處理就是弱相關的任務,而消息處理里面可能又分為消息解碼、業務處理,這兩個任務相對來說相關性就要強多了。因此消息收發和消息處理可以分進程設計,消息解碼和業務處理可以分線程設計。
            4)可能擴展到多機分布的用進程,多核分布的用線程。
            5)都滿足需求的情況下,用你最熟悉、最拿手的方式。
               至于”數據共享、同步“、“編程、調試”、“可靠性”這幾個維度的所謂的“復雜、簡單”應該怎么取舍,只能說:沒有明確的選擇方法。一般有一個選擇原則:如果多進程和多線程都能夠滿足要求,那么選擇你最熟悉、最拿手的那個。
            需要提醒的是:雖然有這么多的選擇原則,但實際應用中都是“進程+線程”的結合方式,千萬不要真的陷入一種非此即彼的誤區。

            轉自:http://software.intel.com/zh-cn/blogs/2010/07/20/400004478/ 

            轉自:http://blog.chinaunix.net/uid-20556054-id-3061450.html

            posted @ 2012-12-18 13:47 鑫龍 閱讀(315) | 評論 (0)編輯 收藏

            進程(英語:Process,中國大陸譯作進程,臺灣譯作行程) 是具有一定獨立功能的程序關于某個數據集合上的一次運行活動,是系統進行資源分配和調度的一個獨立單位。
            程序是一組指令的有序集合,它本身沒有任何運行的含義,只是一個靜態實體。進程是程序在某個數據集上的執行,是一個動態實體(進程本身不會運行,是線程的容器。)。它因創建而產生,因調度而運行,因等待資源或事件而被處于等待狀態,因完成任務而被撤消,反映了一個程序在一定的數據集上運行的全部動態過程。
            若干進程有可能與同一個程序相關系,且每個進程皆可以同步(循序)或不同步(平行)的方式獨立運行。進程為現今分時系統的基本運作單位。


            線程
            (英語:thread,臺灣譯為運行緒),操作系統技術中的術語,是操作系統能夠進行運算調度的最小單位。它被包涵在進程之中,一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發多個線程,每條線程并行執行不同的任務。在Unix System V及SunOS中也被稱為輕量進程(lightweight processes),但輕量進程更多指內核線程(kernel thread),而把用戶線程(user thread)稱為線程。

            線程是獨立調度和分派的基本單位。線程可以操作系統內核調度的內核線程,如Win32 線程;由用戶進程自行調度的用戶線程,如Linux Portable Thread; 或者由內核與用戶進程,如Windows 7的線程,進行混合調度。

            同一進程中的多條線程將共享該進程中的全部系統資源,如虛擬地址空間,文件描述符和信號處理等等。但同一進程中的多個線程有各自的調用棧(call stack),自己的寄存器環境(register context),自己的線程本地存儲(thread-local storage)。

            一個進程可以有很多線程,每條線程并行執行不同的任務。

            在多核或多CPU,或支持Hyper-threading的CPU上使用多線程程序設計的好處是顯而易見,即提高了程序的執行吞吐率。在單CPU單核的計算機上,使用多線程技術,也可以把進程中負責IO處理、人機交互而常備阻塞的部分與密集計算的部分分開來執行,編寫專門的workhorse線程執行密集計算,從而提高了程序的執行效率。(摘自:http://zh.wikipedia.org/wiki/線程)


            進程是資源分配的最小單位,線程是CPU調度的最小單位。線程和進程的區別在于,子進程和父進程有不同的代碼和數據空間,而多個線程則共享數據空間,每個線程有自己的執行堆棧和程序計數器為其執行上下文.多線程主要是為了節約CPU時間,發揮利用,根據具體情況而定. 線程的運行中需要使用計算機的內存資源和CPU。


            多進程: 進程是程序在計算機上的一次執行活動。當你運行一個程序,你就啟動了一個進程。顯然,程序是死的(靜態的),進程是活的(動態的)。進程可以分為系統進程和用戶進程。凡是用于完成操作系統的各種功能的進程就是系統進程,它們就是處于運行狀態下的操作系統本身;所有由用戶啟動的進程都是用戶進程。進程是操作系統進行資源分配的單位。 進程又被細化為線程,也就是一個進程下有多個能獨立運行的更小的單位。在同一個時間里,同一個計算機系統中如果允許兩個或兩個以上的進程處于運行狀態,這便是多任務。現代的操作系統幾乎都是多任務操作系統,能夠同時管理多個進程的運行。 多任務帶來的好處是明顯的,比如你可以邊聽mp3邊上網,與此同時甚至可以將下載的文檔打印出來,而這些任務之間絲毫不會相互干擾。那么這里就涉及到并行的問題,俗話說,一心不能二用,這對計算機也一樣,原則上一個CPU只能分配給一個進程,以便運行這個進程。我們通常使用的計算機中只有一個CPU,也就是說只有一顆心,要讓它一心多用,同時運行多個進程,就必須使用并發技術。實現并發技術相當復雜,最容易理解的是“時間片輪轉進程調度算法”,它的思想簡單介紹如下:在操作系統的管理下,所有正在運行的進程輪流使用CPU,每個進程允許占用CPU的時間非常短(比如10毫秒),這樣用戶根本感覺不出來 CPU是在輪流為多個進程服務,就好象所有的進程都在不間斷地運行一樣。但實際上在任何一個時間內有且僅有一個進程占有CPU。 如果一臺計算機有多個CPU,情況就不同了,如果進程數小于CPU數,則不同的進程可以分配給不同的CPU來運行,這樣,多個進程就是真正同時運行的,這便是并行。但如果進程數大于CPU數,則仍然需要使用并發技術。 進行CPU分配是以線程為單位的,一個進程可能由多個線程組成,這時情況更加復雜,但簡單地說,有如下關系: 

            總線程數<= CPU數量:并行運行 

            總線程數> CPU數量:并發運行 

            并行運行的效率顯然高于并發運行,所以在多CPU的計算機中,多任務的效率比較高。但是,如果在多CPU計算機中只運行一個進程(線程),就不能發揮多CPU的優勢。 這里涉及到多任務操作系統的問題,多任務操作系統(如Windows)的基本原理是:操作系統將CPU的時間片分配給多個線程,每個線程在操作系統指定的時間片內完成(注意,這里的多個線程是分屬于不同進程的).操作系統不斷的從一個線程的執行切換到另一個線程的執行,如此往復,宏觀上看來,就好像是多個線程在一起執行.由于這多個線程分屬于不同的進程,因此在我們看來,就好像是多個進程在同時執行,這樣就實現了多任務.(摘自:http://zhidao.baidu.com/question/2601473)


            多線程
            :在計算機編程中,一個基本的概念就是同時對多個任務加以控制。許多程序設計問題都要求程序能夠停下手頭的工作,改為處理其他一些問題,再返回主進程。可以通過多種途徑達到這個目的。最開始的時候,那些掌握機器低級語言的程序員編寫一些“中斷服務例程”,主進程的暫停是通過硬件級的中斷實現的。盡管這是一種有用的方法,但編出的程序很難移植,由此造成了另一類的代價高昂問題。中斷對那些實時性很強的任務來說是很有必要的。但對于其他許多問題,只要求將問題劃分進入獨立運行的程序片斷中,使整個程序能更迅速地響應用戶的請求。  
            最開始,線程只是用于分配單個處理器的處理時間的一種工具。但假如操作系統本身支持多個處理器,那么每個線程都可分配給一個不同的處理器,真正進入“并行運算”狀態。從程序設計語言的角度看,多線程操作最有價值的特性之一就是程序員不必關心到底使用了多少個處理器。程序在邏輯意義上被分割為數個線程;假如機器本身安裝了多個處理器,那么程序會運行得更快,毋需作出任何特殊的調校。根據前面的論述,大家可能感覺線程處理非常簡單。但必須注意一個問題:共享資源!如果有多個線程同時運行,而且它們試圖訪問相同的資源,就會遇到一個問題。舉個例子來說,兩個線程不能將信息同時發送給一臺打印機。為解決這個問題,對那些可共享的資源來說(比如打印機),它們在使用期間必須進入鎖定狀態。所以一個線程可將資源鎖定,在完成了它的任務后,再解開(釋放)這個鎖,使其他線程可以接著使用同樣的資源。 
            多線程是為了同步完成多項任務,不是為了提高運行效率,而是為了提高資源使用效率來提高系統的效率。線程是在同一時間需要完成多項任務的時候實現的。
            一個采用了多線程技術的應用程序可以更好地利用系統資源。其主要優勢在于充分利用了CPU的空閑時間片,可以用盡可能少的時間來對用戶的要求做出響應,使得進程的整體運行效率得到較大提高,同時增強了應用程序的靈活性。更為重要的是,由于同一進程的所有線程是共享同一內存,所以不需要特殊的數據傳送機制,不需要建立共享存儲區或共享文件,從而使得不同任務之間的協調操作與運行、數據的交互、資源的分配等問題更加易于解決(摘自:http://baike.baidu.com/view/65706.htm)
            通常由操作系統負責多個線程的調度和執行。


            進程間通信IPCInter-Process Communication),指至少兩個進程線程間傳送數據或信號的一些技術或方法。進程是計算機系統分配資源的最小單位。每個進程都有自己的一部分獨立的系統資源,彼此是隔離的。為了能使不同的進程互相訪問資源并進行協調工作,才有了進程間通信。這些進程可以運行在同一計算機上或網絡連接的不同計算機上。

            進程間通信技術包括消息傳遞、同步、共享內存和遠程過程調用。IPC是一種標準的Unix通信機制。

            使用IPC 的理由:

            • 信息共享
            • 加速;
            • 模塊化;
            • 方便以及
            • 私有權分離.

            主要的 IPC 方法

            方法

            提供方(操作系統或其他環境)

            文件

            多數操作系統

            信號

            多數操作系統

            Socket

            多數操作系統

            消息隊列(en:Message queue)

            多數操作系統

            管道(en:Pipe)

            所有的 POSIX systems, Windows.

            具名管道(en:Named Pipe)

            所有的 POSIX 系統, Windows.

            信號量(en:Semaphore)

            所有的 POSIX 系統, Windows.

            共享內存

            所有的 POSIX 系統, Windows.

            Message passing(en:Message passing)
            (
            不共享)

            用于 MPI規范,Java RMI, CORBA, MSMQ, MailSlot以及其他.

            Memory-mapped file(en:Memory-mapped file)

            所有的 POSIX 系統, Windows.

            (摘自:http://zh.wikipedia.org/wiki/行程間通訊) 

            轉自:http://blog.chinaunix.net/uid-20556054-id-3060696.html

            posted @ 2012-12-18 13:44 鑫龍 閱讀(554) | 評論 (0)編輯 收藏

            今天突然想比較一下 write() 和 writev() 的性能, 網上google了半天, 竟然沒有發現一點有關的數據信息, 自己就測試了一下。

            平臺如下:

            CentOS 5.2 Linux kernel 2.6.18-92.e15

            CPU: Intel(R) Pentium(R) 4 CPU 2.40GHz

            Disk: 7200 rpm

            測試的想法是: 對于writev(), 如果有10 個buffer, 并且buffer的大小是1kb,  那么我就先依次調用write() 10 次, 每次寫1KB 到同一個文件, 記錄下時間, 然后記錄下用writev()的時間。 最后, 以write()為baseline, 計算writev()所占的%, 如果%越小, 證明writev() 的性能就越好。

            做了兩組測試,

            第一組, 固定buffer 的個數(10, 100, 1000), 依次增加buffer的大小, 從1KB -- 1024KB, 數據如下, (基準線為相應write()的數據)

            例如, 10 個buffer, 每個buffer size 是1KB。 write() 耗時0.092 ms, writev() 耗時0.098 ms, 圖中的數據即為 1.067 (write_v10, 1KB)

            圖一writev和write性能比較(轉) - 光明磊落 - 光明磊落的博客

             

            第二組, 固定buffer大小(1KB, 2KB, 8KB), 依次增加buffer的數目, 從 200 -- 8000, 數據如下 (基準線為相應write()的數據)

            圖二

            writev和write性能比較(轉) - 光明磊落 - 光明磊落的博客

            第一組數據顯示:1.  隨著buffer的增大 ( > 64KB), writev()的性能開始跟write()持平; 2. 如果buffer的個數過小 , writev()的性能是低于write()的。 從圖一可以看到,  在buffer size 小于1024KB 時, writev() 使用10 個buffer的性能要低于100 和1000。

            第二組數據顯示: 1. 當保持buffer size一定情況下, 增加buffer的個數 (< 2000), writev() 的性能穩定在70%左右; 2. 增加buffer size, 將會降低writev()的性能。 當buffer為8KB 時, writev() 所用時間基本上都為相應write()時間的80%, 性能的提高明顯不如1KB 和 2KB。3. 當buffer的個數超過2000, 并且buffer size 大于2KB, writev()性能將遠不如write()。

            結論:

            writev() 應使用在small write intensive 的workload中, buffer size 應控制在 2KB 以下, 同時buffer的數目不要超過IOV_MAX, 否則 writev() 并不會帶來性能的提高。 

             

            現在, 所要研究的問題是對于不同的workload, 如何快速的確定writev()中buffer的個數和大小, 從而達到較好performance。

            Saturday, May 09, 2009  8:50:48 PM

            posted @ 2012-12-18 11:33 鑫龍 閱讀(1497) | 評論 (0)編輯 收藏

                 首先,建議性鎖和強制性鎖并不是真正存在的鎖,而是一種能對諸如記錄鎖、文件鎖效果產生影響的兩種機制。

            1.建議性鎖機制是這樣規定的:每個使用文件的進程都要主動檢查該文件是否有鎖存在,當然都是通過具體鎖的API,比如fctl記錄鎖F_GETTLK來主動檢查是否有鎖存在。如果有鎖存在并被排斥,那么就主動保證不再進行接下來的IO操作。如果每一個進程都主動進行檢查,并主動保證,那么就說這些進程都以一致性的方法處理鎖,(這里的一致性方法就是之前說的兩個主動)。但是這種一致性方法依賴于編寫進程程序員的素質,也許有的程序員編寫的進程程序遵守這個一致性方法,有的不遵守。不遵守的程序員編寫的進程程序會怎么做呢?也許會不主動判斷這個文件有沒有加上文件鎖或記錄鎖,就直接對這個文件進行IO操作。此時這種有破壞性的IO操作會不會成功呢?如果是在建議性鎖的機制下,這種破壞性的IO就會成功。因為鎖只是建議性存在的,并不強制執行。內核和系統總體上都堅持不使用建議性鎖機制,它們依靠程序員遵守這個規定。(Linux默認是采用建議性鎖)

            2.強制性鎖機制是這樣規定的: 所有記錄或文件鎖功能內核執行的。上述提到的破壞性IO操作會被內核禁止。當文件被上鎖來進行讀寫操作時,在鎖定該文件的進程釋放該鎖之前,內核會強制阻止任何對該文件的讀或寫違規訪問,每次讀或寫訪問都得檢查鎖是否存在。也就是強制性鎖機制,讓鎖變得名副其實,真正達到了鎖的效果,而不是像建議性鎖機制那樣只是個紙老虎。= =!
               設置強制性文件鎖的方式比較特別:

               chmod g+s <filename>
               chmod g-x <filename>

               這是形象的表示,實際編程中應該通過chmod()函數一步完成。不能對目錄、可執行文件設置強制性鎖。 

            3.貼出網上搜到的解釋

            例1,我有幾個進程(不一定有親緣關系)都先通過fctnl鎖機制來判斷再操作文件,這個就叫一致的方法。參見[2] 
            但是,如果同時,又有個流氓進程,管它3721,沖上去,直接open, write一堆操作。 
            這時候那幾個先fcntl 再操作的進程對這種方式無能為力,這樣就叫不一致。文件最后的狀態就不定了。 
            正因為這種鎖約束不了其它的訪問方式,所以叫建議行鎖。強制性鎖需要內核支持的,對read, write, open都會檢查鎖。
            例2,所謂建議性鎖就是假定人們都會遵守某些規則去干一件事。例如,人與車看到紅燈都會停,而看到綠燈才會繼續走,我們可以稱紅綠等為建議鎖。但這只是一種規則而已,你并不防止某些人強闖紅燈。而強制性鎖是你想闖紅燈也闖不了。

            posted @ 2012-12-17 12:24 鑫龍 閱讀(1933) | 評論 (0)編輯 收藏

                 摘要: 這幾天開始拜讀侯捷先生和孟巖先生的譯作《C++標準程序庫:自修教程與參考手冊》 。兩位先生確實譯功上乘,讀得很順。但是讀到P55頁關于auto_ptr_ref的討論,卻百思不得其解:為什么需要引入auto_ptr_ref這個輔助類呢? 從書中描述來看,仿佛與拷貝構造函數 、右值 、類型轉換 有關。于是,結合auto_ptr的源代碼,google之、...  閱讀全文

            posted @ 2012-12-16 16:06 鑫龍 閱讀(261) | 評論 (0)編輯 收藏

            左值右值

            左值(lvalue)和右值(rvalue)是編程中兩個非常基本的概念,但是也非常容易讓人誤解,看了很多文章,自我感覺真正將這個問題講的很透徹的文章還沒有看見,所以自告奮勇來嘗試一下。如果左值右值的概念不是非常清楚的話,它們遲早會像攔路虎一樣跳出來,讓你煩心不已,就像玩電腦游戲的時候每隔一段時間總有那么幾個地雷考驗你的耐性,如果一次把所有地雷掃盡就好了。:)

            左值(lvalue)和右值(rvalue)最先來源于編譯理論(感謝南大小百合的programs)。在C語言中表示位于賦值運算符兩側的兩個值,左邊的就叫左值,右邊的就叫右值。比如:

            int ii = 5;//ii是左值,5是右值

            int jj = ii;//jj是左值,ii是右值

            上面表明,左值肯定可以作為右值使用,但反之則不然。左值和右值的最早區別就在于能否改變。左值是可以變的,右值不能變。【注1

            1:這一點在C++中已經豬羊變色,不再成立。拱豬游戲還是挺好玩的,我還真抓過好幾次全紅心,不過真的好險。:)

            在很多文章中提到,在C++中,左值更多的指的是可以定位,即有地址的值,而右值沒有地址。【注2

            2:這一點仍然不準確,我在程序中生成一個臨時右值std::vector(),你能夠說它沒有地址嗎?難道它是沒有肉體的鬼魂或幽靈?它是有地址的,而且它也是絕對的右值。

            在現代C++中,現在左值和右值基本上已經失去它們原本所具有的意義,對于左值表達式,通過具體名字和引用(pointer or reference)來指定一個對象。非左值就是右值。我來下一個定義:

            左值表示程序中必須有一個特定的名字引用到這個值。

            右值表示程序中沒有一個特定的名字引用到這個值。

            跟它們是否可以改變,是否在棧或堆(stack or heap)中有地址毫無關系。

            1.左值

            在下面的代碼中:

            int ii = 5;

            int const jj = ii;

            int a[5];

            a[0] = 100;

            *(a+3) = 200;

            int const& max( int const& a, int const& b ) //call by reference

            {

                  return a > b ? a : b;

            }

            int& fun(int& a) //call by reference

            {

                  a += 5;

               return a;

            }

            iijja[0]*(a+3),還有函數max的返回值比如max(ii, jj),【注3】函數fun的返回值fun(ii)都是左值。,它們都是有特定的引用名字的值。iijja[0]*(a+3)max(ii, jj)fun(ii)分別就是它們的名字。

            3:在這里有一個不太容易分清楚的盲點。那就是有人會問max(8, 9)到達是左值還是右值,C++標準規定常量引用(reference to const)可以引用到右值,所以max(8, 9)似乎應該是右值,不過不管它是左值,還是右值,我們都不能試圖去改變它。為了與前面的概念一致,我認為它是左值,不可改變的常量左值。

            左值有不能改變的,即被const所修飾的左值,比如上面的jjmax(ii, jj)都是被常量(const)魔咒所困住的左值。

            沒有被const困住的左值當然是可以改變的,比如下面的代碼都是成立的:

            ii = 600;

            a[0] = 700;

            fun(ii) = 800; //OK!

            我們的眼睛沒有問題,fun(ii) = 800;完全正確,因為它是可以改變的左值。所以我們看STL的源碼,就會理解std::vector中的重載operator[]運算符的返回值為什么要寫成引用,因為operator[]必須返回左值。

             

            2.右值

            沒有特定名字的值是右值。先看下面的代碼:

            std::list();

            std::string(“It is a rvalue!”);

            int fun1() //call by value

            {

                  

            }

            int* fun2() //call by reference

            {

                  

            }

            其中std::list()std::string(“It is a rvalue!”),函數fun1的返回值fun1(),函數fun2的返回值fun2()都是右值,它們的值都沒有特定的名字去引用。也許有人會奇怪,fun2()也是右值?最前面的max(a,b)不是左值嗎?

            請看清楚,函數fun2的返回值是pointerpointer也是call by value,而函數max的返回值是referencereferencecall by reference。所以說C++中引入reference不僅僅是為了方便,它也是一種必須。【注4

            4Scott Meyer寫的《More Effective C++》的條款1專門講了pointerreference的區別,寫的很好,辨別的非常清楚。

            fun2()是右值,但 *fun2()卻是左值,就跟經常看到的*p一樣,所以看C++庫代碼的時候,會發現重載operator*的函數返回值是reference

            當然我還遺漏了一種右值,那就是字面上的(literal)值,比如58.23’a’等等理所當然的都是右值。

            右值最初出現的時候,一個最大的特征就是不可改變。但就跟我們的道德標準一樣,時代不同了,標準也變化了,以前的三綱五常早已經被扔到歷史的垃圾堆里面了。

            C++中有可以改變的右值,而且這個特性還非常有用。那就是用戶自定義的類(class)的構造函數生成的臨時對象。比如:

            std::vector(9)std::deque(),……都是可以改變的右值。在Herb Sutter的《More Exceptional C++》中的條款7page51頁有這樣幾行代碼:

            // Example 7-2(b): The right way to shrink-to-fit a vector.

            vector<Customer> c( 10000 );

            // ...now c.capacity() >= 10000...

            // erase all but the first 10 elements

            c.erase( c.begin()+10, c.end() );

            // the following line does shrink c's

            // internal buffer to fit (or close)

            vector<Customer>( c ).swap( c );

            // ...now c.capacity() == c.size(), or

            // perhaps a little more than c.size()

            認真看幾遍,你會發現但vector的大小增大到一定程度,你又用不著這么多空間的時候,你會想辦法把它收縮到最合適的大小,但利用別的辦法比如調用成員函數reserve()都無法辦到,這個時候就必須利用右值可以改變這個性質了。

            vector<Customer>( c ).swap( c );這行代碼就是點睛之處。

            首先使用復制構造函數生成臨時右值vector<Customer>( c ),這個右值正好是合適大小,然后和c交換【注5】,c就變成合適大小了,最后在整個表達式結束的時候,這個臨時右值析構歸還內存空間。真是紳士一般的優雅!

            5:這個時候這個臨時右值就發生了改變。

            如果還不理解,可以看看書,或者直接看庫的源代碼。

            至于為什么會這樣?我思考了一下,我想是這樣的,我們看類(class)的數據布置結構,會發現它的每一個數據成員都是有名字的,我想編譯器在編譯的過程中,都會生成一個外部不所知的對這個臨時對象右值的名字引用,但需要改變這個臨時對象的時候,這個名字就用上了。比如:

            class Point

            {

            public: //純粹為了方便,我把數據成員公開,現實中盡量不要這樣用

                  int x, y ,z;

                  ……//其他各種成員函數

            };

            我們現在就可以改變右值,用到了匿名的引用名字。

            Point().x = 6;//改變了右值

            Point().y = 6;//同意改變了右值,不過注意,這個右值跟上面的不是同一個。

            總結

            左值和右值的真正區別我想就是這些了,左值表示有特定的名字引用,而右值沒有特定的名字引用。當然我仍然會有疏忽,希望大家能夠提醒我,指正我的不足。

            前兩天看Herb Sutter從郵件中寄來的新文章(我訂閱他的新文章郵件通知),一篇是講Tuple數據結構的,沒有什么新意,以前好像看過,還有一篇名字是:(MostlyPrivate,地址為http://www.cuj.com/documents/s=8273/cujcexp2107sutter/ 內容本身并不深,但看完文章,發現隨處可見C++的波詭云譎,又會對什么叫袖里乾坤,滴水藏海多一份感性認識。

            在下一篇文章我想從不同于一般的角度,從自己的經歷談談在校畢業生在IT行業怎樣找工作,我想會讓所有讀者都有一些思考,不僅僅是求職者。題目我已經想好了,就叫《扮虎吃豬》,不過現在我有一些別的事情要忙,所以可能會讓大家等幾天。

            轉載請注明來源,謝謝!

            吳桐寫于2003.6.20

            最近修改2003.6.21

            轉自:
            http://blog.csdn.net/csdnji/article/details/169200

            posted @ 2012-12-16 15:08 鑫龍 閱讀(324) | 評論 (0)編輯 收藏

            習慣了這樣寫,但有時候會反問下自己為什么要const和&?
            1.為什么要&?
            答:&是必須的。
            如果它不是引用,在傳遞參數時,會產生一個局部變量做參數,而這個局部變量的構造又要調copy構造函數一次,....那就子子孫孫無窮匱了....
            但如果是引用,在傳遞參數時候,局部變量只需要綁定原變量,而不需要再一次調用copy構造函數。

            2.為什么要const?
            答:const不是必須的。
            構造函數是用引用方式傳遞復制對象,引用方式傳遞的是地址,因此在構造函數內對該引用的修改會影響源對象。而你在用對象a1構造a2時,自然不希望復制構造函數會改變a1的內容,因此要防止復制構造函數內部修改該引用,所以一般用const聲明。加不加const還是需要看copy構造的行為,比如std::auto_ptr的構造函數,就沒有const,因為它要獲得原有對象指針的擁有權。

            posted @ 2012-12-16 14:52 鑫龍 閱讀(623) | 評論 (0)編輯 收藏

            僅列出標題
            共20頁: First 9 10 11 12 13 14 15 16 17 Last 
            久久性精品| 中文字幕人妻色偷偷久久| 四虎国产精品免费久久久| 99精品久久久久久久婷婷| 久久精品国产一区二区三区| 久久亚洲精品无码aⅴ大香| 久久亚洲精品人成综合网| 国产福利电影一区二区三区久久久久成人精品综合 | 麻豆一区二区99久久久久| 久久青青草原精品影院| 日本WV一本一道久久香蕉| 99精品久久精品一区二区| 亚洲欧洲中文日韩久久AV乱码| 欧美黑人激情性久久| 久久久无码精品午夜| 狠狠色噜噜狠狠狠狠狠色综合久久| 麻豆久久| 久久国产美女免费观看精品 | 国内精品久久久久| 国产精品久久久香蕉| 久久国产V一级毛多内射| 久久66热人妻偷产精品9| 久久天天躁夜夜躁狠狠躁2022| 久久福利青草精品资源站| 麻豆亚洲AV永久无码精品久久| 无码人妻少妇久久中文字幕| 国产精品嫩草影院久久| 东京热TOKYO综合久久精品| A级毛片无码久久精品免费| 欧美久久久久久精选9999| 国产精品99久久久久久董美香| 国产精品久久久久国产A级| 久久99精品久久只有精品| 伊人久久大香线蕉av不卡| 久久久久久久久久久久久久| 尹人香蕉久久99天天拍| 国内精品久久国产| 久久久无码精品亚洲日韩京东传媒 | 国产精品激情综合久久| 狠狠精品干练久久久无码中文字幕| 91久久精品国产免费直播|