china0114.com-日韩欧美中文免费,免费视频一区,免费视频一区,国产精品色网

公眾號
關注微信公眾號
移動端
創頭條企服版APP

Python 中的多進程與線程 每個數據科學家都需要知道

5725
網絡大數據 2019-09-11 13:10 搶發第一評
線程和進程都是現在計算機領域比較時髦的用語。進程 (Process) 是計算機中已運行程序的實體。進程本身不會運行,是線程的容器。

本文最初發布于 FLOYDHUB 博客,原作者:Sumit Ghosh

導讀:線程和進程都是現在計算機領域比較時髦的用語。進程 (Process) 是計算機中已運行程序的實體。進程本身不會運行,是線程的容器。程序本身只是指令的集合,進程才是程序(那些指令) 的真正運行。若干進程有可能與同一個程序相關系,且每個進程皆可以同步(循序) 或不同步(平行) 的方式獨立運行。進程為現今分時系統的基本運作單位。線程(thread),操作系統技術中的術語,是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發多個線程,每條線程并行執行不同的任務。今天,我們翻譯并分享 Sumit Ghosh 撰寫的關于 Python 中的多進程與線程的方方面面,這些內容對于每個有志于成為數據科學家的從業者都是應知必會的內容。

?

?

每個數據科學項目遲早都會面臨一個不可避免的挑戰:速度。使用更大的數據集會導致處理速度變慢,因此,最終不得不考慮優化算法的運行時間。正如大多數人所知道的,并行化就是這種優化的必要步驟。Python 為并行化提供了兩個內置庫:multiprocessing(多進程)和 threading(線程)。在本文中,我們將探討數據科學家如何在這兩者之間進行選擇,以及在選擇時應記住哪些因素。

并行計算與數據科學

眾所周知,數據科學是一門處理大量數據,并從中提取有用見解的科學。通常情況下,我們在數據上執行的操作很容易實現并行化,這意味著不同的處理可以在數據上一次運行一個操作,然后在最后將結果組合起來以得到完整的結果。

為了更好地理解并行性,讓我們考慮一個真實世界的類比。假設你需要打掃家里的三個房間,你可以一個人包攬所有的事情:一個接一個地打掃房間,或者你也可以叫來兩個人幫助你,你們每個人只打掃一個房間。在后一種方法中,每個人都并行地處理整個任務的一部分,從而減少完成了任務所需的總時間,這就是并行性。

在 Python 中,可以通過兩種不同的方式實現并行處理:多進程和線程。

多進程和線程:理論

從根本上來說,多進程和線程是實現并行計算的兩種方法,分別使用進程和線程作為處理代理。要了解這些方法的工作原理,我們就必須弄清楚什么是進程,什么是線程。

?

?

進程

進程是正在執行的計算機程序的實例。每個進程都有自己的內存空間,用于存儲正在運行的指令,以及需要存儲和訪問用來執行的任何數據。

線程

線程是進程的組件,可以并行運行。一個進程可以有多個線程,它們共享相同的內存空間,即父進程的內存空間。這意味著要執行的代碼以及程序中聲明的所有變量,將由所有線程共享。

?

?

進程和線程(圖:筆者與 Cburnett)

例如,讓我們考慮一下你的計算機上正在運行的程序。你可能正在瀏覽器中閱讀本文,瀏覽器可能打開了多個標簽頁。你還可能同時通過 Spotify 桌面應用收聽音樂。瀏覽器和 Spotify 應用程序是不同的進程,它們中的每一個都可以使用多個進程或線程來實現并行性。瀏覽器中的不同標簽頁可能在不同的線程中運行。Spotify 可以在一個線程中播放音樂,在另一個線程中從互聯網下載音樂,并使用第三個線程來顯示 GUI。這就叫做多線程(multithreading)。多進程(多即個進程)也可以做到這一點。事實上,大多數像 Chrome 和 Firefox 這樣的現代瀏覽器使用的是多進程而不是多線程來處理多個標簽。

技術細節

一個進程的所有線程都位于同一個內存空間中,而進程有各自獨立的內存空間。

與進程相比,線程更輕量級,并且開銷更低。生成進程比生成線程要慢一些。

