[Python基礎語法] 專題八.多線程編程之thread和threading

      網友投稿 945 2022-05-29

      一. 線程和進程的概念

      1.為什么引入多線程編程?

      在多線程(Multithreaded,MT)編程出現之前,電腦程序的運行由一個執行序列組成,執行序列按順序在主機的中央處理器CPU中運行。即使整個程序由多個相互獨立無關的子任務組成,程序都會順序執行。

      由于并行處理可以大幅度地提升整個任務的效率,故引入多線程編程。

      多線程中任務具有以下特點:

      (1) 這些任務的本質是異步的,需要有多個并發事務;

      (2) 各個事務的運行順序可以是不確定的、隨機的、不可預測的。

      這樣的編程任務可以分成多個執行流,每個流都有一個要完成的目標。再根據不同的應用,這些子任務可能都要計算出一個中間結果,用于合并得到最后的結果。

      2.什么是進程?

      計算機程序只不過是磁盤中可執行的二進制(或其他類型)的數據。它們只有在被讀取到內存中,被操作系統調用時才開始它們的生命周期。

      進程(亦稱為重量級進程)是程序的一次執行。每個進程都有自己的地址空間、內存、數據棧及其他記錄其運行軌跡的輔助數據。操作系統管理在其上運行所有的進程,并為這些進程公平分配時間、進程也可以通過fork和spawn操作來完成其他的任務。

      不過進程有自己的內存空間,數據棧等,所以只能使用進程間通訊(interprocess communication, IPC),而不能直接共享信息。

      3.什么是線程?

      線程(亦稱為輕量級進程)跟進程有些相似,不同的是:所有的線程運行在同一個進程中,共享相同的運行環境。它們可以被想象成是在主進程或“主線程”中并行運行的“迷你進程”。

      線程有開始,順序執行和結束三部分。它有一個自己的指令指針,記錄自己運行到什么地方。線程的運行可能被搶占(中斷)或暫時的被掛起(睡眠),讓其他線程運行,這叫做讓步。

      一個進程中的各個線程之間共享同一片數據空間,所以線程之間可以比進程之間更方便地共享數據以及相互通訊。線程一般都是并發執行的,正是由于這種并行和數據共享的機制使得多個任務的合作變成可能。

      實際上,在單CPU的系統中,真正的并發是不可能的,每個線程會被安排成每次只運行一小會,然后就把CPU讓出來,讓其他的線程去運行。在進程的整個運行過程中,每個線程都只做自己的事,在需要的時候跟其他的線程共享運行的結果。

      當然,這樣的共享并不是完全沒有危險的。如果多個線程共同訪問同一片數據,則由于數據訪問的順序不同,有可能導致數據結果的不一致的問題,即競態條件(race condition)。同樣,大多數線程庫都帶有一些列的同步原語,來控制線程的執行和數據的訪問。

      另一個需要注意的是由于有的函數會在完成之前阻塞住,在沒有特別為多線程做修改的情況下,這種“貪婪”的函數會讓CPU的時間分配有所傾斜,導致各個線程分配到的運行時間可能不盡相同,不盡公平。

      4.簡述進程和線程的區別

      參考下面三篇文章:

      進程和線程關系及區別 -?yaosiming2011

      進程與線程的區別 -?flashsky

      應屆生經典面試題:說說進程與線程的區別與聯系 -?way_testlife

      二. Python線程和全局解釋器鎖

      1.全局解釋器鎖(GIL)

      Python代碼的執行由Python虛擬機(也叫解釋器主循環)來控制。Python在設置之初就考慮到要在主循環中,同時只有一個線程在執行,就像單CPU的系統中運行多個進程那樣,內存中可以存放多個程序,但任意時刻,只有一個程序在CPU中運行。同樣,雖然Python解釋器可以“運行”多個線程,但任意時刻,只有一個線程在解釋器中運行。

      對Python虛擬機的訪問由全局解釋器鎖(global interpreter lock,GIL)來控制,正是這個鎖能保證同一時刻只有一個線程在運行。在多線程環境中,Python虛擬機按一下方式執行:

      (1) 設置GIL

      (2) 切換到一個線程去運行

      (3) 運行:

      a. 指定數量的字節碼的指令,或者

      b. 線程主動讓出控制(可以調用time.sleep(0))

      (4) 把線程設置為睡眠狀態

      (5) 解鎖GIL

      (6) 再次重復以上所有步驟

      在調用外部代碼(如C/C++擴展函數)的時候,GIL將會被鎖定,直到這個函數結束為止(由于這期間沒有Python的字節碼被運行,所以不會做線程切換)。編寫擴展的程序員可以主動解鎖GIL。不過Python開發人員則不用擔心在這些情況下你的Python代碼會被鎖住。

      對源代碼,解釋器主循環和GIL感興趣的人,可以看看Python/ceval.c文件。

      2.退出線程

      當一個線程結束計算,它就退出了。線程可以調用thread.exit()之類的退出函數,也可以使用Python退出進程的標準方法,如sys.exit()或拋出一個SystemExit異常等。不過,你不可以直接殺掉Kill一個線程。

      后面會講述兩個與線程相關的模塊,在這兩個模塊中,該書中不建議使用thread模塊。主要原因是當主線程退出的時候,其他所有線程沒有被清除就退出了。而threading模塊就能確保所有“重要的”子線程都退出后,進程才會結束。

      主線程應該是一個好的管理者,它要了解每個線程都要做些什么事,線程都需要什么數據和什么參數,以及在線程結束的時候,它們都提供了什么結果。這樣,主線程就可以把各個線程的結果組成一個有意義的最后結果。

      在Python2.7交互式解釋器中導入import thread沒有報錯即表示線程可用。

      3.沒有線程的例子

      使用time.sleep()函數來演示線程的工作,這個例子主要為后面線程做對比。time.sleep()需要一個浮點型的參數,來指定“睡眠”的時間(單位秒)。這就相當于程序的運行會被掛起指定的時間。

      代碼解釋:兩個計時器,loop0睡眠4秒,loop1()睡眠2秒,它們是在一個進程或者線程中,順序地執行loop0()和loop1(),那總運行時間為6秒。有可能啟動過程中會再花些時間。

      from time import sleep, ctime def loop0(): print 'Start loop 0 at:', ctime() sleep(4) print 'Loop 0 done at:', ctime() def loop1(): print 'Start loop 1 at:', ctime() sleep(2) print 'Loop 1 done at:', ctime() def main(): print 'Starting at:', ctime() loop0() loop1() print 'All done at:', ctime() if __name__ == '__main__': main()

      代碼的運行結果如下圖所示,它將和后面的并行代碼做對比。

      4.避免使用thread模塊

      Python提供了幾個用于多線程編程的模塊,包括thread、threading和Queue等。

      (1) thread模塊: 允許程序員創建和管理線程,它提供了基本的線程和鎖的支持。

      (2) threading模塊: 允許程序員創建和管理線程,它提供了更高級別,更強的線程管理的功能。

      (3) Queue模塊: 允許用戶創建一個可用于多個線程間共享數據的隊列數據結構。

      下面簡單分析為什么需要避免使用thread模塊?

      (1) 首先更高級別的threading模塊更為先進,對線程的支持更為完善,而且使用thread模塊里的屬性有可能會與threading出現沖突。

      (2) 其次,低級別的thread模塊的同步原語很少(實際只有一個),而threading模塊則有很多。

      (3) 另一個原因是thread對你的進程什么時候應該結束完全沒有控制,當主線程結束時,所有的線程都會被強制結束掉,沒有警告也不會有正常的清除工作。而threading模塊能確保重要的子線程退出后進程才退出。

      當然,為了你更好的理解線程,還是會對thread進行講解。但是我們只建議那些有經驗的專家想訪問線程的底層結構時,才使用thread模塊。而如果可以,你的第一個線程程序應盡可能使用threading等高級別的模塊。

      三. thread模塊

      1.基礎知識

      首先來看看thread模塊都提供了些什么。除了產生線程外,thread模塊也提供了基本的同步數據結構鎖對象(lock object,也叫原語鎖、簡單鎖、互斥鎖、互斥量、二值信號量)。同步原語與線程的管理是密不可分的。

      常用的線程模塊函數

      LockType類型鎖對象方

      start_new_thread()函數是thread模塊的一個關鍵函數,它的語法和內建的apply()函數一樣,其參數為:函數,函數的參數以及可選的關鍵字的參數。不同的是,函數不是在主線程里運行,而是產生一個新的線程來運行這個函數。

      2.Thread模塊實現代碼

      現在實現一個線程的代碼,與前面沒有線程總運行時間為6秒的進行對比。

      import thread from time import sleep, ctime def loop0(): print 'Start loop 0 at:', ctime() sleep(4) print 'Loop 0 done at:', ctime() def loop1(): print 'Start loop 1 at:', ctime() sleep(2) print 'Loop 1 done at:', ctime() def main(): try: print 'Starting at:', ctime() thread.start_new_thread(loop0, ()) thread.start_new_thread(loop1, ()) sleep(6) print 'All done at:', ctime() except Exception,e: print 'Error:',e finally: print 'END\n' if __name__ == '__main__': main()

      代碼解釋:

      使用thread模塊提供簡單的額多線程機制。loop0和loop1并發地被執行(顯然,短的那個先結束),總的運行時間為最慢的那個線程的運行時間,而不是所有的線程的運行時間之和。start_new_thread()要求一定要有前兩個參數,即使運行的函數不要參數,也要傳一個空的元組。

      由于采用Python IDLE運行總是報錯Runtime,而且已經設置了sleep(6)。運行一個線程勉強能運行,兩個線程無論是thread或threading都報錯,估計環境配置問題。

      最后采用Cygwin Terminal模擬Linux下運行程序。可以發現loop1和loop0是并發執行的,其中loop1先結束運行2秒,而loop0運行4秒。

      同時程序主函數中多了個sleep(6),為什么要加這一句話呢?

      因為如果我們沒有讓主線程停下來,那主線程就會運行下一條語句,顯示“All done”,然后就關閉運行著loop0和loop1的兩個線程,退出了。

      我們沒有寫讓主線程停下來等所有子線程結束后再繼續運行的代碼,這就是前面所說的需要同步的原因。在這里,我們使用sleep(6)作為同步機制。設置6秒,兩個線程一個4秒(53-57),一個2秒(53-55),在主線程等待6秒(53-59)后應該已經結束了。

      cygwin需要用到的常見用法包括,也可以安裝VIM編輯器:

      cd c: ? ?       ?進入 'c:' 目錄,空格用'\ '轉義字符

      pwd ? ? ?       ? 顯示工作路徑

      ls ? ? ? ?       查看目錄中的文件

      file test.py ? ? ? ? ? ? ? ? 查看文件內容

      python test.py ? ? ? ? ?運行python程序

      配置方法見:http://blog.sina.com.cn/s/blog_691ebcfc0101lgme.html

      -見:http://pan.baidu.com/s/1jGYEtro

      3.線程加鎖方法

      那么,有什么好的管理線程的方法呢?而不是在主線程里做個額外的延時6秒操作。因為總的運行時間并不比單線程的代碼少;而且使用sleep()函數做線程的同步操作是不可靠的;如果循環的執行時間不能事先確定的話,這可能會造成主線程過早或過晚的退出。

      這就需要引入鎖的概念。下面代碼執行loop函數,與前面代碼的區別是不用為線程什么時候結束再做額外的等待了。使用鎖之后,可以在兩個線程都退出后,馬上退出。

      #coding=utf-8 import thread from time import sleep, ctime loops = [4,2] #等待時間 #鎖序號 等待時間 鎖對象 def loop(nloop, nsec, lock): print 'start loop', nloop, 'at:', ctime() sleep(nsec) print 'loop', nloop, 'done at:', ctime() lock.release() #解鎖 def main(): print 'starting at:', ctime() locks =[] nloops = range(len(loops)) #以loops數組創建列表并賦值給nloops for i in nloops: lock = thread.allocate_lock() #創建鎖對象 lock.acquire() #獲取鎖對象 加鎖 locks.append(lock) #追加到locks[]數組中 #執行多線程 (函數名,函數參數) for i in nloops: thread.start_new_thread(loop,(i,loops[i],locks[i])) #循環等待順序檢查每個所都被解鎖才停止 for i in nloops: while locks[i].locked(): pass print 'all end:', ctime() if __name__ == '__main__': main()

      運行結果如下:

      Starting at: Tue Dec ?8 21:57:56 2015

      Start loop 0?at:?Tue Dec ?8 21:57:56 2015

      Start loop 1 at: Tue Dec ?8 21:57:56 2015

      Loop 1 done at: Tue Dec ?8 21:57:58 2015

      Loop 0 done at: Tue Dec ?8 21:58:00 2015

      All end: Tue Dec ?8 21:58:00 2015

      我們在函數中記錄下循環的號碼和睡眠的時間,同時每個線程都會被分配一個事先已經獲得的鎖,在sleep()的時間到了之后就釋放相應的鎖以通知住線程,這個線程已經結束了。

      (1) loops[4, 2]定義睡眠時間 nloops=range(len(loops))創建列表[0, 1] 號碼;

      (2) 調用thread.allocate_lock()函數創建一個鎖的列表,并分別調用各個鎖的acquire()函數獲得鎖對象。獲得鎖表示“把鎖鎖上”,并放到鎖列表locks中;

      (3) 再循環創建線程,調用thread.start_new_thread(loop,(i,loops[i],locks[i]))。參數對應線程循環號、睡眠時間和鎖。

      (4) 在線程結束時,需要做解鎖操作,調用lock.release()函數;

      (5) 最后一個循環是坐在那一直等待(達到暫停主線程的目的),直到兩個鎖都被解鎖才繼續運行。它是順序檢查每個鎖,主線程需不停地對所有鎖進行檢查直到都釋放。

      為什么我們不在創建鎖的循環里創建線程呢?一方面是想實現線程的同步,所以要讓“所有的馬同時沖出柵欄”;另外獲取鎖要花些時間,如果線程退出太快,可能導致還沒有獲得鎖,線程就已經結束了。

      最后再強調下,thread模塊僅僅了解就行,你應該使用更高級別的threading等。

      四. threading模塊

      threading模塊不僅提供了Thread類,還提供了各種非常好用的同步機制。如下表列出了threading模塊里所有的對象。

      1.守護線程

      其中thread模塊需要避免的一個原因是:它不支持守護線程。當主線程退出時,所有的子線程不論它們是否還在工作,都會被強行退出。有時我們并不期望這種行為,這就引入了守護線程的概念。

      Threading模塊支持守護線程,它們工作流程如下:守護線程一般是一個等待客戶請求的服務器,如果沒有客戶提出請求,它就在那等著。如果你設定一個線程為守護線程,就表示你在說這個線程是不重要的,在進程退出時,不用等待這個線程退出,正如網絡編程中服務器線程運行在一個無限循環中,一般不會退出的。

      如果你的主線程要退出的時候,不用等待那些子線程完成,那就設定這些線程的daemon屬性。即,線程開始(調用thread.start())之前,調用setDaemon()函數設定線程的daemon標準(thread.setDaemon(True))就表示這個線程“不重要”。

      如果你想要等待子線程完成再退出,那就什么都不用做,或者顯示地調用thread.setDaemon(False)以保證其daemon標志位False。你可以調用thread.isDaemon()函數來判斷其daemon標志的值。

      新的子線程會繼承其父線程的daemon標志,整個Python會在所有的非守護線程退出后才會結束,即進程中沒有非守護線程存在的時候才結束。

      2.Thread類

      threading的Thread類是你主要的運行對象。它有很多thread模塊里沒有的函數。

      用Thread類,可以用多種方法來創建線程。現在介紹三種方法,你可以選擇自己喜歡或社和自己程序的方法(通常選擇最后一個):

      (1) 創建一個Thread的實例,傳給它一個函數;

      (2) 創建一個Thread的實例,傳給它一個可調用的類對象;

      (3) 從Thread派生出一個子類,創建一個這個子類的實例。

      3.創建Thread實例,傳給它一個函數

      這第一個例子使用方法一,把函數及其參數如上面Thread模塊的例子一樣傳進去。主要變化包括:添加了一些Thread對象;在實例化每個Thread對象時,把函數(target)和參數(args)都傳進去,得到返回的Thread實例。

      實例化一個Thread調用Thread()方法與調用thread.start_new_thread()之間的最大區別是:新的線程不會立即開始。在你創建線程對象,但不想馬上開始運行線程的時候,這是一個很有用的同步特性。

      threading模塊的Thread類有一個join()函數,允許主線程等待線程的結束。

      #coding=utf-8 import threading from time import sleep, ctime loops = [4,2] #睡眠時間 def loop(nloop, nsec): print 'Start loop', nloop, 'at:', ctime() sleep(nsec) print 'Loop', nloop, 'done at:', ctime() def main(): print 'Starting at:', ctime() threads = [] nloops = range(len(loops)) #列表[0,1] #創建線程 for i in nloops: t = threading.Thread(target=loop,args=(i,loops[i])) threads.append(t) #開始線程 for i in nloops: threads[i].start() #等待所有結束線程 for i in nloops: threads[i].join() print 'All end:', ctime() if __name__ == '__main__': main()

      運行結果如下圖所示:其中loop0和loop1并行執行,loop1先結束共執行2秒,loop0后結束執行4秒,總共運行時間4秒。注意:此時Start是分行顯示了。

      所有的線程都創建之后,再一起調用start()函數啟動線程,而不是創建一個啟動一個。而且,不用再管理一堆鎖(分配鎖、獲得鎖、釋放鎖、檢查鎖的狀態等),只要簡單地對每個線程調用join()函數就可以了。

      join()會等到線程結束,或者在給了timeout參數的時候,等到超時為止。使用join()比使用一個等待鎖釋放的無限循環清楚一些(也稱“自旋鎖”)。

      join()的另一個比較重要的方法是它可以完全不用調用。一旦線程啟動后,就會一直運行,直到線程的函數結束,退出為止。

      如果你的主線程除了等線程結束外,還有其他的事情要做(如處理或等待其他的客戶請求),那就不用調用join(),只有在你要等待線程結束的時候才要調用join()。

      4.創建一個Thread實例,傳給它一個可調用的類對象

      這是第二個方法,與傳一個函數很相似,但它是傳一個可調用的類的實例供線程啟動的時候執行,這是多線程編程的一個更為面向對象的方法。相對于一個或幾個函數來說,由于類對象里可以使用類請打的功能,可以保存更多的信息,這種方法更為靈活。

      #coding=utf-8 import threading from time import sleep, ctime loops = [4,2] #睡眠時間 class ThreadFunc(object): def __init__(self, func, args, name=''): self.name=name self.func=func self.args=args def __call__(self): apply(self.func, self.args) def loop(nloop, nsec): print "Start loop", nloop, 'at:', ctime() sleep(nsec) print 'Loop', nloop, 'done at:', ctime() def main(): print 'Starting at:', ctime() threads=[] nloops = range(len(loops)) #列表[0,1] for i in nloops: #調用ThreadFunc類實例化的對象,創建所有線程 t = threading.Thread( target=ThreadFunc(loop, (i,loops[i]), loop.__name__) ) threads.append(t) #開始線程 for i in nloops: threads[i].start() #等待所有結束線程 for i in nloops: threads[i].join() print 'All end:', ctime() if __name__ == '__main__': main()

      運行結果如下圖所示,傳遞的是一個可調用的類,而不是一個函數。

      創建Thread對象時會實例化一個可調用類ThreadFunc的類對象。這個類保存了函數的參數,函數本身以及函數的名字字符串。

      構造器__init__()函數:初始化賦值工作;

      特殊函數__call__():由于我們已經有要用的參數,所以就不用再傳到Thread()構造器中;由于我們有一個參數的元組,這時要在代碼中使用apply()函數。

      apply(func?[,?args?[,?kwargs?]])?函數:用于當函數參數已經存在于一個元組或字典中時,間接地調用函數。args是一個包含將要提供給函數的按位置傳遞的參數的元組。如果省略了args,任何參數都不會被傳遞,kwargs是一個包含關鍵字參數的字典。

      def say(a, b): print a, b apply(say,("Eastmount", "Python線程")) # 輸出 # Eastmount Python線程

      5.Thread派生一個子類,創建這個子類的實例

      這是第三個方法,主要是如何子類化Thread類,該方法與第二個方法類似。其中創建子類方法和調用類對象方法的最重要改變是:

      (1) MyThread子類的構造器一定要先調用基類的構造器;

      (2) 之前特殊函數__call__()在子類中,名字要改為run()。

      #coding=utf-8 import threading from time import sleep, ctime loops = [4,2] #睡眠時間 class MyThread(threading.Thread): def __init__(self, func, args, name=''): threading.Thread.__init__(self) self.name=name self.func=func self.args=args def run(self): #run()函數 apply(self.func, self.args) def loop(nloop, nsec): print "Start loop", nloop, 'at:', ctime() sleep(nsec) print 'Loop', nloop, 'done at:', ctime() def main(): print 'Starting at:', ctime() threads=[] nloops = range(len(loops)) #列表[0,1] for i in nloops: #子類MyThread實例化,創建所有線程 t = MyThread(loop, (i,loops[i]), loop.__name__) threads.append(t) #開始線程 for i in nloops: threads[i].start() #等待所有結束線程 for i in nloops: threads[i].join() print 'All end:', ctime() if __name__ == '__main__': main()

      [Python基礎語法] 專題八.多線程編程之thread和threading

      運行結果如下圖所示:

      6.線程運行斐波那契、階乘和加和

      需要在MyThread類中加入輸出信息,除了使用apply()函數運行斐波那契、接觸和加和函數外,還把結果保存到實現的self.res屬性中,并創建一個函數getResult()得到結果。換句話說,子類方法更加靈活。

      #coding=utf-8 import threading from time import sleep, ctime class MyThread(threading.Thread): def __init__(self, func, args, name=''): threading.Thread.__init__(self) self.name=name self.func=func self.args=args def getResult(self): return self.res def run(self): #run()函數 print "Starting", self.name, 'at:', ctime() self.res = apply(self.func, self.args) print self.name, 'finished at:', ctime()

      在threadfunc.py文件中調用前面定義的Thread子類,myThread.py中的MyThread類。由于這些函數運行得很快(斐波那契函數運行慢些),使用sleep()函數比較它們的時間。實際工作中不需要添加sleep()函數。

      #coding=utf-8 from myThread import MyThread #myThread.py文件中MyThread類 from time import sleep, ctime #斐波那契函數 def fib(x): sleep(0.005) if x < 2: return 1 return (fib(x-2) + fib(x-1)) #階乘函數 factorial calculation def fac(x): sleep(0.1) if x < 2: return 1 return (x * fac(x-1)) #求和函數 def sum(x): sleep(0.1) if x < 2: return 1 return (x + sum(x-1)) funcs = [fib, fac, sum] n = 14 def main(): nfuncs = range(len(funcs)) print '*****單線程方法*****' for i in nfuncs: print 'Starting', funcs[i].__name__, 'at:', ctime() print funcs[i](n) print 'Finished', funcs[i].__name__, 'at:', ctime() print '*****結束單線程*****' print ' ' print '*****多線程方法*****' threads = [] for i in nfuncs: #調用MyThread類實例化的對象,創建所有線程 t = MyThread(funcs[i], (n,), funcs[i].__name__) threads.append(t) #開始線程 for i in nfuncs: threads[i].start() #等待所有結束線程 for i in nfuncs: threads[i].join() print threads[i].getResult() print '*****結束多線程*****' if __name__ == '__main__': main()

      運行結果如下圖所示,單線程運行10s,多線程運行6s。

      至于Queue模塊這里就不再敘述了。

      下面介紹除了各種同步對象和線程對象外,threading模塊還提供了一些函數。

      最后給出一些多線程編程中可能用得到的模塊。

      總之,這篇文章主要是參考《Python核心編程》的,希望文章對你有所幫助~尤其是初學Python編程的,同時為后面我學習多線程的爬蟲或分布式爬蟲做鋪墊。這篇文章花了自己一些時間,寫到半夜;寫文不易,且看且珍惜吧!勿噴~

      2014年分享的文章,原文地址:https://blog.csdn.net/Eastmount/article/details/39854689

      希望能與大家一起在華為云社區共同進步

      (By:Eastmount 2021-07-21 夜于武漢)

      Python 任務調度 多線程

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:JS數據結構與算法總結 (第一篇)
      下一篇:學習索引結構的一些案例——Jeff Dean在SystemML會議上發布的論文(下)
      相關文章
      亚洲AV无码成人网站在线观看| 91麻豆精品国产自产在线观看亚洲 | 亚洲综合成人婷婷五月网址| 久久亚洲精品中文字幕| 亚洲女同成av人片在线观看| xvideos亚洲永久网址| 日韩欧美亚洲中文乱码| 亚洲精品国产首次亮相| 亚洲欧洲日产国码久在线| 亚洲日韩国产二区无码 | 亚洲人成网男女大片在线播放| 亚洲欧洲日韩综合| 亚洲国产韩国一区二区| 亚洲人妖女同在线播放| 亚洲精品二三区伊人久久| 亚洲一区欧洲一区| 亚洲国产精品久久人人爱| 亚洲国产精品久久人人爱| 亚洲日本乱码卡2卡3卡新区| 亚洲综合无码无在线观看| 久久久久久亚洲精品影院| 亚洲欧美日韩国产精品一区| 亚洲国产欧洲综合997久久| 国产精品亚洲一区二区在线观看 | 免费观看亚洲人成网站| 亚洲精品和日本精品| 国产亚洲成归v人片在线观看| 国产亚洲人成无码网在线观看| 久久久久亚洲精品成人网小说| 亚洲日本中文字幕| 亚洲国产av高清无码| 亚洲午夜福利在线视频| 亚洲AV无码专区在线厂| 亚洲第一区精品观看| 亚洲宅男天堂在线观看无病毒| 久久精品亚洲视频| 亚洲av成人综合网| 亚洲AV无码国产一区二区三区| 亚洲毛片不卡av在线播放一区| 亚洲欧洲日产国码av系列天堂| 亚洲综合一区二区国产精品|