日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS多线程详解:实践篇

發布時間:2024/1/1 编程问答 58 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS多线程详解:实践篇 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

iOS多線程實踐中,常用的就是子線程執行耗時操作,然后回到主線程刷新UI。在iOS中每個進程啟動后都會建立一個主線程(UI線程),這個線程是其他線程的父線程。由于在iOS中除了主線程,其他子線程是獨立于Cocoa Touch的,所以只有主線程可以更新UI界面。iOS多線程開發實踐方式有4種,分別為Pthreads、NSThread、GCD、NSOperation,下面分別講一講各自的使用方式,以及優缺點。

pthread: 跨平臺,適用于多種操作系統,可移植性強,是一套純C語言的通用API,且線程的生命周期需要程序員自己管理,使用難度較大,所以在實際開發中通常不使用。 NSThread: 基于OC語言的API,使得其簡單易用,面向對象操作。線程的聲明周期由程序員管理,在實際開發中偶爾使用。 GCD: 基于C語言的API,充分利用設備的多核,旨在替換NSThread等線程技術。線程的生命周期由系統自動管理,在實際開發中經常使用。 NSOperation: 基于OC語言API,底層是GCD,增加了一些更加簡單易用的功能,使用更加面向對象。線程生命周期由系統自動管理,在實際開發中經常使用。

Pthreads

引自 維基百科 實現POSIX 線程標準的庫常被稱作Pthreads,一般用于Unix-like POSIX 系統,如Linux、 Solaris。但是Microsoft Windows上的實現也存在,例如直接使用Windows API實現的第三方庫pthreads-w32;而利用Windows的SFU/SUA子系統,則可以使用微軟提供的一部分原生POSIX API。

其實,這就是一套在很多操作系統上都通用的多線程API,所以移植性很強,基于C封裝的一套線程框架,iOS上也是適用的。

Pthreads創建線程

- (void)onThread {// 1. 創建線程: 定義一個pthread_t類型變量pthread_t thread;// 2. 開啟線程: 執行任務pthread_create(&thread, NULL, run, NULL);// 3. 設置子線程的狀態設置為detached,該線程運行結束后會自動釋放所有資源pthread_detach(thread); }void * run(void *param) {NSLog(@"%@", [NSThread currentThread]);return NULL; }

打印結果: 2018-03-16 11:06:12.298115+0800 ocgcd[13744:5710531] <NSThread: 0x1c026f100>{number = 4, name = (null)}

如果出現'pthread_create' is invalid in C99報錯,原因是沒有導入#import <pthread.h>

——pthread_create(&thread, NULL, run, NULL); 中各項參數含義:——

  • 第一個參數&thread是線程對象,指向線程標識符的指針
  • 第二個是線程屬性,可賦值NULL
  • 第三個run表示指向函數的指針(run對應函數里是需要在新線程中執行的任務)
  • 第四個是運行函數的參數,可賦值NULL

Pthreads其他相關方法

  • pthread_create():創建一個線程
  • pthread_exit():終止當前線程
  • pthread_cancel():中斷另外一個線程的運行
  • pthread_join():阻塞當前的線程,直到另外一個線程運行結束
  • pthread_attr_init():初始化線程的屬性
  • pthread_attr_setdetachstate():設置脫離狀態的屬性(決定這個線程在終止時是否可以被結合)
  • pthread_attr_getdetachstate():獲取脫離狀態的屬性
  • pthread_attr_destroy():刪除線程的屬性
  • pthread_kill():向線程發送一個信號

Pthreads常用函數與功能

  • pthread_t

pthread_t用于表示Thread ID,具體內容根據實現的不同而不同,有可能是一個Structure,因此不能將其看作為整數。

  • pthread_equal

pthread_equal函數用于比較兩個pthread_t是否相等。

int pthread_equal(pthread_t tid1, pthread_t tid2)
  • pthread_self

pthread_self函數用于獲得本線程的thread id。

pthread _t pthread_self(void);

Pthreads實現互斥鎖

鎖可以被動態或靜態創建,可以用宏PTHREAD_MUTEX_INITIALIZER來靜態的初始化鎖,采用這種方式比較容易理解,互斥鎖是pthread_mutex_t的結構體,而這個宏是一個結構常量,如下可以完成靜態的初始化鎖:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;也可以用pthread_mutex_init函數動態的創建,函數原型如:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr)

總共有100張火車票,開啟兩個線程,北京和上海兩個窗口同時賣票,賣一張票就減去庫存,使用鎖,保證北京和上海賣票的庫存是一致的。實現如下。

#import "ViewController.h" #include <pthread.h>@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view, typically from a nib.[self onThread]; }pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; NSMutableArray *tickets;- (void)onThread {tickets = [NSMutableArray array];//生成100張票for (int i = 0; i < 100; i++) {[tickets addObject:[NSNumber numberWithInt:i]];}//線程1 北京賣票窗口// 1. 創建線程1: 定義一個pthread_t類型變量pthread_t thread1;// 2. 開啟線程1: 執行任務pthread_create(&thread1, NULL, run, NULL);// 3. 設置子線程1的狀態設置為detached,該線程運行結束后會自動釋放所有資源pthread_detach(thread1);//線程2 上海賣票窗口// 1. 創建線程2: 定義一個pthread_t類型變量pthread_t thread2;// 2. 開啟線程2: 執行任務pthread_create(&thread2, NULL, run, NULL);// 3. 設置子線程2的狀態設置為detached,該線程運行結束后會自動釋放所有資源pthread_detach(thread2);}void * run(void *param) {while (true) {//鎖門,執行任務pthread_mutex_lock(&mutex);if (tickets.count > 0) {NSLog(@"剩余票數%ld, 賣票窗口%@", tickets.count, [NSThread currentThread]);[tickets removeLastObject];[NSThread sleepForTimeInterval:0.2];}else {NSLog(@"票已經賣完了");//開門,讓其他任務可以執行pthread_mutex_unlock(&mutex);break;}//開門,讓其他任務可以執行pthread_mutex_unlock(&mutex);}return NULL; }@end

打印結果: 2018-03-16 11:47:01.069412+0800 ocgcd[13758:5723862] 剩余票數100, 賣票窗口<NSThread: 0x1c0667600>{number = 5, name = (null)} 2018-03-16 11:47:01.272654+0800 ocgcd[13758:5723863] 剩余票數99, 賣票窗口<NSThread: 0x1c0672b40>{number = 6, name = (null)} 2018-03-16 11:47:01.488456+0800 ocgcd[13758:5723862] 剩余票數98, 賣票窗口<NSThread: 0x1c0667600>{number = 5, name = (null)} 2018-03-16 11:47:01.691334+0800 ocgcd[13758:5723863] 剩余票數97, 賣票窗口<NSThread: 0x1c0672b40>{number = 6, name = (null)} … 2018-03-16 11:47:12.110962+0800 ocgcd[13758:5723862] 剩余票數46, 賣票窗口<NSThread: 0x1c0667600>{number = 5, name = (null)} 2018-03-16 11:47:12.316060+0800 ocgcd[13758:5723863] 剩余票數45, 賣票窗口<NSThread: 0x1c0672b40>{number = 6, name = (null)} 2018-03-16 11:47:12.529002+0800 ocgcd[13758:5723862] 剩余票數44, 賣票窗口<NSThread: 0x1c0667600>{number = 5, name = (null)} 2018-03-16 11:47:12.731459+0800 ocgcd[13758:5723863] 剩余票數43, 賣票窗口<NSThread: 0x1c0672b40>{number = 6, name = (null)} … 2018-03-16 11:47:21.103237+0800 ocgcd[13758:5723862] 剩余票數2, 賣票窗口<NSThread: 0x1c0667600>{number = 5, name = (null)} 2018-03-16 11:47:21.308605+0800 ocgcd[13758:5723863] 剩余票數1, 賣票窗口<NSThread: 0x1c0672b40>{number = 6, name = (null)} 2018-03-16 11:47:21.511062+0800 ocgcd[13758:5723862] 票已經賣完了 2018-03-16 11:47:21.511505+0800 ocgcd[13758:5723863] 票已經賣完了

對鎖的操作主要包括加鎖pthread_mutex_lock()、解鎖pthread_mutex_unlock()和測試加鎖pthread_mutex_trylock()三個。 pthread_mutex_trylock()語義與pthread_mutex_lock()類似,不同的是在鎖已經被占據時返回EBUSY而不是掛起等待。

NSThread

NSThread是面向對象的,封裝程度最小最輕量級的,使用更靈活,但要手動管理線程的生命周期、線程同步和線程加鎖等,開銷較大。 NSThread的基本使用比較簡單,可以動態創建初始化NSThread對象,對其進行設置然后啟動;也可以通過NSThread的靜態方法快速創建并啟動新線程;此外NSObject基類對象還提供了隱式快速創建NSThread線程的performSelector系列類別擴展工具方法;NSThread還提供了一些靜態工具接口來控制當前線程以及獲取當前線程的一些信息。

NSThread創建線程

NSThread有三種創建方式:

  • initWithTarget方式,先創建線程對象,再啟動