在線程之間共享對象更容易,因為它們共享的是相同的內存空間。為了在進程之間實現同樣的效果,我們必須使用一些類似 IPC(inter-process communication,進程間通信)模型,通常是由操作系統提供的。

并行計算的陷阱

在程序中引入并行性并不總是一個正和博弈;有一些需要注意的陷阱。最重要的陷阱如下:

競態條件: 正如我們已經討論過的,線程具有共享的內存空間,因此它們可以訪問共享變量。當多個線程試圖通過同時更改同一個變量時,就會出現競態條件。線程調度程序可以在線程之間任意切換,因此,我們無法知曉線程將試圖更改數據的順序。這可能導致兩個線程中的任何一個出現不正確的行為,特別是如果線程決定基于變量的值執行某些操作時。為了防止這種情況的發生,可以在修改變量的代碼段周圍放置互斥鎖,這樣,一次只能有一個線程可以寫入變量。

饑餓: 當線程在更長的時間內被拒絕訪問特定資源時,就會發生饑餓,因此,整個程序速度就會變慢。這可能是涉及不良的線程調度算法的意外副作用。

死鎖: 過度使用互斥鎖也有一個缺點,它可能會在程序中引入死鎖。死鎖是一個線程等待另一個線程將鎖釋放,但另一個線程需要一個資源來完成第一個線程所持有的鎖。這樣,兩個線程都會停止,程序也隨之停止。死鎖可以被看作是饑餓的一種極端情況。要避免這種情況,我們必須小心不要引入太多相互依賴的鎖。

活鎖: 活鎖是指線程在循環中繼續運行,但沒有任何進展。這也是由于涉及不良和互斥鎖的使用不當造成的。

Python 中的多進程和線程

全局解釋鎖

當涉及到 Python 時,有一些奇怪的地方需要記住。我們知道,線程共享相同的內存空間,因此必須采取特殊的預防措施,以便兩個線程不會寫入相同的內存位置。CPython 解釋器使用一種名為 GIL 的機制或全局解釋鎖來處理這個問題。

摘自 Python 的官方 wiki :

在 CPython 中,全局解釋鎖(Global Interpreter Lock,GIL)是一個互斥鎖,用來保護對 Python 對象的訪問,防止多個線程同時執行 Python 字節碼。這種鎖是必要的,主要是因為 CPython 的內存管理不是線程安全的。

請查看這個幻燈片來了解 Python GIL 的詳細信息: Understanding the Python GIL

GIL 完成了它的工作,但是也付出了代價。它有效地序列化了解釋器級別的指令。它的工作原理如下:任何線程要執行任何函數,都必須獲取全局鎖。一次只能有一個線程可以獲得全局鎖,這意味著解釋器最終會串行地運行指令。這種設計使內存管理做到線程安全,但結果是,它根本不能利用多個 CPU 內核。在單核 CPU 中(這正是設計師在開發 CPython 時所考慮的),這并不是什么大問題。但是,如果使用多核 CPU 的話,那么這個全局鎖最終將會成為一個瓶頸了。

如果你的程序在其他地方存在更嚴重的瓶頸,例如在網絡、IO、或者用戶交互方面,那么全局鎖這個瓶頸就變得無關緊要了。在這些情況下,線程化是一種完全有效的并行化方法。但對于計算密集型(CPU bound)的程序,線程化最終會使程序變慢。讓我們通過一些用例來探討這個問題。

線程用例

GUI 程序始終使用線程來使應用程序作出響應。例如,在文本編輯程序中,一個線程負責記錄用戶輸入,另一個線程負責顯示文本,第三個線程負責拼寫檢查,等等。在這里,程序必須等待用戶交互,這是最大的瓶頸。使用多進程并不會使程序變得更快。

線程處理的另一個用例是 IO 密集型(IO bound)或網絡密集型的程序,比如 Web Scraper 。在這種情況下,多個線程可以負責并行抓取多個 Web 網頁。線程必須從互聯網上下載網頁,這將是最大的瓶頸,因此線程對于這種情況來說是一個完美的解決方案。網絡密集型的 Web 服務器的工作方式類似:對于它們這種情況,多進程并不比線程有任何優勢。另一個相關的例子是 TensorFlow ,它使用線程池(thread pool)來并行地轉換數據。

多進程的用例

在程序是計算密集型的,且不需要進行任何 IO 或用戶交互的情況下,那么多進程就比線程處理更為出色。例如,任何只處理數字的程序都將從多進程中獲得巨大的加速;事實上,線程化處理可能會降低它的運行速度。一個有趣的實際例子是 Pytorch Dataloader ,它使用多個子進程將數據加載到 GPU 中。

在 Python 中的并行化

Python 為并行化方法提供了兩個同名的庫: multiprocessing 和 threading。盡管它們之間存在根本的不同,但這兩個庫提供了非常相似的 API(從 Python 3.7 開始)。讓我們看看它們的實際應用。

import threading import random from functools import reduce

def func(number): random_list = random.sample(range(1000000), number) return reduce(lambda x, y: x*y, random_list)

number = 50000 thread1 = threading.Thread(target=func, args=(number,)) thread2 = threading.Thread(target=func, args=(number,))

thread1.start() thread2.start()

thread1.join() thread2.join()

你可以看到,我創建一個函數 func,它創建了一個隨機數列表,然后按順序將其中的所有元素相乘。如果項目數量足夠大,比如 5 萬或 10 萬,這可能是一個相當繁重的過程。

然后,我創建了兩個線程,它們將執行相同的函數。線程對象有一個異步啟動線程的 start 方法。如果我們想等待它們終止并返回,就必須調用 join 方法,這就是我們這段代碼所做的事情。

正如你所見,在后臺將一個新線程轉化為一個任務的 API 非常簡單。很棒的是,用于多進程的 API 也幾乎完全相同;讓我們來看一下。

import multiprocessing import random from functools import reduce

def func(number): random_list = random.sample(range(1000000), number) return reduce(lambda x, y: x*y, random_list)

number = 50000 process1 = multiprocessing.Process(target=func, args=(number,)) process2 = multiprocessing.Process(target=func, args=(number,))

process1.start() process2.start()

process1.join() process2.join()

代碼就是這樣的,只需將 multiprocessing.Process 與 threading.Thread 進行交換。 你使用多進程實現了完全相同的程序。

很顯然,你可以用它做更多的事情,但這已不在本文的范疇之內,因此我們將不再贅述。如果有興趣了解更多相關信息,請查看 threading — Thread-based parallelism。

基準

現在我們已經了解了實現并行化的代碼是什么樣子的,讓我們回到性能問題上來。正如我們之前所指出的,線程處理不適合計算密集型任務。在這種情況下,它最終會成為瓶頸。我們可以使用一些簡單的基準來驗證這一點。

首先,讓我們看看上面所展示的代碼示例中線程和多進程的比較。請記住,此任務不涉及任何類型的 IO,因此它是純計算密集型的任務。

?

?

讓我們來看看 IO 密集型的任務的類似基準測試。例如,下面的函數:

import requests

def func(number): url = 'http://example.com/' for i in range(number): response = requests.get(url) with open('example.com.txt', 'w') as output: output.write(response.text)

?

這個函數的作用就是只獲取一個網頁,并將其保存到本地文件中,如此循環多次。雖然沒有什么用,但是很直接,因此非常適合用來演示。讓我們看一下基準測試。

?

?

從這兩張圖表中可以注意到以下幾點:

在這兩種情況下,單個進程比單個線程花費更多的執行時間。顯然,進程的開銷比線程更大。

對于計算密集型的任務,多個進程的性能要比多個線程的性能要好得多。然而,當我們使用 8x 并行化時,這種差異就變得不那么明顯了。由于我的筆記本的 CPU 是四核的,因此最多可以有四個進程有效地使用多個內核。因此,當我使用更多進程時,它就不能很好地進行擴展。但是,它的性能仍然比線程要好很多,因為線程根本就不能利用多核。

對于 IO 密集型的任務,那么 CPU 就不是瓶頸了。因此,GIL 的常見限制在這里并不適用,而多進程也沒有什么優勢。不僅如此,線程的輕量級開銷實際上使它們比多進程更快,而且線程的性能始終優于多進程。

區別、優點和缺點

線程在同一個內存空間中運行;進程有獨立的內存。