- (void)onThread {// 創建并啟動NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];// 設置線程名[thread setName:@"thread1"];// 設置優先級,優先級從0到1,1最高[thread setThreadPriority:0.9];// 啟動[thread start]; }- (void)run {NSLog(@"當前線程%@", [NSThread currentThread]); }

打印結果: 2018-03-16 13:47:25.133244+0800 ocgcd[13811:5776836] 當前線程<NSThread: 0x1c0264480>{number = 4, name = thread1}

  • detachNewThreadSelector顯式創建并啟動線程
- (void)onThread {// 使用類方法創建線程并自動啟動線程[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; }- (void)run {NSLog(@"當前線程%@", [NSThread currentThread]); }

打印結果: 2018-03-16 13:49:34.620546+0800 ocgcd[13814:5777803] 當前線程<NSThread: 0x1c026a940>{number = 5, name = (null)}

  • performSelectorInBackground隱式創建并啟動線程
- (void)onThread {// 使用NSObject的方法隱式創建并自動啟動[self performSelectorInBackground:@selector(run) withObject:nil]; }- (void)run {NSLog(@"當前線程%@", [NSThread currentThread]); }

打印結果: 2018-03-16 13:54:33.451895+0800 ocgcd[13820:5780922] 當前線程<NSThread: 0x1c4460280>{number = 4, name = (null)}

NSThread方法

//獲取當前線程+(NSThread *)currentThread; //創建線程后自動啟動線程 + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument; //是否是多線程 + (BOOL)isMultiThreaded; //線程字典 - (NSMutableDictionary *)threadDictionary; //線程休眠到什么時間 + (void)sleepUntilDate:(NSDate *)date; //線程休眠多久 + (void)sleepForTimeInterval:(NSTimeInterval)ti; //取消線程 - (void)cancel; //啟動線程 - (void)start; //退出線程 + (void)exit; //線程優先級 + (double)threadPriority; + (BOOL)setThreadPriority:(double)p; - (double)threadPriority NS_AVAILABLE(10_6, 4_0); - (void)setThreadPriority:(double)p NS_AVAILABLE(10_6, 4_0); //調用棧返回地址 + (NSArray *)callStackReturnAddresses NS_AVAILABLE(10_5, 2_0); + (NSArray *)callStackSymbols NS_AVAILABLE(10_6, 4_0); //設置線程名字 - (void)setName:(NSString *)n NS_AVAILABLE(10_5, 2_0); - (NSString *)name NS_AVAILABLE(10_5, 2_0); //獲取棧的大小 - (NSUInteger)stackSize NS_AVAILABLE(10_5, 2_0); - (void)setStackSize:(NSUInteger)s NS_AVAILABLE(10_5, 2_0); // 獲得主線程 + (NSThread *)mainThread; //是否是主線程 - (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); + (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // reports whether current thread is main + (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0); //初始化方法 - (id)init NS_AVAILABLE(10_5, 2_0); // designated initializer - (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument NS_AVAILABLE(10_5, 2_0); //是否正在執行 - (BOOL)isExecuting NS_AVAILABLE(10_5, 2_0); //是否執行完成 - (BOOL)isFinished NS_AVAILABLE(10_5, 2_0); //是否取消線程 - (BOOL)isCancelled NS_AVAILABLE(10_5, 2_0); - (void)cancel NS_AVAILABLE(10_5, 2_0); //線程啟動 - (void)start NS_AVAILABLE(10_5, 2_0); - (void)main NS_AVAILABLE(10_5, 2_0); // thread body method @end //多線程通知 FOUNDATION_EXPORT NSString * const NSWillBecomeMultiThreadedNotification; FOUNDATION_EXPORT NSString * const NSDidBecomeSingleThreadedNotification; FOUNDATION_EXPORT NSString * const NSThreadWillExitNotification;@interface NSObject (NSThreadPerformAdditions) //與主線程通信 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array; - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;// equivalent to the first method with kCFRunLoopCommonModes //與其他子線程通信 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0); - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);// equivalent to the first method with kCFRunLoopCommonModes //隱式創建并啟動線程 - (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg NS_AVAILABLE(10_5, 2_0);

NSThread線程狀態

  • 啟動線程
// 線程啟動 - (void)start;
  • 阻塞線程
// 線程休眠到某一時刻 + (void)sleepUntilDate:(NSDate *)date; // 線程休眠多久 + (void)sleepForTimeInterval:(NSTimeInterval)ti;
  • 結束線程
// 結束線程 + (void)exit;

關于cancel的疑問,當使用cancel方法時,只是改變了線程的狀態標識,并不能結束線程,所以我們要配合isCancelled方法進行使用。

- (void)onThread {// 使用NSObject的方法隱式創建并自動啟動[self performSelectorInBackground:@selector(run) withObject:nil]; }- (void)run {NSLog(@"當前線程%@", [NSThread currentThread]);for (int i = 0 ; i < 100; i++) {if (i == 20) {//取消線程[[NSThread currentThread] cancel];NSLog(@"取消線程%@", [NSThread currentThread]);}if ([[NSThread currentThread] isCancelled]) {NSLog(@"結束線程%@", [NSThread currentThread]);//結束線程[NSThread exit];NSLog(@"這行代碼不會打印的");}} }

打印結果: 2018-03-16 14:11:44.423324+0800 ocgcd[13833:5787076] 當前線程<NSThread: 0x1c4466840>{number = 4, name = (null)} 2018-03-16 14:11:44.425124+0800 ocgcd[13833:5787076] 取消線程<NSThread: 0x1c4466840>{number = 4, name = (null)} 2018-03-16 14:11:44.426391+0800 ocgcd[13833:5787076] 結束線程<NSThread: 0x1c4466840>{number = 4, name = (null)}

線程的狀態如下圖:

1、新建:實例化對象

2、就緒:向線程對象發送start消息,線程對象被加入“可調度線程池”等待CPU調度;detach方法和performSelectorInBackground方法會直接實例化一個線程對象并加入“可調度線程池”

3、運行:CPU負責調度“可調度線程池”中線程的執行,線程執行完成之前,狀態可能會在“就緒”和“運行”之間來回切換,“就緒”和“運行”之間的狀態變化由CPU負責,程序員不能干預

4、阻塞:當滿足某個預定條件時,可以使用休眠或鎖阻塞線程執行,影響的方法有:sleepForTimeInterval,sleepUntilDate,@synchronized(self)線程鎖;線程對象進入阻塞狀態后,會被從“可調度線程池”中移出,CPU 不再調度

5、死亡

死亡方式:

正常死亡:線程執行完畢 非正常死亡:線程內死亡—>[NSThread exit]:強行中止后,后續代碼都不會在執行 線程外死亡:[threadObj cancel]—>通知線程對象取消,在線程執行方法中需要增加isCancelled判斷,如果isCancelled == YES,直接返回

死亡后線程對象的isFinished屬性為YES;如果是發送cancle消息,線程對象的isCancelled屬性為YES;死亡后stackSize == 0,內存空間被釋放

NSThread線程間通信

在開發中,我們經常會在子線程進行耗時操作,操作結束后再回到主線程去刷新UI。這就涉及到了子線程和主線程之間的通信。看一下官方關于NSThread的線程間通信的方法。

// 在主線程上執行操作 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait; - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray<NSString *> *)array;// equivalent to the first method with kCFRunLoopCommonModes// 在指定線程上執行操作 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0); - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);// 在當前線程上執行操作,調用 NSObject 的 performSelector:相關方法 - (id)performSelector:(SEL)aSelector; - (id)performSelector:(SEL)aSelector withObject:(id)object; - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

下面通過一個經典的下載圖片DEMO來展示線程之間的通信。具體步驟如下: 1、開啟一個子線程,在子線程中下載圖片。 2、回到主線程刷新UI,將圖片展示在UIImageView中。

func onThread() {let urlStr = "http://tupian.aladd.net/2015/7/2941.jpg"self.performSelector(inBackground: #selector(downloadImg(_:)), with: urlStr) }@objc func downloadImg(_ urlStr: String) {//打印當前線程print("下載圖片線程", Thread.current)//獲取圖片鏈接guard let url = URL.init(string: urlStr) else {return}//下載圖片二進制數據guard let data = try? Data.init(contentsOf: url) else {return}//設置圖片guard let img = UIImage.init(data: data) else {return}//回到主線程刷新UIself.performSelector(onMainThread: #selector(downloadFinished(_:)), with: img, waitUntilDone: false) }@objc func downloadFinished(_ img: UIImage) {//打印當前線程print("刷新UI線程", Thread.current)// 賦值圖片到imageviewself.imageView.image = image }

NSThread線程安全

線程安全,也可以被稱為線程同步,主要是解決多線程爭搶操作資源的問題,就比如火車票,全國各地多個售票窗口同事去售賣同一列火車票。 怎么保證,多地售票的票池保持一致,就需要用到多線程同步的技術去實現了。

NSThread線程安全和GCD、NSOperation線程安全都是一樣的,實現方法無非就是加鎖(各種鎖的實現)、信號量、GCD柵欄等。 具體實現,可以看iOS多線程詳解:概念篇線程同步段落。

GCD

GCD(Grand Central Dispatch是蘋果為多核并行運算提出的C語言并發技術框架。 GCD會自動利用更多的CPU內核; 會自動管理線程的生命周期(創建線程,調度任務,銷毀線程等); 程序員只需要告訴GCD想要如何執行什么任務,不需要編寫任何線程管理代碼。

GCD底層實現

我們使用的GCD的API是C語言函數,全部包含在LIBdispatch庫中,DispatchQueue通過結構體和鏈表被實現為FIFO的隊列;而FIFO的隊列是由dispatch_async等函數追加的Block來管理的;Block不是直接加入FIFO隊列,而是先加入Dispatch Continuation結構體,然后在加入FIFO隊列,Dispatch Continuation用于記憶Block所屬的Dispatch Group和其他一些信息(相當于上下文)。 Dispatch Queue可通過dispatch_set_target_queue()設定,可以設定執行該Dispatch Queue處理的Dispatch Queue為目標。該目標可像串珠子一樣,設定多個連接在一起的Dispatch Queue,但是在連接串的最后必須設定Main Dispatch Queue,或各種優先級的Global Dispatch Queue,或是準備用于Serial Dispatch Queue的Global Dispatch Queue

Global Dispatch Queue的8種優先級:

.High priority .Default Priority .Low Priority .Background Priority .High Overcommit Priority .Default Overcommit Priority .Low Overcommit Priority .Background Overcommit Priority

附有Overcommit的Global Dispatch Queue使用在Serial Dispatch Queue中,不管系統狀態如何,都會強制生成線程的 Dispatch Queue。 這8種Global Dispatch Queue各使用1個pthread_workqueue

  • GCD初始化

GCD初始化時,使用pthread_workqueue_create_np函數生成pthread_workqueue。pthread_workqueue包含在Libc提供的pthreads的API中,他使用bsthread_register和workq_open系統調用,在初始化XNU內核的workqueue之后獲取workqueue信息。

其中XNU有四種workqueue:

WORKQUEUE_HIGH_PRIOQUEUE WORKQUEUE_DEFAULT_PRIOQUEUE WORKQUEUE_LOW_PRIOQUEUE WORKQUEUE_BG_PRIOQUEUE

這四種workqueue與Global Dispatch Queue的執行優先級相同

  • Dispatch Queue執行block的過程

1、當在Global Dispatch Queue中執行Block時,libdispatch從Global Dispatch Queue自身的FIFO中取出Dispatch Continuation,調用pthread_workqueue_additem_np函數,將該Global Dispatch Queue、符合其優先級的workqueue信息以及執行Dispatch Continuation的回調函數等傳遞給pthread_workqueue_additem_np函數的參數。

2、thread_workqueue_additem_np()使用workq_kernreturn系統調用,通知workqueue增加應當執行的項目。

3、根據該通知,XUN內核基于系統狀態判斷是否要生成線程,如果是Overcommit優先級的Global Dispatch Queue,workqueue則始終生成線程。

4、workqueue的線程執行pthread_workqueue(),該函數用libdispatch的回調函數,在回調函數中執行執行加入到Dispatch Continuatin的Block。

5、Block執行結束后,進行通知Dispatch Group結束,釋放Dispatch Continuation等處理,開始準備執行加入到Dispatch Continuation中的下一個Block。

GCD使用步驟

GCD 的使用步驟其實很簡單,只有兩步。

1、創建一個隊列(串行隊列或并發隊列) 2、將任務追加到任務的等待隊列中,然后系統就會根據任務類型執行任務(同步執行或異步執行)

隊列的創建方法/獲取方法

iOS系統默認已經存在兩種隊列,主隊列(串行隊列)和全局隊列(并發隊列),那我們可以利用GCD提供的接口創建并發和串行隊列。

關于同步、異步、串行、并行的概念和區別,在iOS多線程詳解:概念篇中有詳細說明

  • 創建串行隊列
//創建串行隊列 let que = DispatchQueue.init(label: "com.jacyshan.thread")

使用DispatchQueue初始化創建隊列,默認是串行隊列。第一個參數是表示隊列的唯一標識符,用于 DEBUG,可為空,Dispatch Queue 的名稱推薦使用應用程序 ID 這種逆序全程域名。

  • 創建并發隊列
//創建并發隊列 let que = DispatchQueue.init(label: "com.jacyshan.thread", attributes: .concurrent)

第二個參數輸入.concurrent標示創建的是一個并發隊列

  • 獲取主隊列

主隊列(Main Dispatch Queue)是GCD 提供了的一種特殊的串行隊列 所有放在主隊列中的任務,都會放到主線程中執行。

//獲取主隊列 let que = DispatchQueue.main
  • 獲取全局隊列

GCD 默認提供了全局并發隊列(Global Dispatch Queue)。

//獲取全局隊列 let que = DispatchQueue.global()
任務的創建方法

GCD 提供了同步執行任務的創建方法sync和異步執行任務創建方法async。

//同步執行任務創建方法 que.sync {print("任務1", Thread.current) }//異步執行任務創建方法 que.async {print("任務2", Thread.current) }

有兩種隊列(串行隊列/并發隊列),兩種任務執行方式(同步執行/異步執行),那么我們就有了四種不同的組合方式。這四種不同的組合方式是:

1、同步執行 + 并發隊列 2、異步執行 + 并發隊列 3、同步執行 + 串行隊列 4、異步執行 + 串行隊列

系統還提供了兩種特殊隊列:全局并發隊列、主隊列。全局并發隊列可以作為普通并發隊列來使用。但是主隊列因為有點特殊,所以我們就又多了兩種組合方式。這樣就有六種不同的組合方式了。

5、同步執行 + 主隊列 6、異步執行 + 主隊列

六中組合方式區別通過顯示如下。

區別并發隊列串行隊列主隊列
同步執行沒有開啟新線程,串行執行任務沒有開啟新線程,串行執行任務主線程調用:死鎖卡住不執行 其他線程調用:沒有開啟新線程,串行執行任務
異步執行有開啟新線程,并發執行任務有開啟新線程(1條),串行執行任務沒有開啟新線程,串行執行任務

GCD六種組合實現

同步+并發隊列

在當前線程中執行任務,任務按順序執行。

@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//創建并發隊列let que = DispatchQueue.init(label: "com.jackyshan.thread", attributes: .concurrent)//添加任務1que.sync {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2) //模擬耗時操作print("任務1Thread---", Thread.current) //打印當前線程}}//添加任務2que.sync {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2) //模擬耗時操作print("任務2Thread---", Thread.current)}}print("代碼塊------end")}

打印結果: currentThread— <NSThread: 0x1c0078f00>{number = 1, name = main} 代碼塊------begin 任務1Thread— <NSThread: 0x1c0078f00>{number = 1, name = main} 任務1Thread— <NSThread: 0x1c0078f00>{number = 1, name = main} 任務2Thread— <NSThread: 0x1c0078f00>{number = 1, name = main} 任務2Thread— <NSThread: 0x1c0078f00>{number = 1, name = main} 代碼塊------end

可以看到任務是在主線程執行的,因為同步并沒有開啟新的線程。因為同步會阻塞線程,所以當我們的任務操作耗時的時候,我們界面的點擊和滑動都是無效的。 因為UI的操作也是在主線程,但是任務的耗時已經阻塞了線程,UI操作是沒有反應的

異步+并發隊列

開啟多個線程,任務交替執行。

@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//創建并發隊列let que = DispatchQueue.init(label: "com.jackyshan.thread", attributes: .concurrent)//添加任務1que.async {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2) //模擬耗時操作print("任務1Thread---", Thread.current) //打印當前線程}}//添加任務2que.async {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2) //模擬耗時操作print("任務2Thread---", Thread.current)}}print("代碼塊------end") }

打印結果: currentThread— <NSThread: 0x1c0062180>{number = 1, name = main} 代碼塊------begin 代碼塊------end 任務1Thread— <NSThread: 0x1c02695c0>{number = 4, name = (null)} 任務2Thread— <NSThread: 0x1c02694c0>{number = 5, name = (null)} 任務1Thread— <NSThread: 0x1c02695c0>{number = 4, name = (null)} 任務2Thread— <NSThread: 0x1c02694c0>{number = 5, name = (null)}

可以看到任務是在多個新的線程執行完成的,并沒有在主線程執行,因此任務在執行耗時操作的時候,并不會影響UI操作。 異步可以開啟新的線程,并發又可以執行多個線程的任務。因為異步沒有阻塞線程,代碼塊------begin 代碼塊------end立即執行了,其他線程執行完耗時操作之后才打印。

同步+串行隊列

在當前線程中執行任務,任務按順序執行。

@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//創建串行隊列,DispatchQueue默認是串行隊列let que = DispatchQueue.init(label: "com.jackyshan.thread")//添加任務1que.sync {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2) //模擬耗時操作print("任務1Thread---", Thread.current) //打印當前線程}}//添加任務2que.sync {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2) //模擬耗時操作print("任務2Thread---", Thread.current)}}print("代碼塊------end") }

打印結果: currentThread— <NSThread: 0x1c40658c0>{number = 1, name = main} 代碼塊------begin 任務1Thread— <NSThread: 0x1c40658c0>{number = 1, name = main} 任務1Thread— <NSThread: 0x1c40658c0>{number = 1, name = main} 任務2Thread— <NSThread: 0x1c40658c0>{number = 1, name = main} 任務2Thread— <NSThread: 0x1c40658c0>{number = 1, name = main} 代碼塊------end

同樣的,串行執行同步任務的時候,也沒有開啟新的線程,在主線程上執行任務,耗時操作會影響UI操作。

異步+串行隊列

開啟一個新的線程,在新的線程中執行任務,任務按順序執行。

@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//創建串行隊列,DispatchQueue默認是串行隊列let que = DispatchQueue.init(label: "com.jackyshan.thread")//添加任務1que.async {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2) //模擬耗時操作print("任務1Thread---", Thread.current) //打印當前線程}}//添加任務2que.async {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2) //模擬耗時操作print("任務2Thread---", Thread.current)}}print("代碼塊------end") }

打印結果: currentThread— <NSThread: 0x1c407b700>{number = 1, name = main} 代碼塊------begin 代碼塊------end 任務1Thread— <NSThread: 0x1c0462440>{number = 4, name = (null)} 任務1Thread— <NSThread: 0x1c0462440>{number = 4, name = (null)} 任務2Thread— <NSThread: 0x1c0462440>{number = 4, name = (null)} 任務2Thread— <NSThread: 0x1c0462440>{number = 4, name = (null)}

從打印可以看到只開啟了一個線程(串行只會開啟一個線程),任務是在新的線程按順序執行的。 任務是在代碼塊------begin 代碼塊------end后執行的(異步不會等待任務執行完畢)

下面是講__主隊列__,主隊列是一種特殊的串行隊列,所有任務(異步同步)都會在主線程執行。

同步+主隊列

任務在主線程中調用會出現死鎖,其他線程不會。

  • 在主線程執行同步+主隊列

界面卡死,所有操作沒有反應。任務互相等待造成死鎖。

@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//獲取主隊列let que = DispatchQueue.main//添加任務1que.sync {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2) //模擬耗時操作print("任務1Thread---", Thread.current) //打印當前線程}}//添加任務2que.sync {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2) //模擬耗時操作print("任務2Thread---", Thread.current)}}print("代碼塊------end") }

打印結果: currentThread— <NSThread: 0x1c00766c0>{number = 1, name = main} 代碼塊------begin

可以看到代碼塊------begin執行完,后面就不執行的,卡住不動了,等一會還會崩潰。

感覺死鎖很多文章講的不是很清楚,其實流程就是互相等待,簡單解釋如下:

原因是onThread()這個任務是在主線程執行的,任務1被添加到主隊列,要等待隊列onThread()任務執行完才會執行。 然后,任務1是在onThread()這個任務中的,按照FIFO的原則,onThread()先被添加到主隊列,應該先執行完,但是任務1在等待onThread()執行完才會執行。 這樣就造成了死鎖,互相等待對方完成任務。

  • 在其他線程執行同步+主隊列

主隊列不會開啟新的線程,任務按順序在主線程執行

override func viewDidLoad() {super.viewDidLoad()// Do any additional setup after loading the view, typically from a nib.//開啟新的線程執行onThread任務performSelector(inBackground: #selector(onThread), with: nil) }@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//獲取主隊列let que = DispatchQueue.main//添加任務1que.sync {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2) //模擬耗時操作print("任務1Thread---", Thread.current) //打印當前線程}}//添加任務2que.sync {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2) //模擬耗時操作print("任務2Thread---", Thread.current)}}print("代碼塊------end") }

打印結果: currentThread— <NSThread: 0x1c0076a80>{number = 4, name = (null)} 代碼塊------begin 任務1Thread— <NSThread: 0x1c406d680>{number = 1, name = main} 任務1Thread— <NSThread: 0x1c406d680>{number = 1, name = main} 任務2Thread— <NSThread: 0x1c406d680>{number = 1, name = main} 任務2Thread— <NSThread: 0x1c406d680>{number = 1, name = main} 代碼塊------end

onThread任務是在其他線程執行的,沒有添加到主隊列,所有也不會等待任務1、2的完成,因此不會死鎖。

這里有個疑問,有的人會想串行隊列+同步和并發隊列+同步為什么不會死鎖呢,其實如果onThread任務和同步任務在同一個隊列中,而且同步任務是在onThread中執行的,也會造成死鎖。 在一個隊列中,就會出現互相等待的現象,剛好同步又不好開啟新的線程,這樣就會死鎖了。

異步+主隊列

主隊列不會開啟新的線程,任務按順序在主線程執行

@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//獲取主隊列let que = DispatchQueue.main//添加任務1que.async {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2) //模擬耗時操作print("任務1Thread---", Thread.current) //打印當前線程}}//添加任務2que.async {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2) //模擬耗時操作print("任務2Thread---", Thread.current)}}print("代碼塊------end") }

打印結果: currentThread— <NSThread: 0x1c4076500>{number = 1, name = main} 代碼塊------begin 代碼塊------end 任務1Thread— <NSThread: 0x1c4076500>{number = 1, name = main} 任務1Thread— <NSThread: 0x1c4076500>{number = 1, name = main} 任務2Thread— <NSThread: 0x1c4076500>{number = 1, name = main} 任務2Thread— <NSThread: 0x1c4076500>{number = 1, name = main}

可以看到onThread任務執行完了,沒有等待任務1、2的完成(異步立即執行不等待),所以不會死鎖。 主隊列是串行隊列,任務是按順序一個接一個執行的。

GCD的其他方法

asyncAfter延遲執行

很多時候我們希望延遲執行某個任務,這個時候使用DispatchQueue.main.asyncAfter是很方便的。 這個方法并不是立馬執行的,延遲執行也不是絕對準確,可以看到,他是在延遲時間過后,把任務追加到主隊列,如果主隊列有其他耗時任務,這個延遲任務,相對的也要等待任務完成。

@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//主線程延遲執行let delay = DispatchTime.now() + .seconds(3)DispatchQueue.main.asyncAfter(deadline: delay) {print("asyncAfter---", Thread.current)} }

打印結果: currentThread— <NSThread: 0x1c407f900>{number = 1, name = main} 代碼塊------begin asyncAfter— <NSThread: 0x1c407f900>{number = 1, name = main}

DispatchWorkItem

DispatchWorkItem是一個代碼塊,它可以在任意一個隊列上被調用,因此它里面的代碼可以在后臺運行,也可以在主線程運行。 它的使用真的很簡單,就是一堆可以直接調用的代碼,而不用像之前一樣每次都寫一個代碼塊。我們也可以使用它的通知完成回調任務。

做多線程的業務的時候,經常會有需求,當我們在做耗時操作的時候完成的時候發個通知告訴我這個任務做完了。

@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//創建workItemlet workItem = DispatchWorkItem.init {for _ in 0..<2 {print("任務workItem---", Thread.current)}}//全局隊列(并發隊列)執行workItemDispatchQueue.global().async {workItem.perform()}//執行完之后通知workItem.notify(queue: DispatchQueue.main) {print("任務workItem完成---", Thread.current)}print("代碼塊------結束") }

打印結果: currentThread— <NSThread: 0x1c4079300>{number = 1, name = main} 代碼塊------begin 代碼塊------結束 任務workItem— <NSThread: 0x1c42705c0>{number = 5, name = (null)} 任務workItem— <NSThread: 0x1c42705c0>{number = 5, name = (null)} 任務workItem完成— <NSThread: 0x1c4079300>{number = 1, name = main}

可以看到我們使用全局隊列異步執行了workItem,任務執行完,收到了通知。

DispatchGroup隊列組

有些復雜的業務可能會有這個需求,幾個隊列執行任務,然后把這些隊列都放到一個組Group里,當組里所有隊列的任務都完成了之后,Group發出通知,回到主隊列完成其他任務。

@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//創建 DispatchGrouplet group =DispatchGroup()group.enter()//全局隊列(并發隊列)執行任務DispatchQueue.global().async {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//耗時操作print("任務1------", Thread.current)//打印線程}group.leave()}//如果需要上個隊列完成后再執行可以用waitgroup.wait()group.enter()//自定義并發隊列執行任務DispatchQueue.init(label: "com.jackyshan.thread").async {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//耗時操作print("任務2------", Thread.current)//打印線程}group.leave()}//全部執行完后回到主線程刷新UIgroup.notify(queue: DispatchQueue.main) {print("任務執行完畢------", Thread.current)//打印線程}print("代碼塊------結束") }

打印結果: currentThread— <NSThread: 0x1c0261bc0>{number = 1, name = main} 代碼塊------begin 任務1------ <NSThread: 0x1c046b900>{number = 5, name = (null)} 任務1------ <NSThread: 0x1c046b900>{number = 5, name = (null)} 代碼塊------結束 任務2------ <NSThread: 0x1c0476b00>{number = 6, name = (null)} 任務2------ <NSThread: 0x1c0476b00>{number = 6, name = (null)} 任務執行完畢------ <NSThread: 0x1c0261bc0>{number = 1, name = main}

兩個隊列,一個執行默認的全局隊列,一個是自己自定義的并發隊列,兩個隊列都完成之后,group得到了通知。 如果把group.wait()注釋掉,我們會看到兩個隊列的任務會交替執行。

dispatch_barrier_async柵欄方法

dispatch_barrier_async是oc的實現,Swift的實現que.async(flags: .barrier)這樣。

@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//創建并發隊列let que = DispatchQueue.init(label: "com.jackyshan.thread", attributes: .concurrent)//并發異步執行任務que.async {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//耗時操作print("任務0------", Thread.current)//打印線程}}//并發異步執行任務que.async {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//耗時操作print("任務1------", Thread.current)//打印線程}}//柵欄方法:等待隊列里前面的任務執行完之后執行que.async(flags: .barrier) {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//耗時操作print("任務2------", Thread.current)//打印線程}//執行完之后執行隊列后面的任務}//并發異步執行任務que.async {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//耗時操作print("任務3------", Thread.current)//打印線程}}print("代碼塊------結束") }

打印結果: currentThread— <NSThread: 0x1c4078a00>{number = 1, name = main} 代碼塊------begin 代碼塊------結束 任務0------ <NSThread: 0x1c427d5c0>{number = 5, name = (null)} 任務1------ <NSThread: 0x1c0470f80>{number = 4, name = (null)} 任務0------ <NSThread: 0x1c427d5c0>{number = 5, name = (null)} 任務1------ <NSThread: 0x1c0470f80>{number = 4, name = (null)} 任務2------ <NSThread: 0x1c0470f80>{number = 4, name = (null)} 任務2------ <NSThread: 0x1c0470f80>{number = 4, name = (null)} 任務3------ <NSThread: 0x1c0470f80>{number = 4, name = (null)} 任務3------ <NSThread: 0x1c0470f80>{number = 4, name = (null)}

可以看到由于任務2執行的barrier的操作,任務0和1交替執行,任務2等待0和1執行完才執行,任務3也是等待任務2執行完畢。 也可以看到由于barrier的操作,并沒有開啟新的線程去跑任務。

Quality Of Service(QoS)和優先級

在使用 GCD 與 dispatch queue 時,我們經常需要告訴系統,應用程序中的哪些任務比較重要,需要更高的優先級去執行。當然,由于主隊列總是用來處理 UI 以及界面的響應,所以在主線程執行的任務永遠都有最高的優先級。不管在哪種情況下,只要告訴系統必要的信息,iOS 就會根據你的需求安排好隊列的優先級以及它們所需要的資源(比如說所需的 CPU 執行時間)。雖然所有的任務最終都會完成,但是,重要的區別在于哪些任務更快完成,哪些任務完成得更晚。

用于指定任務重要程度以及優先級的信息,在 GCD 中被稱為 Quality of Service(QoS)。事實上,QoS 是有幾個特定值的枚舉類型,我們可以根據需要的優先級,使用合適的 QoS 值來初始化隊列。如果沒有指定 QoS,則隊列會使用默認優先級進行初始化。要詳細了解 QoS 可用的值,可以參考這個文檔,請確保你仔細看過這個文檔。下面的列表總結了 Qos 可用的值,它們也被稱為 QoS classes。第一個 class 代碼了最高的優先級,最后一個代表了最低的優先級:

  • userInteractive
  • userInitiated
  • default
  • utility
  • background
  • unspecified

創建兩個隊列,優先級都是userInteractive,看看效果:

@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//創建并發隊列1let que1 = DispatchQueue.init(label: "com.jackyshan.thread1", qos: .userInteractive, attributes: .concurrent)//創建并發隊列2let que2 = DispatchQueue.init(label: "com.jackyshan.thread2", qos: .userInteractive, attributes: .concurrent)que1.async {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//耗時操作print("任務1------", Thread.current)//打印線程}}que2.async {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//耗時操作print("任務2------", Thread.current)//打印線程}}print("代碼塊------結束") }

打印結果: currentThread— <NSThread: 0x1c0073680>{number = 1, name = main} 代碼塊------begin 代碼塊------結束 任務1------ <NSThread: 0x1c047cd80>{number = 5, name = (null)} 任務2------ <NSThread: 0x1c0476c40>{number = 3, name = (null)} 任務1------ <NSThread: 0x1c047cd80>{number = 5, name = (null)} 任務2------ <NSThread: 0x1c0476c40>{number = 3, name = (null)}

兩個隊列的優先級一樣,任務也是交替執行,這和我們預測的一樣。

下面把queue1的優先級改為background,看看效果:

@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//創建并發隊列1let que1 = DispatchQueue.init(label: "com.jackyshan.thread1", qos: .background, attributes: .concurrent)//創建并發隊列2let que2 = DispatchQueue.init(label: "com.jackyshan.thread2", qos: .userInteractive, attributes: .concurrent)que1.async {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//耗時操作print("任務1------", Thread.current)//打印線程}}que2.async {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//耗時操作print("任務2------", Thread.current)//打印線程}}print("代碼塊------結束") }

打印結果: currentThread— <NSThread: 0x1c006afc0>{number = 1, name = main} 代碼塊------begin 代碼塊------結束 任務2------ <NSThread: 0x1c4070180>{number = 5, name = (null)} 任務1------ <NSThread: 0x1c006d400>{number = 6, name = (null)} 任務2------ <NSThread: 0x1c4070180>{number = 5, name = (null)} 任務1------ <NSThread: 0x1c006d400>{number = 6, name = (null)}

可以看到queue1的優先級調低為background,queue2的任務就優先執行了。

還有其他的優先級,從高到低,就不一一相互比較了。

DispatchSemaphore信號量

GCD 中的信號量是指 Dispatch Semaphore,是持有計數的信號。類似于過高速路收費站的欄桿。可以通過時,打開欄桿,不可以通過時,關閉欄桿。在 Dispatch Semaphore 中,使用計數來完成這個功能,計數為0時等待,不可通過。計數為1或大于1時,計數減1且不等待,可通過。

  • DispatchSemaphore(value: ):用于創建信號量,可以指定初始化信號量計數值,這里我們默認1.
  • semaphore.wait():會判斷信號量,如果為1,則往下執行。如果是0,則等待。
  • semaphore.signal():代表運行結束,信號量加1,有等待的任務這個時候才會繼續執行。

可以使用DispatchSemaphore實現線程同步,保證線程安全。

加入有一個票池,同時幾個線程去賣票,我們要保證每個線程獲取的票池是一致的。 使用DispatchSemaphore和剛才講的DispatchWorkItem來實現,我們看看效果。

@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//創建票池var tickets = [Int]()for i in 0..<38 {tickets.append(i)}//創建一個初始計數值為1的信號let semaphore = DispatchSemaphore(value: 1)let workItem = DispatchWorkItem.init {semaphore.wait()if tickets.count > 0 {Thread.sleep(forTimeInterval: 0.2)//耗時操作print("剩余票數", tickets.count, Thread.current)tickets.removeLast()//去票池庫存}else {print("票池沒票了")}semaphore.signal()}//創建并發隊列1let que1 = DispatchQueue.init(label: "com.jackyshan.thread1", qos: .background, attributes: .concurrent)//創建并發隊列2let que2 = DispatchQueue.init(label: "com.jackyshan.thread2", qos: .userInteractive, attributes: .concurrent)que1.async {for _ in 0..<20 {workItem.perform()}}que2.async {for _ in 0..<20 {workItem.perform()}}print("代碼塊------結束") }

currentThread— <NSThread: 0x1c407a6c0>{number = 1, name = main} 代碼塊------begin 代碼塊------結束 剩余票數 38 <NSThread: 0x1c44706c0>{number = 8, name = (null)} 剩余票數 37 <NSThread: 0x1c0264c80>{number = 9, name = (null)} 剩余票數 36 <NSThread: 0x1c44706c0>{number = 8, name = (null)} … 剩余票數 19 <NSThread: 0x1c0264c80>{number = 9, name = (null)} 剩余票數 18 <NSThread: 0x1c44706c0>{number = 8, name = (null)} 剩余票數 17 <NSThread: 0x1c0264c80>{number = 9, name = (null)} 剩余票數 16 <NSThread: 0x1c44706c0>{number = 8, name = (null)} … 剩余票數 2 <NSThread: 0x1c44706c0>{number = 8, name = (null)} 剩余票數 1 <NSThread: 0x1c0264c80>{number = 9, name = (null)} 票池沒票了 票池沒票了

可以看到我們的資源沒有因為造成資源爭搶而出現數據紊亂。信號量很好的實現了多線程同步的功能。

DispatchSource

DispatchSource provides an interface for monitoring low-level system objects such as Mach ports, Unix descriptors, Unix signals, and VFS nodes for activity and submitting event handlers to dispatch queues for asynchronous processing when such activity occurs. DispatchSource提供了一組接口,用來提交hander監測底層的事件,這些事件包括Mach ports,Unix descriptors,Unix signals,VFS nodes。

Tips: DispatchSource這個class很好的體現了Swift是一門面向協議的語言。這個類是一個工廠類,用來實現各種source。比如DispatchSourceTimer(本身是個協議)表示一個定時器。

  • DispatchSourceProtocol

基礎協議,所有的用到的DispatchSource都實現了這個協議。這個協議的提供了公共的方法和屬性: 由于不同的source是用到的屬性和方法不一樣,這里只列出幾個公共的方法

  • activate //激活

  • suspend //掛起

  • resume //繼續

  • cancel //取消(異步的取消,會保證當前eventHander執行完)

  • setEventHandler //事件處理邏輯

  • setCancelHandler //取消時候的清理邏輯

  • DispatchSourceTimer

在Swift 3中,可以方便的用GCD創建一個Timer(新特性)。DispatchSourceTimer本身是一個協議。 比如,寫一個timer,1秒后執行,然后10秒后自動取消,允許10毫秒的誤差

PlaygroundPage.current.needsIndefiniteExecution = truepublic let timer = DispatchSource.makeTimerSource()timer.setEventHandler {//這里要注意循環引用,[weak self] inprint("Timer fired at \(NSDate())") }timer.setCancelHandler {print("Timer canceled at \(NSDate())" ) }timer.scheduleRepeating(deadline: .now() + .seconds(1), interval: 2.0, leeway: .microseconds(10))print("Timer resume at \(NSDate())")timer.resume()DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10), execute:{timer.cancel() })

deadline表示開始時間,leeway表示能夠容忍的誤差。

DispatchSourceTimer也支持只調用一次。

func scheduleOneshot(deadline: DispatchTime, leeway: DispatchTimeInterval = default)
  • UserData

DispatchSource中UserData部分也是強有力的工具,這部分包括兩個協議,兩個協議都是用來合并數據的變化,只不過一個是按照+(加)的方式,一個是按照|(位與)的方式。

DispatchSourceUserDataAdd DispatchSourceUserDataOr

在使用這兩種Source的時候,GCD會幫助我們自動的將這些改變合并,然后在適當的時候(target queue空閑)的時候,去回調EventHandler,從而避免了頻繁的回調導致CPU占用過多。

let userData = DispatchSource.makeUserDataAddSource()var globalData:UInt = 0userData.setEventHandler {let pendingData = userData.dataglobalData = globalData + pendingDataprint("Add \(pendingData) to global and current global is \(globalData)") }userData.resume()let serialQueue = DispatchQueue(label: "com")serialQueue.async {for var index in 1...1000 {userData.add(data: 1)}for var index in 1...1000 {userData.add(data: 1)} }

Add 32 to global and current global is 32 Add 1321 to global and current global is 1353 Add 617 to global and current global is 1970 Add 30 to global and current global is 2000

NSOperation

NSOperation是基于GCD的一個抽象基類,將線程封裝成要執行的操作,不需要管理線程的生命周期和同步,但比GCD可控性更強,例如可以加入操作依賴(addDependency)、設置操作隊列最大可并發執行的操作個數(setMaxConcurrentOperationCount)、取消操作(cancel)等。NSOperation作為抽象基類不具備封裝我們的操作的功能,需要使用兩個它的實體子類:NSBlockOperation和繼承NSOperation自定義子類。NSOperation需要配合NSOperationQueue來實現多線程。

NSOperation使用步驟

自定義Operation

繼承Operation創建一個類,并重寫main方法。當調用start的時候,會在適當的時候執行main里面的任務。

class ViewController: UIViewController {@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")let op = JKOperation.init()op.start()print("代碼塊------結束")} }class JKOperation: Operation {override func main() {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務---", Thread.current)} }

打印結果: currentThread— <NSThread: 0x1c007a280>{number = 1, name = main} 代碼塊------begin 任務— <NSThread: 0x1c007a280>{number = 1, name = main} 代碼塊------結束

可以看到自定義JKOperation,初始化之后,調用start方法,main方法里面的任務執行了,是在主線程執行的。 因為我們沒有使用OperationQueue,所以沒有創建新的線程。

使用BlockOperation

初始化BlockOperation之后,調用start方法。

@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")let bop = BlockOperation.init {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務---", Thread.current)}bop.start()print("代碼塊------結束") }

打印結果: currentThread— <NSThread: 0x1c4070640>{number = 1, name = main} 代碼塊------begin 任務— <NSThread: 0x1c4070640>{number = 1, name = main} 代碼塊------結束

配合OperationQueue實現

初始化OperationQueue之后,調用addOperation,代碼塊就會自定執行,調用機制執行是有OperationQueue里面自動實現的。 addOperation的方法里面其實是生成了一個BlockOperation對象,然后執行了這個對象的start方法。

@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")OperationQueue.init().addOperation {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務---", Thread.current)}print("代碼塊------結束") }

打印結果: currentThread— <NSThread: 0x1c006a700>{number = 1, name = main} 代碼塊------begin 代碼塊------結束 任務— <NSThread: 0x1c0469900>{number = 5, name = (null)}

可以看到OperationQueue初始化,默認是生成了一個并發隊列,而且執行的是一個異步操作,所以打印任務的線程不是在主線程。

隊列的創建方法/獲取方法

OperationQueue沒有實現串行隊列的方法,也沒有像GCD那樣實現了一個全局隊列。 只有并發隊列的實現和主隊列的獲取。

  • 創建并發隊列

并發隊列的任務是并發(幾乎同時)執行的,可以最大發揮CPU多核的優勢。 看到有的說通過maxConcurrentOperationCount設置并發數量1就實現了串行。 實際上是不對的,通過設置優先級可以控制隊列的任務交替執行,在下面講到maxConcurrentOperationCount會實現代碼。

//創建并發隊列 let queue = OperationQueue.init()

OperationQueue初始化,默認實現的是并發隊列。

  • 獲取主隊列

我們的主隊列是串行隊列,任務是一個接一個執行的。

//獲取主隊列 let queue = OperationQueue.main

獲取主隊列的任務是異步執行的。

@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//獲取主隊列let queue = OperationQueue.mainqueue.addOperation {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務1---", Thread.current)}queue.addOperation {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務2---", Thread.current)}print("代碼塊------結束") }

打印結果: currentThread— <NSThread: 0x1c0064f80>{number = 1, name = main} 代碼塊------begin 代碼塊------結束 任務1— <NSThread: 0x1c0064f80>{number = 1, name = main} 任務2— <NSThread: 0x1c0064f80>{number = 1, name = main}

任務的創建方法
  • 通過BlockOperation創建任務
BlockOperation.init {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務1---", Thread.current) }.start()
  • 通過OperationQueue創建任務
//創建并發隊列 let queue = OperationQueue.init()queue.addOperation {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務---", Thread.current) }

NSOperation相關方法

最大并發操作數:maxConcurrentOperationCount

maxConcurrentOperationCount 默認情況下為-1,表示不進行限制,可進行并發執行。 maxConcurrentOperationCount這個值不應超過系統限制(64),即使自己設置一個很大的值,系統也會自動調整為 min{自己設定的值,系統設定的默認最大值}。

  • 設置maxConcurrentOperationCount為1,實現串行操作。
@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//創建并發隊列let queue = OperationQueue.init()//設置最大并發數為1queue.maxConcurrentOperationCount = 1let bq1 = BlockOperation.init {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務1---", Thread.current)}}let bq2 = BlockOperation.init {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務2---", Thread.current)}}queue.addOperations([bq1, bq2], waitUntilFinished: false)print("代碼塊------結束") }

打印結果: currentThread— <NSThread: 0x1c0067880>{number = 1, name = main} 代碼塊------begin 代碼塊------結束 任務1— <NSThread: 0x1c046ad40>{number = 4, name = (null)} 任務1— <NSThread: 0x1c046ad40>{number = 4, name = (null)} 任務2— <NSThread: 0x1c046ad40>{number = 4, name = (null)} 任務2— <NSThread: 0x1c046ad40>{number = 4, name = (null)}

從打印結果可以看到隊列里的任務是按串行執行的。 這是因為隊列里的任務優先級一樣,在只有一個并發隊列數的時候,任務按順序執行。

  • 設置maxConcurrentOperationCount為1,實現并發操作。
@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//創建并發隊列let queue = OperationQueue.init()//設置最大并發數為1queue.maxConcurrentOperationCount = 1let bq1 = BlockOperation.init {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務1---", Thread.current)}}bq1.queuePriority = .lowlet bq2 = BlockOperation.init {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務2---", Thread.current)}}bq2.queuePriority = .highlet bq3 = BlockOperation.init {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務3---", Thread.current)}}bq3.queuePriority = .normalqueue.addOperations([bq1, bq2, bq3], waitUntilFinished: false)print("代碼塊------結束") }

currentThread— <NSThread: 0x1c4261780>{number = 1, name = main} 代碼塊------begin 代碼塊------結束 任務2— <NSThread: 0x1c0279340>{number = 4, name = (null)} 任務2— <NSThread: 0x1c0279340>{number = 4, name = (null)} 任務3— <NSThread: 0x1c0279340>{number = 4, name = (null)} 任務3— <NSThread: 0x1c0279340>{number = 4, name = (null)} 任務1— <NSThread: 0x1c0279340>{number = 4, name = (null)} 任務1— <NSThread: 0x1c0279340>{number = 4, name = (null)}

可以我們通過設置優先級queuePriority,實現了隊列的任務交替執行了。

  • 設置maxConcurrentOperationCount為11,實現并發操作。
@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//創建并發隊列let queue = OperationQueue.init()//設置最大并發數為1queue.maxConcurrentOperationCount = 11let bq1 = BlockOperation.init {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務1---", Thread.current)}}let bq2 = BlockOperation.init {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務2---", Thread.current)}}queue.addOperations([bq1, bq2], waitUntilFinished: false)print("代碼塊------結束") }

打印結果: currentThread— <NSThread: 0x1c407a200>{number = 1, name = main} 代碼塊------begin 代碼塊------結束 任務2— <NSThread: 0x1c42647c0>{number = 4, name = (null)} 任務1— <NSThread: 0x1c04714c0>{number = 3, name = (null)} 任務2— <NSThread: 0x1c42647c0>{number = 4, name = (null)} 任務1— <NSThread: 0x1c04714c0>{number = 3, name = (null)}

maxConcurrentOperationCount大于1的時候,實現了并發操作。

等待執行完成:waitUntilFinished

waitUntilFinished阻塞當前線程,直到該操作結束。可用于線程執行順序的同步。

比如實現兩個并發隊列按順序執行。

@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//創建并發隊列1let queue1 = OperationQueue.init()let bq1 = BlockOperation.init {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務1---", Thread.current)}}let bq2 = BlockOperation.init {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務2---", Thread.current)}}queue1.addOperations([bq1, bq2], waitUntilFinished: true)//創建并發隊列2let queue2 = OperationQueue.init()let bq3 = BlockOperation.init {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務3---", Thread.current)}}let bq4 = BlockOperation.init {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務4---", Thread.current)}}queue2.addOperations([bq3, bq4], waitUntilFinished: true)print("代碼塊------結束")}

打印結果: currentThread— <NSThread: 0x1c407d1c0>{number = 1, name = main} 代碼塊------begin 任務1— <NSThread: 0x1c0467d40>{number = 4, name = (null)} 任務2— <NSThread: 0x1c0460a00>{number = 3, name = (null)} 任務1— <NSThread: 0x1c0467d40>{number = 4, name = (null)} 任務2— <NSThread: 0x1c0460a00>{number = 3, name = (null)} 任務3— <NSThread: 0x1c0467d40>{number = 4, name = (null)} 任務4— <NSThread: 0x1c0460a00>{number = 3, name = (null)} 任務3— <NSThread: 0x1c0467d40>{number = 4, name = (null)} 任務4— <NSThread: 0x1c0460a00>{number = 3, name = (null)} 代碼塊------結束

通過設置隊列的waitUntilFinished為true,可以看到queu1的任務并發執行完了之后,queue2的任務才開始并發執行。 而且所有的執行是在代碼塊------begin和代碼塊------結束之間的。queue1和queue2阻塞了主線程。

操作依賴:addDependency
@IBAction func onThread() {//打印當前線程print("currentThread---", Thread.current)print("代碼塊------begin")//創建并發隊列let queue = OperationQueue.init()let bq1 = BlockOperation.init {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務1---", Thread.current)}}let bq2 = BlockOperation.init {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務2---", Thread.current)}}let bq3 = BlockOperation.init {for _ in 0..<2 {Thread.sleep(forTimeInterval: 0.2)//執行耗時操作print("任務3---", Thread.current)}}bq3.addDependency(bq1)queue.addOperations([bq1, bq2, bq3], waitUntilFinished: false)print("代碼塊------結束") }

打印結果: currentThread— <NSThread: 0x1c0065740>{number = 1, name = main} 代碼塊------begin 代碼塊------結束 任務1— <NSThread: 0x1c0660300>{number = 5, name = (null)} 任務2— <NSThread: 0x1c4071340>{number = 6, name = (null)} 任務1— <NSThread: 0x1c0660300>{number = 5, name = (null)} 任務2— <NSThread: 0x1c4071340>{number = 6, name = (null)} 任務3— <NSThread: 0x1c0660300>{number = 5, name = (null)} 任務3— <NSThread: 0x1c0660300>{number = 5, name = (null)}

不添加操作依賴

打印結果: currentThread— <NSThread: 0x1c4072c40>{number = 1, name = main} 代碼塊------begin 代碼塊------結束 任務1— <NSThread: 0x1c0270c80>{number = 7, name = (null)} 任務2— <NSThread: 0x1c447df00>{number = 4, name = (null)} 任務3— <NSThread: 0x1c0270cc0>{number = 8, name = (null)} 任務1— <NSThread: 0x1c0270c80>{number = 7, name = (null)} 任務2— <NSThread: 0x1c447df00>{number = 4, name = (null)} 任務3— <NSThread: 0x1c0270cc0>{number = 8, name = (null)}

可以看到任務3在添加了操作依賴任務1,執行就一直等待任務1完成。

優先級:queuePriority

NSOperation 提供了queuePriority(優先級)屬性,queuePriority屬性適用于同一操作隊列中的操作,不適用于不同操作隊列中的操作。默認情況下,所有新創建的操作對象優先級都是NSOperationQueuePriorityNormal。但是我們可以通過setQueuePriority:方法來改變當前操作在同一隊列中的執行優先級。

public enum QueuePriority : Int {case veryLowcase lowcase normalcase highcase veryHigh }
  • 當一個操作的所有依賴都已經完成時,操作對象通常會進入準備就緒狀態,等待執行。* queuePriority屬性決定了進入準備就緒狀態下的操作之間的開始執行順序。并且,優先級不能取代依賴關系。* 如果一個隊列中既包含高優先級操作,又包含低優先級操作,并且兩個操作都已經準備就緒,那么隊列先執行高優先級操作。比如上例中,如果 op1 和 op4 是不同優先級的操作,那么就會先執行優先級高的操作。* 如果,一個隊列中既包含了準備就緒狀態的操作,又包含了未準備就緒的操作,未準備就緒的操作優先級比準備就緒的操作優先級高。那么,雖然準備就緒的操作優先級低,也會優先執行。優先級不能取代依賴關系。如果要控制操作間的啟動順序,則必須使用依賴關系。##### NSOperation常用屬性和方法

  • 取消操作方法

open func cancel()可取消操作,實質是標記isCancelled狀態。

  • 判斷操作狀態方法

open var isExecuting: Bool { get }判斷操作是否正在在運行。

open var isFinished: Bool { get }判斷操作是否已經結束。

open var isConcurrent: Bool { get }判斷操作是否處于串行。

open var isAsynchronous: Bool { get }判斷操作是否處于并發。

open var isReady: Bool { get }判斷操作是否處于準備就緒狀態,這個值和操作的依賴關系相關。

open var isCancelled: Bool { get }判斷操作是否已經標記為取消。

  • 操作同步

open func waitUntilFinished()阻塞當前線程,直到該操作結束。可用于線程執行順序的同步。

open var completionBlock: (() -> Swift.Void)?會在當前操作執行完畢時執行 completionBlock。

open func addDependency(_ op: Operation)添加依賴,使當前操作依賴于操作 op 的完成。

open func removeDependency(_ op: Operation)移除依賴,取消當前操作對操作 op 的依賴。

open var dependencies: [Operation] { get }在當前操作開始執行之前完成執行的所有操作對象數組。

open var queuePriority: Operation.QueuePriority設置當前操作在隊列中的優先級。

NSOperationQueue常用屬性和方法
  • 取消/暫停/恢復操作

open func cancelAllOperations()可以取消隊列的所有操作。

open var isSuspended: Bool判斷隊列是否處于暫停狀態。true為暫停狀態,false為恢復狀態。可設置操作的暫停和恢復,true代表暫停隊列,false代表恢復隊列。

  • 操作同步

open func waitUntilAllOperationsAreFinished()阻塞當前線程,直到隊列中的操作全部執行完畢。

  • 添加/獲取操作

open func addOperation(_ block: @escaping () -> Swift.Void) 向隊列中添加一個 NSBlockOperation 類型操作對象。

open func addOperations(_ ops: [Operation], waitUntilFinished wait: Bool)向隊列中添加操作數組,wait 標志是否阻塞當前線程直到所有操作結束。

open var operations: [Operation] { get }當前在隊列中的操作數組(某個操作執行結束后會自動從這個數組清除)。

open var operationCount: Int { get }當前隊列中的操作數。

  • 獲取隊列

open class var current: OperationQueue? { get }獲取當前隊列,如果當前線程不是在 NSOperationQueue 上運行則返回 nil。

open class var main: OperationQueue { get } 獲取主隊列。

總結

以上是生活随笔為你收集整理的iOS多线程详解:实践篇的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

国产午夜小视频 | 中文字幕精品一区久久久久 | 国产aa精品 | 91精品国产91久久久久久三级 | 91日韩精品一区 | 国产a国产 | 玖玖精品在线 | av观看网站| 国产字幕av | www夜夜操| 国产精品一区免费看8c0m | 国产白浆视频 | 天天干夜夜爱 | 国产精品久久久区三区天天噜 | 亚洲综合婷婷 | 欧美日韩在线电影 | 亚洲精品乱码白浆高清久久久久久 | 色噜噜日韩精品一区二区三区视频 | 亚洲va综合va国产va中文 | 精品国产123| 婷婷丁香在线 | 精品五月天 | 激情久久综合 | 久久精品欧美 | 欧美一区二区三区在线 | 91福利影院在线观看 | 98超碰人人 | 波多野结衣精品视频 | 三级av小说| 日韩天天干 | 日日夜夜人人天天 | www.黄色小说.com| 中文字幕文字幕一区二区 | 国产精品久久在线 | 黄色视屏在线免费观看 | 久久婷婷久久 | 久久久久女人精品毛片 | 国产精品视频最多的网站 | 天堂va在线高清一区 | 欧美一区二区在线免费观看 | 亚洲精品美女久久久久网站 | 日韩精品免费在线观看视频 | 五月综合在线观看 | 成人福利在线播放 | 天天躁日日躁狠狠躁 | 在线观看国产91 | 99久久精品费精品 | 人人爽人人爽人人爽学生一级 | 国产精品久久久久久久久久不蜜月 | 国产美女精品人人做人人爽 | 国产在线传媒 | 91高清完整版在线观看 | 四虎小视频| 亚洲 欧洲 国产 精品 | 久久精品一 | 中文字幕免费久久 | 欧美精品天堂 | 日韩国产欧美在线视频 | 99爱视频在线观看 | 成人黄色大片在线免费观看 | 精品国产99 | 超级碰碰免费视频 | 久久99精品久久久久久 | 久久免费av | 一区二区三区在线视频观看58 | 99在线热播 | 99精品在线视频播放 | 狠狠色噜噜狠狠狠狠2021天天 | 综合激情婷婷 | 精品国产一区二 | 久久综合狠狠综合久久综合88 | 五月宗合网 | 国产精品成人自拍 | 在线激情小视频 | 久亚洲| 91最新中文字幕 | 69亚洲精品 | 亚洲精品午夜aaa久久久 | 欧美精品被| 天天操天天弄 | 日日夜夜操操操操 | 国产又黄又硬又爽 | 欧美成人69av| 国产91精品高清一区二区三区 | 久久久久久久久久久久久久av | 国产一级h| 国产黄大片| 在线激情电影 | 一区二区三区 中文字幕 | 日韩欧美视频免费在线观看 | 免费影视大全推荐 | 日韩在线高清视频 | 毛片基地黄久久久久久天堂 | 日韩黄色软件 | 久久夜av | 免费在线观看亚洲视频 | 日韩一区二区三区免费视频 | 在线欧美国产 | 亚洲精品ww | 狠狠做六月爱婷婷综合aⅴ 日本高清免费中文字幕 | 成人午夜在线观看 | 女人18毛片a级毛片一区二区 | 午夜91在线| 国产在线1区 | 夜夜骑日日操 | 成人试看120秒 | 免费视频黄色 | 毛片在线播放网址 | 91丨九色丨高潮丰满 | 国产精品一区二 | 中文字幕丝袜 | 国产黄色视| 久久久久97国产 | 91在线资源 | 在线成人中文字幕 | 国产一级视频在线 | 免费观看完整版无人区 | 最新婷婷色| 日本黄色免费观看 | 麻豆国产视频 | 免费观看www视频 | 91精品国产一区 | 白丝av免费观看 | 人人干人人上 | 97国产在线| 成人天堂网 | 亚洲国产成人久久 | 国产极品尤物在线 | 美女精品网站 | 亚洲成人av电影在线 | 中文字幕在线看视频国产 | 麻豆视频入口 | 久草在线最新 | 久草影视在线观看 | 最近中文字幕免费 | 免费在线观看污网站 | 五月天综合网站 | 亚洲狠狠婷婷 | 91网页版在线观看 | 国产中文字幕一区二区 | 久久久久久综合 | 日韩精品视频在线免费观看 | 91视视频在线直接观看在线看网页在线看 | 久久精品爱爱视频 | 日日干精品 | 天天综合婷婷 | 亚洲精品视频在线免费 | 国产精品扒开做爽爽的视频 | 色a综合 | 国产一区在线免费 | 国产黑丝一区二区三区 | 精品久久久久久久久久 | 国产精品日韩久久久久 | 久久久高清免费视频 | 91精品第一页 | 一级黄毛片 | 91视频在线免费观看 | 国产成人在线一区 | 91av视频免费在线观看 | 综合中文字幕 | 狠狠躁夜夜躁人人爽超碰91 | 国产在线第三页 | 国产精品麻豆视频 | 91伊人影院 | 亚洲精品黄网站 | 久久久国产精品亚洲一区 | 亚洲天堂网在线视频 | 免费三级a | 国产精品a级 | 在线播放视频一区 | 天天操天天干天天干 | 久久国产精品视频免费看 | 日本xxxxav | 国产精品久久久久久久久久久杏吧 | 日韩超碰在线 | 高清免费在线视频 | 日韩理论在线观看 | 色综合久久88色综合天天6 | 欧美日韩激情视频8区 | 99在线精品视频观看 | 午夜av一区 | 一本一本久久a久久精品综合 | 99久久精品日本一区二区免费 | 国产传媒中文字幕 | 在线观看日本高清mv视频 | 中文字幕视频观看 | 精品专区一区二区 | 天天射天天 | 国产黄免费| 天天综合操 | 激情伊人五月天久久综合 | 在线视频婷婷 | 免费视频97 | 色综合天天综合在线视频 | 亚洲一区二区精品3399 | 免费99视频 | 成 人 黄 色 视频免费播放 | 国产一区二区在线免费播放 | 欧美久久久影院 | 久久久国产一区二区三区四区小说 | 五月色综合 | 色多多污污在线观看 | 成人欧美一区二区三区黑人麻豆 | 超碰在线免费福利 | 亚洲视频网站在线观看 | 国产对白av | 天天射天天艹 | 手机av永久免费 | 亚洲综合国产精品 | 日韩精品一区二区三区免费观看视频 | 精品视频免费看 | 97视频免费| 色综合www | 911香蕉视频 | 中文字幕高清免费日韩视频在线 | 久久久久久久影院 | 婷婷久久一区二区三区 | 狠狠干电影 | 天天爱天天色 | 午夜丰满寂寞少妇精品 | 欧美亚洲三级 | 色偷偷中文字幕 | 久久手机免费视频 | 久久久久免费精品国产 | 日韩精品第一区 | 国产黄色大全 | 欧美做受高潮 | 日韩免费 | 在线99热| 国内精品久久久久影院一蜜桃 | 少妇性bbb搡bbb爽爽爽欧美 | 免费网站在线观看成人 | 91视频免费观看 | 99视频精品全部免费 在线 | 亚洲精品久久久久中文字幕二区 | 黄污污网站 | 久久免费影院 | 国产午夜在线观看 | 日本在线观看中文字幕无线观看 | 免费成人黄色片 | 国产一区二区三区 在线 | 天天操夜夜操天天射 | 综合激情网... | 日韩www在线 | 久久精品一区二区三区四区 | 99色网站 | 日本久久久久 | 久久九九久久 | 久久激情久久 | 色丁香色婷婷 | 91精品国产自产在线观看永久 | 亚洲成人网在线 | 国产小视频91| 国产精品成人自产拍在线观看 | 日本黄色免费在线观看 | 毛片网站在线观看 | aa一级片| 精品亚洲免费 | 欧美性生交大片免网 | 一二区电影 | 成人欧美一区二区三区在线观看 | 色婷婷av在线 | 91精品国产一区二区在线观看 | 黄色国产区| 国产精品不卡在线 | 国产无套精品久久久久久 | 九九免费在线观看 | 色网站视频| 国产精品成人免费 | 久久久在线免费观看 | 天海冀一区二区三区 | 亚洲伊人天堂 | 欧美日韩另类在线观看 | 91久久电影 | 免费av网址在线观看 | av片在线看 | 免费视频 你懂的 | 久久激情视频 久久 | 国产精品美女久久久久久久网站 | 天天爽人人爽夜夜爽 | 国产精品第二十页 | 麻豆手机在线 | 国产精品永久在线 | 97人人艹 | 久久国产热视频 | 天天综合色天天综合 | 一区二区三区免费 | 日韩一区二区三免费高清在线观看 | 国产精品久久免费看 | 九九九九热精品免费视频点播观看 | 久久免费视频8 | 免费视频91 | 国产在线观看地址 | 麻豆观看 | 97视频免费在线看 | 黄色com | 亚洲国产美女精品久久久久∴ | 丁香婷婷综合五月 | 一色屋精品视频在线观看 | a电影在线观看 | 美女视频永久黄网站免费观看国产 | 日韩高清国产精品 | av韩国在线 | 日韩在线视频二区 | 国产精品va视频 | 在线 影视 一区 | 激情综合网天天干 | av蜜桃在线 | 91视频91色 | 亚洲成人免费在线观看 | 精品视频亚洲 | 丁香综合| 久久精品视频99 | 6080yy精品一区二区三区 | 天堂va欧美va亚洲va老司机 | 欧美日韩一区二区久久 | 欧美一级日韩免费不卡 | 日韩在线观看你懂得 | 亚洲日本在线视频观看 | 少妇做爰k8经典 | 日韩一区在线免费观看 | 久久精品视频免费 | 日韩素人在线观看 | 久久99久久99精品免观看软件 | 午夜天使 | 五月天亚洲综合 | 精品国产一区二区三区蜜臀 | 亚洲精品视频免费 | 最近中文国产在线视频 | 亚洲欧美日韩国产一区二区 | 国产精品区二区三区日本 | 色射爱| 高清国产一区 | 97理论片 | 中文字幕一区二区三区在线观看 | 人人澡人人模 | 丝袜足交在线 | 97日日碰人人模人人澡分享吧 | 日韩欧美69 | 日本精品一二区 | 国产精品一区二区av麻豆 | 免费看污网站 | 欧美日韩一区二区三区在线观看视频 | 中日韩免费视频 | 99久久99| 久久99精品国产麻豆宅宅 | 色偷偷88888欧美精品久久 | 天堂黄色片| 99久久精品免费看 | 日韩一区正在播放 | 在线 日韩 av | 成人精品久久久 | aa级黄色大片 | av888av.com | 久久久久女人精品毛片 | 日本99干网 | 成人试看120秒 | 国产一区视频在线播放 | 久久精品视频播放 | 国产精品亚 | 伊人影院得得 | 久久国产精品99久久久久久丝袜 | a级国产乱理论片在线观看 伊人宗合网 | 激情五月播播久久久精品 | 日韩欧美一区二区三区黑寡妇 | 日日夜夜天天久久 | 久久美女精品 | 国产91精品一区二区麻豆亚洲 | www.黄色| 91成人网在线观看 | 午夜视频福利 | 日日夜夜精品免费 | 麻豆视频观看 | 激情五月播播久久久精品 | 久草网视频 | 日本黄色免费在线 | 欧美日韩一区二区三区在线免费观看 | 国产精品1区 | 99久久精品一区二区成人 | 久久午夜视频 | 国产精品精 | 天堂av官网 | 精品国产一区二区三区久久 | 六月天色婷婷 | 色中色资源站 | 一区二区日韩av | 91在线亚洲 | 久久99精品国产麻豆婷婷 | 精品在线亚洲视频 | 亚洲黄色在线播放 | 一区二区三区福利 | 99精品黄色 | 日韩区欧美久久久无人区 | 九九电影在线 | 日韩av免费一区 | 国产精品久久久久久99 | 国产三级精品三级在线观看 | 成人在线视频论坛 | av免费看在线 | 99热播精品 | 亚洲影院国产 | 精品在线视频观看 | 2021久久 | 国产精品系列在线观看 | 成人久久视频 | 三级在线国产 | 欧美日韩在线观看一区二区三区 | 日日综合| 最近中文字幕高清字幕免费mv | 亚洲无毛专区 | 中文字幕a∨在线乱码免费看 | 亚洲成色 | 国产精品9999久久久久仙踪林 | 韩日精品在线观看 | 成人av在线电影 | 国产精品毛片一区 | 91免费的视频在线播放 | 色婷婷99 | 99色亚洲 | 天天碰天天操 | 视频福利在线 | 国产在线观看黄 | 免费的成人av | 国产婷婷视频在线 | 亚洲午夜久久久久久久久久久 | 午夜精品一区二区三区免费 | 亚洲国产97在线精品一区 | 国产黄色网| 91在线蜜桃臀 | 欧洲一区精品 | 99国产在线 | 日韩欧美91 | 久久天天躁狠狠躁亚洲综合公司 | 久久亚洲成人网 | 精品国产乱码久久久久久1区二区 | 成人黄色大片在线免费观看 | 国产免费又爽又刺激在线观看 | 99热这里精品 | 日本aa在线| 91视频久久| 中文字幕在线一区二区三区 | 国产综合在线视频 | 亚洲国产99 | 亚洲精品一区二区三区四区高清 | 精品视频免费播放 | 亚州天堂| 插久久| 精品一二三四五区 | 国产精品美乳一区二区免费 | 香蕉影视app | 欧美一二三四在线 | 超碰在线最新 | 日本福利视频在线 | 精品久久在线 | 亚洲aaa毛片 | 欧美二区视频 | 国产精品资源在线观看 | 午夜精品一区二区三区在线观看 | 国产v在线播放 | 日韩亚洲欧美中文字幕 | 日韩精品一区不卡 | 国产精品日韩高清 | 中文在线中文资源 | www.天天成人国产电影 | 欧美极品少妇xxxx | 亚洲视频免费在线 | 97福利 | 午夜精品电影一区二区在线 | 91九色网站 | 久久视频一区二区 | 久久婷婷一区 | 亚洲视频免费 | 精品一区二区三区电影 | 在线观看韩日电影免费 | 黄色片免费在线 | 日韩精品大片 | 亚洲欧美日韩国产一区二区 | 91av色| 丁香六月天婷婷 | 国产亲近乱来精品 | 麻豆精品在线视频 | 91看片在线看片 | 欧美美女激情18p | 欧美黑吊大战白妞欧美 | 91手机视频 | 91亚洲综合| 91av原创| 国产高清日韩 | 色综合五月天 | 99re国产 | 国产精品欧美久久久久无广告 | 在线91观看 | 九九综合久久 | 日韩欧美国产成人 | 美女视频黄频大全免费 | 久久精品精品电影网 | 久久久.com| 国产婷婷vvvv激情久 | 2023亚洲精品国偷拍自产在线 | 91在线国产观看 | 99久久婷婷国产精品综合 | 日本黄色免费电影网站 | 麻花豆传媒mv在线观看 | 黄色网址中文字幕 | 欧美精品久久99 | 永久免费的av电影 | 欧美在线你懂的 | 天天操天天干天天操天天干 | 久久久人人爽 | 久久精品视频2 | 日韩精品中文字幕一区二区 | 在线91播放 | se婷婷| 日韩二区三区在线 | 日韩欧美有码在线 | 欧美极品久久 | 国产欧美日韩一区 | 色欧美日韩 | 亚洲精品国产精品国自 | 97久久精品午夜一区二区 | 91精品国产乱码在线观看 | 91大神在线观看视频 | 国产69精品久久久久久久久久 | 久久久久久久久久久福利 | 2023av在线 | 麻花豆传媒mv在线观看 | 国内精品视频在线 | 99热精品视| 国产精品18久久久久久不卡孕妇 | 久久精视频 | 五月天丁香 | 激情五月婷婷综合 | 成人动漫精品一区二区 | 夜夜躁日日躁 | 成人在线网站观看 | 中文字幕一区二区三区在线观看 | 丁香婷婷电影 | 美女视频永久黄网站免费观看国产 | 国产一级免费观看视频 | 国产系列精品av | 久久精品视频在线免费观看 | 91看片一区二区三区 | 日韩丝袜在线 | 国产福利资源 | 四虎成人免费观看 | 精品9999 | 中文字幕大全 | 亚洲欧美国产精品 | 色婷婷骚婷婷 | 色播六月天 | 永久中文字幕 | 高清av网 | 色综合久久88色综合天天6 | 精品国产一区二区三区男人吃奶 | 国产黄在线 | 久久97久久 | 色综合久久中文综合久久牛 | 正在播放国产91 | 在线观看深夜视频 | 久久全国免费视频 | 中文字幕av在线电影 | 最新国产精品拍自在线播放 | 97视频在线免费 | 久久久久久久久久免费视频 | 欧美一级激情 | 麻豆传媒视频在线免费观看 | 国产网站在线免费观看 | 精品一二三区视频 | 国产xx在线 | 精品视频不卡 | 国产色视频123区 | 国产自产高清不卡 | 国产日韩欧美视频在线观看 | 成人免费看电影 | 五月天天天操 | 国产视频精品免费 | 国产精品porn | 天天射天天射 | 狠狠色丁香 | 婷婷精品| 一级黄色片毛片 | 亚洲视频网站在线观看 | 亚洲成人av一区二区 | 天天草天天摸 | 国产精品色婷婷 | 天天搞天天干 | 国产一区网址 | 日韩精品极品视频 | 免费国产在线观看 | 福利视频在线看 | 国产伦理久久精品久久久久_ | 欧美少妇18p| 欧美色综合天天久久综合精品 | 国模一二三区 | 亚洲午夜久久久综合37日本 | 国产最新91 | 五月婷婷激情 | 免费看一级一片 | 久久成人一区二区 | 亚洲国产小视频在线观看 | 免费网站观看www在线观看 | 欧美美女视频在线观看 | 99精品国产99久久久久久福利 | 精品xxx| 91av原创| 久久精美视频 | 国产一区二区高清 | 久久99精品国产 | 国产美女在线免费观看 | 丁香婷婷色月天 | 婷婷激情五月 | 色婷丁香| 在线观看日韩 | 中文字幕一区二 | 成年人在线播放视频 | 欧美日韩精品在线免费观看 | 一区二区影院 | 二区三区中文字幕 | 国产精品免费大片视频 | 精品在线播放视频 | 黄色av电影| 黄色网址在线播放 | 国产三级午夜理伦三级 | 日本系列中文字幕 | 天天爽人人爽夜夜爽 | 亚洲欧美综合精品久久成人 | 国产精品永久免费观看 | 日本女人的性生活视频 | 久久只精品99品免费久23小说 | 色一色在线 | 麻豆成人精品视频 | 亚洲区色 | 五月婷婷久久综合 | 久久久久久免费毛片精品 | 日韩免费在线视频 | 三上悠亚在线免费 | 日韩爱爱网站 | 国产精品欧美久久久久无广告 | 亚洲国产精久久久久久久 | 精品国产一区二区三区久久久 | 亚洲第一久久久 | 天天爱天天色 | 国产欧美日韩一区 | 欧美福利在线播放 | 日韩久久网站 | 美女在线观看网站 | 婷婷精品国产欧美精品亚洲人人爽 | 97人人超碰在线 | 成人免费视频a | 欧美成年网站 | 黄色软件视频网站 | 久久96国产精品久久99软件 | 欧美日韩在线精品一区二区 | 97色国产| 天天干天天射天天操 | 亚洲综合五月天 | 免费av观看 | 国产精品高 | 视频二区在线 | 中文区中文字幕免费看 | 天堂网一区二区三区 | 国产精品久久久久久久久久免费看 | 免费69视频 | 精品视频久久久久久 | 国产精品嫩草影院99网站 | 亚洲专区路线二 | 国产精品夜夜夜一区二区三区尤 | 狠狠躁日日躁狂躁夜夜躁av | 久久精品激情 | 成人91在线 | 人人射av | 国产精品福利在线 | 日本中文字幕网址 | 99r在线精品 | 精精国产xxxx视频在线播放 | 国产精品一区二区白浆 | 欧美激精品 | 精品久久国产精品 | 精品中文字幕在线观看 | 91精品国自产在线观看欧美 | 欧美嫩草影院 | 日韩欧美在线一区 | 91香蕉视频色版 | 天天天色综合 | 激情视频免费在线观看 | 成人在线黄色电影 | 久草视频免费 | 久久99亚洲精品 | 很黄很色很污的网站 | 亚洲涩涩一区 | 欧美天天射 | 91看片一区二区三区 | 久久国产品 | 91欧美精品 | 激情欧美网| 欧美少妇18p | av千婊在线免费观看 | 国产精品美女视频网站 | 亚洲视频每日更新 | 国产精品久久久久久久久久妇女 | 欧洲一区精品 | 中文字幕在线播放一区 | 97超视频| 国产99久久精品一区二区300 | 在线看日韩 | 高清免费av在线 | 激情综合色图 | 曰韩在线 | 97精品国产97久久久久久久久久久久 | 亚洲综合色丁香婷婷六月图片 | 777xxx欧美| 国产在线综合视频 | 99久久超碰中文字幕伊人 | 亚洲毛片久久 | 免费三级大片 | 久久久精品久久日韩一区综合 | 久久久午夜视频 | 91精品黄色| 国产精品免费一区二区三区在线观看 | 成人在线播放网站 | 久久高清毛片 | 免费av福利 | 国产看片网站 | 狠狠狠狠狠狠干 | 欧美国产高清 | 偷拍视频一区 | 日韩精品一区二区在线观看 | 在线一级片 | 日韩女同av | 精品国产一区二区三区久久影院 | 一区三区在线欧 | 深爱婷婷网 | 色午夜影院 | 区一区二在线 | 三级黄色在线观看 | 国产麻豆果冻传媒在线观看 | 精品久久久久一区二区国产 | www.亚洲精品| 人人草天天草 | 激情五月在线 | 在线视频麻豆 | 日日日操 | 夜夜夜夜夜夜操 | 中文字幕乱码在线播放 | 成人av在线资源 | 久久美女免费视频 | 免费91麻豆精品国产自产在线观看 | 91av资源在线| 色久av| 在线观看免费日韩 | 欧美国产日韩久久 | 国产精品一区二区三区久久久 | 视频成人永久免费视频 | 国产手机在线精品 | 国产精品久久久久久av | 婷婷精品国产欧美精品亚洲人人爽 | 国产精品久久久久久五月尺 | 久久综合国产伦精品免费 | 视频在线99 | 亚洲国产丝袜在线观看 | 欧美日韩三级在线观看 | 久草综合视频 | 91在线www | 美女国产 | 五月婷婷在线播放 | 免费福利视频网 | 国产成人久久av977小说 | 亚洲精品中文字幕在线 | 久久久久免费 | 色com网| 国产精品一区二区久久精品爱涩 | 91免费版成人 | 91麻豆精品国产91久久久使用方法 | 六月丁香激情综合色啪小说 | 园产精品久久久久久久7电影 | 午夜av一区二区三区 | 青青河边草免费直播 | 天天操夜夜操国产精品 | 久久er99热精品一区二区 | 天天操夜夜摸 | 亚洲国产成人在线观看 | 日韩欧美一区二区三区视频 | 人人看看人人 | 精选久久 | 91av官网| 免费看的黄色的网站 | 免费看色网站 | 日韩a免费| 亚洲综合小说电影qvod | 精品一区二区在线观看 | 天天精品视频 | 91在线公开视频 | 91精品久久久久久久久 | 成人午夜片av在线看 | 丰满少妇在线观看网站 | 91福利影院在线观看 | 有没有在线观看av | 国产精品区二区三区日本 | 久久久精品小视频 | 久久精品7| av免费在线免费观看 | 特级大胆西西4444www | 人人藻人人澡人人爽 | 狠狠操狠狠干天天操 | 精品毛片一区二区免费看 | 波多野结依在线观看 | 综合国产在线观看 | 色香天天| 天堂av高清 | 欧美一区二区精品在线 | 日日夜夜操操操操 | 欧美黄污视频 | 在线看国产| 日韩欧美高清一区二区三区 | 国产精品一区二区三区99 | 日韩理论在线视频 | 国产精品一区二区视频 | 狠狠躁日日躁狂躁夜夜躁 | 国产日本在线 | 在线观看国产区 | 亚洲国产成人高清精品 | 欧美性爽爽 | 97人人艹| 亚洲开心激情 | 免费裸体视频网 | 欧美 日韩 国产 成人 在线 | 日韩视频一区二区 | 久久久久成人精品免费播放动漫 | 亚州精品天堂中文字幕 | 天堂av一区二区 | 香蕉在线观看视频 | www.在线观看av| 亚洲天堂网视频 | 久久av免费电影 | 亚洲日韩精品欧美一区二区 | 欧美日韩啪啪 | 嫩草av影院 | 成人免费看片98欧美 | 日本久久片| 成人h视频 | 在线激情av电影 | 日韩三区在线 | 日本一区二区高清不卡 | 公开超碰在线 | 中文字幕无吗 | 黄网在线免费观看 | 国产精品久久一 | 亚洲精品在线二区 | 久久精品国产免费 | 97夜夜澡人人双人人人喊 | 国产一区电影在线观看 | 97香蕉久久国产在线观看 | 久久国产精品久久久 | 天天射天天操天天 | 欧美aa级 | 在线看片一区 | av一区二区三区在线播放 | 亚洲一区美女视频在线观看免费 | 国产特级毛片aaaaaa毛片 | 新版资源中文在线观看 | 精品久久久久久综合日本 | 亚洲无人区小视频 | 天天操夜夜叫 | 色天天中文| 91精品一区二区三区蜜桃 | 中文字幕在线播出 | 麻豆一精品传二传媒短视频 | 欧美日韩国产精品爽爽 | 国产成人三级一区二区在线观看一 | 国产精品18久久久久久久久久久久 | 激情综合五月天 | 成人av在线一区二区 | 国产精品18久久久久久久久久久久 | 韩国视频一区二区三区 | 亚洲欧洲成人 | 国产一区二区久久精品 | 国产视频1| 日本一区二区不卡高清 | 国产成人一区二区三区免费看 | 中文网丁香综合网 | 中文字幕亚洲不卡 | 国产美女被啪进深处喷白浆视频 | 国产精品视频永久免费播放 | 人人狠狠综合久久亚洲婷 | 欧美日韩免费观看一区=区三区 | 亚洲电影久久 | 日日夜夜天天干 | 美女在线观看av | 激情欧美在线观看 | 国产午夜精品一区 | 欧美精品在线观看免费 | 婷婷色社区 | 男女啪啪网站 | 91成人精品一区在线播放69 | 一本色道久久综合亚洲二区三区 | 久久国产精品99精国产 | 日日干天天插 | 亚洲经典视频在线观看 | 视频福利在线观看 | 爱色av.com | 国产亚洲一级高清 | 成人黄色在线电影 | 久久久久久久久久久久99 | 国产一卡二卡四卡国 | 在线视频欧美日韩 | 狠狠色免费 | 免费观看视频黄 | 综合网天天射 | 国产精品婷婷午夜在线观看 | 97精品在线 | 成人午夜在线电影 | 国内精品久久久久国产 | 亚洲午夜精品久久久久久久久 | 日韩区视频 | 波多野结衣在线观看一区 | 一本一道久久a久久综合蜜桃 | 在线播放日韩av | 国产午夜精品久久久久久久久久 | 一二三区视频在线 | 中文字幕在线一二 | 久久精品久久精品久久精品 | 婷婷av资源 | 久久久久久国产精品美女 | 久久综合狠狠综合久久激情 | 天天爽天天爽 | 久久久久这里只有精品 | 免费在线国产 | 在线成人一区 | 亚洲全部视频 | 国产玖玖精品视频 | 99久久精品国产一区 | 国产色区 | www色片| 在线岛国av | 亚洲精品女人久久久 | 亚洲精品一区二区久 | 国产一级片免费播放 | 日韩视频免费观看高清 | 国产高清av | 视频一区二区三区视频 | 亚洲欧美日本国产 | 手机在线日韩视频 | 国产 字幕 制服 中文 在线 | 在线视频一二三 | 亚洲视频 视频在线 | 美女黄频在线观看 | 正在播放国产精品 | 国产99亚洲| 又黄又爽又湿又无遮挡的在线视频 | 成人小视频在线观看免费 | 黄色免费在线看 | 久久婷婷精品视频 | 欧美国产视频在线 | 91精品在线免费观看 | 91丨九色丨蝌蚪丰满 | 91成人免费在线视频 | 欧美伦理一区 | 亚洲国产午夜视频 | 国产成人一区在线 | 日韩黄色软件 | 亚洲黄色小说网址 | 国产高清av免费在线观看 | 国产精品视频线看 | 一级淫片在线观看 | 国产精品视频久久 | 韩国av免费在线观看 | 午夜私人影院 | 91视频高清 | 亚洲 欧美 变态 国产 另类 | 亚洲黄色av网址 | 在线看片一区 | 久久综合亚洲鲁鲁五月久久 | 探花视频在线版播放免费观看 | 久久国产精品视频 | 91丨九色丨国产丨porny精品 | 91亚色视频 | 色婷久久 | 黄色av电影| 国产片网站| 91香蕉视频在线下载 | 91女神的呻吟细腰翘臀美女 | 99久久这里有精品 | 国产亚洲精品福利 | 91最新视频在线观看 | 天天干视频在线 | av在线影片 | 97狠狠操 | 在线看一区 | 国产视频亚洲 | 狠狠插天天干 | 91av在线免费 | 天天天综合网 | 国产精品免费不卡 | 亚洲精品美女久久久久网站 | 国产成人久久久77777 | 国产精品入口a级 | 成人在线免费看 | 国产成人免费在线观看 |