從之前的一點開始:線程之間共享對象更容易,但問題的另一面是,你必須采取額外的措施來進行對象同步,確保兩個線程不會同時寫入同一個對象,不會發生競態條件。

由于對象同步增加了編程開銷,多線程編程更容易出錯。而另一方面,多進程編程則很容易實現。

與進程相比,線程的開銷更低;生成進程比線程花費更多的時間。

由于 Python 中 GIL 的局限性,線程無法利用多個 CPU 內核實現真正的并行化。而多進程則沒有任何這樣的限制。

進程調度由 OS 處理,而線程調度由 Python 解釋器來完成。

子進程是可中斷、可終止的,而子線程則不是。你必須等待線程終止或 join。

從所有這些討論中,我們可以得出以下結論:

線程應該用于涉及 IO 或用戶交互的程序。

多進程應該用于計算密集型程序。

站在數據科學家的角度來看

典型的數據處理管道可以分為以下幾個步驟:

讀取?數據并存儲到主存儲器或 GPU 中。

使用 CPU 或 GPU 進行計算。

將挖掘出的信息存儲在數據庫或磁盤中。

讓我們探索一下如何在這些任務中引入并行性,以便加快它們的運行速度。

步驟 1 涉及從磁盤讀取數據,因此顯然磁盤 IO 將成為這一步驟的瓶頸。正如我們已經討論過的,線程是并行化這種操作的最佳選擇。類似地,步驟 3 也是引入線程的理想候選步驟。

但是,步驟 2 包括了涉及 CPU 或 GPU 的計算。如果它是一個基于 CPU 的任務,那么使用線程就沒有用;相反,我們必須進行多進程。只有這樣,我們才能充分利用 CPU 的多核并實現并行性。如果它是基于 GPU 的任務,由于 GPU 已經在硬件級別上實現了大規模并行化架構,使用正確的接口(庫和驅動程序)與 GPU 交互應該會解決其余的問題。

?

?

現在你可能會想,“恐怕我的數據管道看起來有點不同啊;我有一些任務并不完全適合這個通用框架啊。”不過,你應該能夠觀察到此處用來決定線程和多進程之間的關系。你應該考慮的因素包括:

你的任務是否具有任何形式的 IO?

IO 是否為程序的瓶頸?

你的任務是否依賴于 CPU 的大量計算?

考慮到這些因素,再加上上面提到的要點,你應該能夠作出自己的決定了。另外,請記住,你不必在整個程序中,使用單一形式的并行化。你應該為程序的不同部分使用其中一種或另一種形式的并行化,以適合該特定部分的為準。

現在,我們來看一下數據科學家可能面臨的兩個示例場景,以及如何使用并行計算來加速它們。

場景:下載電子郵件

假設你想分析你自己的創業公司收件箱里的所有電子郵件,并了解趨勢:誰是發送頻率最高的發件人,在電子郵件中出現的最常見的關鍵詞是什么,一周中的哪一天或者一天中的哪個時段收到的電子郵件最多,等等。當然,這個項目的第一步是將電子郵件下載到你的電腦上。

首先,讓我們按順序執行,不使用任何并行化。下面是要使用的代碼,它應該很容易理解。有一個函數 download_emails,它將電子郵件的 ID 列表作為輸入,并按順序下載它們。這會將此函數與一次 100 封電子郵件列表的 ID 一起調用。

import imaplib import time

IMAP_SERVER = 'imap.gmail.com' USERNAME = 'username@gmail.com' PASSWORD = 'password'

def download_emails(ids): client = imaplib.IMAP4_SSL(IMAP_SERVER) client.login(USERNAME, PASSWORD) client.select() for i in ids: print(f'Downloading mail id: {i.decode()}') _, data = client.fetch(i, '(RFC822)') with open(f'emails/{i.decode()}.eml', 'wb') as f: f.write(data 0 ) client.close() print(f'Downloaded {len(ids)} mails!')

start = time.time()

client = imaplib.IMAP4_SSL(IMAP_SERVER) client.login(USERNAME, PASSWORD) client.select() _, ids = client.search(None, 'ALL') ids = ids[0].split() ids = ids[:100] client.close()

download_emails(ids) print('Time:', time.time() - start)

Time taken :: 35.65300488471985 seconds.

現在,讓我們在這個任務中引入一些并行化能力,以加快速度。在開始編寫代碼之前,我們必須在線程和多進程之間作出決定。正如你到目前為止所了解的那樣,當涉及到 IO 為瓶頸的任務時,線程就是最佳選擇。手邊的任務顯然屬于這一類,因為它是通過互聯網訪問 IMAP 服務器。因此我們將使用 threading(線程)。

我們將要使用的大部分代碼與我們在順序情況下使用的代碼是相同的。唯一的區別是,我們將 100 封電子郵件的 ID 列表拆分為 10 個較小的塊,每一塊包含 10 個 ID,然后創建 10 個線程并使用不同的塊調用函數 download_emails。我使用 Python 標準庫中的 concurrent.futures.ThreadPoolExecutor 類進行線程化處理。

import imaplib import time from concurrent.futures import ThreadPoolExecutor

IMAP_SERVER = 'imap.gmail.com' USERNAME = 'username@gmail.com' PASSWORD = 'password'

def download_emails(ids): client = imaplib.IMAP4_SSL(IMAP_SERVER) client.login(USERNAME, PASSWORD) client.select() for i in ids: print(f'Downloading mail id: {i.decode()}') _, data = client.fetch(i, '(RFC822)') with open(f'emails/{i.decode()}.eml', 'wb') as f: f.write(data 0 ) client.close() print(f'Downloaded {len(ids)} mails!')

start = time.time()

client = imaplib.IMAP4_SSL(IMAP_SERVER) client.login(USERNAME, PASSWORD) client.select() _, ids = client.search(None, 'ALL') ids = ids[0].split() ids = ids[:100] client.close()

number_of_chunks = 10 chunk_size = 10 executor = ThreadPoolExecutor(max_workers=number_of_chunks) futures = [] for i in range(number_of_chunks): chunk = ids[i*chunk_size:(i+1)*chunk_size] futures.append(executor.submit(download_emails, chunk))

for future in concurrent.futures.as_completed(futures): pass print('Time:', time.time() - start)

Time taken :: 9.841094255447388 seconds.

正如你所看到的,線程化大大加快了執行速度。

場景:使用 Scikit-Learn 進行分類

假設你有一個分類問題,想為此使用隨機森林(random forest)分類器。因為它是一種標準的、眾所周知的機器學習方法,所以我們不打算“重新發明輪子 ”,只使用 sklearn.ensemble.RandomForestClassifier。

下面的代碼段用于演示目的。我使用輔助函數 sklearn.datasets.make_classification 創建了一個分類數據集,然后在此基礎上訓練了一個 RandomForestClassifier 。此外,我正在對代碼中做核心工作的部分進行計時,以對模型進行擬合。

from sklearn.ensemble import RandomForestClassifier from sklearn import datasets import time

X, y = datasets.make_classification(n_samples=10000, n_features=50, n_informative=20, n_classes=10)

start = time.time() model = RandomForestClassifier(n_estimators=500) model.fit(X, y) print('Time:', time.time()-start)

Time taken :: 34.17733192443848 seconds.

現在,我們來看看如何減少這個算法的運行時間。我們知道這個算法在一定程度上實行并行化,但什么樣的并行化才是合適的呢?它沒有任何 IO 瓶頸;相反,這是一項非常耗費 CPU 的任務。因此,多進程將是合理的選擇。

幸運的是, sklearn 已經在這個算法中實現了多進程,我們不必從頭開始編寫。正如你在下面的代碼中所看到的那樣,我們只需提供一個參數 n_jobs ,即它應該使用的進程數量,來啟用多進程。

from sklearn.ensemble import RandomForestClassifier from sklearn import datasets import time

X, y = datasets.make_classification(n_samples=10000, n_features=50, n_informative=20, n_classes=10)

start = time.time() model = RandomForestClassifier(n_estimators=500, n_jobs=4) model.fit(X, y) print('Time:', time.time()-start)

Time taken :: 14.576200723648071 seconds.

正如預期的那樣,多進程使其運行速度提高了很多。

結論

大多數(如果不是所有的話)數據科學項目將會看到并行計算速度大幅提高。事實上,許多流行的數據科學庫已經內置了并行性,你只需啟用它即可。因此,在嘗試自己實現它之前,請先查看正在使用的庫的文檔,并檢查它是否支持并行性(順便說一句,我強烈建議你查看 dask )。如果沒有的話,希望本文能夠幫助你自己來實現并行性。

作者介紹:

Sumit 是一名計算機愛好者,很小就開始編程。目前正在德里印度理工學院(IIT Delhi)攻讀計算機科學碩士學位。除了編程之外,他還喜歡哲學、吉他、攝影和寫作。

原文鏈接:

Multiprocessing vs. Threading in Python: What Every Data Scientist Needs to Know

分享到:0收藏

上一篇:在花 100 天學習人工智能之后,我得出這 5 個結論 最后一頁下一篇:

聲明:該文章版權歸原作者所有,轉載目的在于傳遞更多信息,并不代表本網贊同其觀點和對其真實性負責。如涉及作品內容、版權和其它問題,請在30日內與本網聯系。
您閱讀這篇文章花了0
轉發這篇文章只需要1秒鐘
喜歡這篇 0
評論一下 0
凱派爾知識產權全新業務全面上線
相關文章
評論
試試以這些內容開始評論吧
登錄后發表評論
凱派爾知識產權全新業務全面上線
寧波城市站
金華城市站
×
#熱門搜索#
精選雙創服務
歷史搜索 清空

Tel:18514777506

關注微信公眾號

創頭條企服版APP

china0114.com-日韩欧美中文免费,免费视频一区,免费视频一区,国产精品色网
欧美亚洲一区二区在线观看| 欧美大片在线观看一区二区| 91精品国产乱码久久蜜臀| 欧美激情中文不卡| 喷水一区二区三区| 91福利在线免费观看| 久久久久久毛片| 日韩国产欧美在线观看| 91亚洲国产成人精品一区二三| 精品粉嫩aⅴ一区二区三区四区 | 高潮精品一区videoshd| 日韩三级高清在线| 亚洲www啪成人一区二区麻豆| 99视频在线精品| 日本一区二区久久| 国产麻豆欧美日韩一区| 欧美成人一区二区三区在线观看| 午夜日韩在线电影| 欧美无乱码久久久免费午夜一区| 亚洲品质自拍视频| 99久久婷婷国产综合精品电影 | 日本中文字幕一区二区有限公司| 欧美亚洲动漫制服丝袜| 一区二区三区在线高清| 91在线精品秘密一区二区| 国产精品久久看| 国产91高潮流白浆在线麻豆| 久久久久久久久久久久久女国产乱| 狠狠色狠狠色合久久伊人| 日韩一区二区三区免费看| 日韩精品乱码免费| 欧美日本一区二区| 偷拍亚洲欧洲综合| 在线播放91灌醉迷j高跟美女 | 国产精品一二三| 久久免费视频一区| 国产精品亚洲午夜一区二区三区 | 国产精品卡一卡二| 成人国产精品免费观看视频| 国产欧美精品一区aⅴ影院| 国产精品18久久久久久久网站| 久久人人97超碰com| 国产成人日日夜夜| 国产精品三级视频| 波多野结衣91| 亚洲美女偷拍久久| 在线看不卡av| 五月天网站亚洲| 日韩视频免费观看高清完整版 | 韩国v欧美v日本v亚洲v| 久久综合九色综合欧美就去吻| 国产毛片一区二区| 国产精品少妇自拍| 色综合天天做天天爱| 亚洲综合视频网| 在线不卡中文字幕| 激情文学综合丁香| 国产精品日韩精品欧美在线| 一本高清dvd不卡在线观看| 亚洲午夜电影在线| 日韩欧美国产午夜精品| 国产精品亚洲成人| 国产精品家庭影院| 欧美午夜不卡视频| 免费看欧美美女黄的网站| 久久影视一区二区| 不卡一卡二卡三乱码免费网站| 亚洲精品久久7777| 91麻豆精品国产91久久久| 国产曰批免费观看久久久| 国产精品毛片高清在线完整版| 一本到不卡精品视频在线观看| 午夜欧美大尺度福利影院在线看| 日韩免费观看2025年上映的电影| 国产盗摄女厕一区二区三区 | 日本国产一区二区| 午夜精品福利久久久| 精品av久久707| voyeur盗摄精品| 亚洲午夜av在线| 精品乱人伦小说| 99久久综合国产精品| 午夜精品福利一区二区蜜股av | 国产91精品精华液一区二区三区 | 成人午夜精品在线| 一区二区三区在线高清| 欧美电影免费提供在线观看| 成人免费视频免费观看| 亚洲bt欧美bt精品777| 精品成人a区在线观看| 91片黄在线观看| 热久久一区二区| 国产精品欧美精品| 欧美挠脚心视频网站| 国产精品系列在线观看| 亚洲一区二区三区视频在线播放| 精品国产成人系列| 欧洲一区在线观看| 国产精品影视天天线| 亚洲电影一级片| 日韩精品中文字幕一区二区三区| 久久99精品国产麻豆婷婷 | 日韩福利视频导航| 国产午夜精品理论片a级大结局| 色综合久久综合网97色综合 | 不卡一区二区在线| 日本不卡的三区四区五区| 国产精品午夜在线观看| 7777精品伊人久久久大香线蕉完整版| 高清国产一区二区| 日韩精品每日更新| 亚洲欧美自拍偷拍色图| 日韩欧美中文字幕制服| 91丨porny丨国产| 寂寞少妇一区二区三区| 一区二区三区精品视频| 久久久久久久电影| 欧美日韩国产区一| 99精品热视频| 国产精品九色蝌蚪自拍| 精品国免费一区二区三区| 欧洲精品一区二区| 成人性视频网站| 久久精品99国产国产精| 亚洲午夜久久久久久久久久久 | 亚洲电影在线播放| 欧美韩国日本不卡| 欧美一区二区三区视频免费播放| 91丨porny丨首页| 国产成人免费av在线| 亚洲激情欧美激情| 中文字幕国产一区| 精品嫩草影院久久| 欧美日本韩国一区二区三区视频| 99re6这里只有精品视频在线观看 99re8在线精品视频免费播放 | 精品一区二区三区蜜桃| 亚洲成人激情综合网| 亚洲欧美激情小说另类| 日本一区二区视频在线| 精品福利视频一区二区三区| 91精品国产91热久久久做人人 | 亚洲乱码国产乱码精品精可以看 | 人妖欧美一区二区| 亚洲一区av在线| 1区2区3区精品视频| 国产欧美一区二区三区网站 | 亚洲欧美综合色| 久久久美女毛片| 日韩欧美综合一区| 91精品国产麻豆| 欧美精品久久99| 欧美日韩久久一区| 欧美网站一区二区| 欧美亚洲自拍偷拍| 91黄视频在线观看| 一本大道久久精品懂色aⅴ| 99在线热播精品免费| av网站一区二区三区| www.欧美日韩| 99精品在线免费| 99精品久久只有精品| 成人av电影免费在线播放| 成人伦理片在线| 成人精品在线视频观看| 成人午夜精品一区二区三区| 成人久久18免费网站麻豆 | 亚洲欧洲av一区二区三区久久| 欧美国产日本韩| 国产精品你懂的在线| 中文字幕亚洲电影| 亚洲人成在线观看一区二区| 亚洲女人****多毛耸耸8| 亚洲精品免费在线播放| 亚洲制服欧美中文字幕中文字幕| 夜夜操天天操亚洲| 亚洲bt欧美bt精品777| 日韩在线卡一卡二| 久久成人18免费观看| 国产在线视频精品一区| 国产成人精品午夜视频免费| 成人综合婷婷国产精品久久免费| caoporn国产精品| 色综合久久综合网| 欧美日韩一区二区不卡| 欧美一级午夜免费电影| 欧美精品一区男女天堂| 国产色91在线| 中文字幕永久在线不卡| 一区二区欧美国产| 日韩福利电影在线| 国内精品视频666| 成人av在线播放网站| 色94色欧美sute亚洲13| 在线播放欧美女士性生活| xnxx国产精品| 日韩毛片精品高清免费| 亚洲国产一区视频| 蜜桃一区二区三区在线| 懂色av一区二区三区免费观看| 色婷婷亚洲婷婷|