日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > C# >内容正文

C#

C#多线程编程系列(五)- C# ConcurrentBag的实现原理

發(fā)布時間:2023/12/10 C# 57 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C#多线程编程系列(五)- C# ConcurrentBag的实现原理 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

目錄

  • 一、前言
  • 二、ConcurrentBag類
  • 三、 ConcurrentBag線程安全實現(xiàn)原理
    • 1. ConcurrentBag的私有字段
    • 2. 用于數(shù)據(jù)存儲的ThreadLocalList類
    • 3. ConcurrentBag實現(xiàn)新增元素
    • 4. ConcurrentBag 如何實現(xiàn)迭代器模式
  • 四、總結
  • 筆者水平有限,如果錯誤歡迎各位批評指正!


一、前言#

筆者最近在做一個項目,項目中為了提升吞吐量,使用了消息隊列,中間實現(xiàn)了生產(chǎn)消費模式,在生產(chǎn)消費者模式中需要有一個集合,來存儲生產(chǎn)者所生產(chǎn)的物品,筆者使用了最常見的List<T>集合類型。

由于生產(chǎn)者線程有很多個,消費者線程也有很多個,所以不可避免的就產(chǎn)生了線程同步的問題。開始筆者是使用lock關鍵字,進行線程同步,但是性能并不是特別理想,然后有網(wǎng)友說可以使用SynchronizedList<T>來代替使用List<T>達到線程安全的目的。于是筆者就替換成了SynchronizedList<T>,但是發(fā)現(xiàn)性能依舊糟糕,于是查看了SynchronizedList<T>的源代碼,發(fā)現(xiàn)它就是簡單的在List<T>提供的API的基礎上加了lock,所以性能基本與筆者實現(xiàn)方式相差無幾。

最后筆者找到了解決的方案,使用ConcurrentBag<T>類來實現(xiàn),性能有很大的改觀,于是筆者查看了ConcurrentBag<T>的源代碼,實現(xiàn)非常精妙,特此在這記錄一下。

二、ConcurrentBag類#

ConcurrentBag<T>實現(xiàn)了IProducerConsumerCollection<T>接口,該接口主要用于生產(chǎn)者消費者模式下,可見該類基本就是為生產(chǎn)消費者模式定制的。然后還實現(xiàn)了常規(guī)的IReadOnlyCollection<T>類,實現(xiàn)了該類就需要實現(xiàn)IEnumerable<T>、IEnumerable、 ICollection類。

ConcurrentBag<T>對外提供的方法沒有List<T>那么多,但是同樣有Enumerable實現(xiàn)的擴展方法。類本身提供的方法如下所示。

名稱說明
Add將對象添加到 ConcurrentBag?中。
CopyTo從指定數(shù)組索引開始,將 ConcurrentBag?元素復制到現(xiàn)有的一維?Array?中。
Equals(Object)確定指定的?Object?是否等于當前的?Object。 (繼承自?Object。)
Finalize允許對象在“垃圾回收”回收之前嘗試釋放資源并執(zhí)行其他清理操作。 (繼承自?Object。)
GetEnumerator返回循環(huán)訪問 ConcurrentBag?的枚舉器。
GetHashCode用作特定類型的哈希函數(shù)。 (繼承自?Object。)
GetType獲取當前實例的?Type。 (繼承自?Object。)
MemberwiseClone創(chuàng)建當前?Object?的淺表副本。 (繼承自?Object。)
ToArray將 ConcurrentBag?元素復制到新數(shù)組。
ToString返回表示當前對象的字符串。 (繼承自?Object。)
TryPeek嘗試從 ConcurrentBag?返回一個對象但不移除該對象。
TryTake嘗試從 ConcurrentBag?中移除并返回對象。

三、 ConcurrentBag線程安全實現(xiàn)原理#

1. ConcurrentBag的私有字段#

ConcurrentBag線程安全實現(xiàn)主要是通過它的數(shù)據(jù)存儲的結構和細顆粒度的鎖。

?

Copy

public class ConcurrentBag<T> : IProducerConsumerCollection<T>, IReadOnlyCollection<T> { // ThreadLocalList對象包含每個線程的數(shù)據(jù) ThreadLocal<ThreadLocalList> m_locals; // 這個頭指針和尾指針指向中的第一個和最后一個本地列表,這些本地列表分散在不同線程中 // 允許在線程局部對象上枚舉 volatile ThreadLocalList m_headList, m_tailList; // 這個標志是告知操作線程必須同步操作 // 在GlobalListsLock 鎖中 設置 bool m_needSync; }

首選我們來看它聲明的私有字段,其中需要注意的是集合的數(shù)據(jù)是存放在ThreadLocal線程本地存儲中的。也就是說訪問它的每個線程會維護一個自己的集合數(shù)據(jù)列表,一個集合中的數(shù)據(jù)可能會存放在不同線程的本地存儲空間中,所以如果線程訪問自己本地存儲的對象,那么是沒有問題的,這就是實現(xiàn)線程安全的第一層,使用線程本地存儲數(shù)據(jù)

然后可以看到ThreadLocalList m_headList, m_tailList;這個是存放著本地列表對象的頭指針和尾指針,通過這兩個指針,我們就可以通過遍歷的方式來訪問所有本地列表。它使用volatile修飾,不允許線程進行本地緩存,每個線程的讀寫都是直接操作在共享內(nèi)存上,這就保證了變量始終具有一致性。任何線程在任何時間進行讀寫操作均是最新值。對于volatile修飾符,感謝我是攻城獅指出描述錯誤。

最后又定義了一個標志,這個標志告知操作線程必須進行同步操作,這是實現(xiàn)了一個細顆粒度的鎖,因為只有在幾個條件滿足的情況下才需要進行線程同步。

2. 用于數(shù)據(jù)存儲的ThreadLocalList類#

接下來我們來看一下ThreadLocalList類的構造,該類就是實際存儲了數(shù)據(jù)的位置。實際上它是使用雙向鏈表這種結構進行數(shù)據(jù)存儲。

?

Copy

[Serializable] // 構造了雙向鏈表的節(jié)點 internal class Node { public Node(T value) { m_value = value; } public readonly T m_value; public Node m_next; public Node m_prev; } /// <summary> /// 集合操作類型 /// </summary> internal enum ListOperation { None, Add, Take }; /// <summary> /// 線程鎖定的類 /// </summary> internal class ThreadLocalList { // 雙向鏈表的頭結點 如果為null那么表示鏈表為空 internal volatile Node m_head; // 雙向鏈表的尾節(jié)點 private volatile Node m_tail; // 定義當前對List進行操作的種類 // 與前面的 ListOperation 相對應 internal volatile int m_currentOp; // 這個列表元素的計數(shù) private int m_count; // The stealing count // 這個不是特別理解 好像是在本地列表中 刪除某個Node 以后的計數(shù) internal int m_stealCount; // 下一個列表 可能會在其它線程中 internal volatile ThreadLocalList m_nextList; // 設定鎖定是否已進行 internal bool m_lockTaken; // The owner thread for this list internal Thread m_ownerThread; // 列表的版本,只有當列表從空變?yōu)榉强战y(tǒng)計是底層 internal volatile int m_version; /// <summary> /// ThreadLocalList 構造器 /// </summary> /// <param name="ownerThread">擁有這個集合的線程</param> internal ThreadLocalList(Thread ownerThread) { m_ownerThread = ownerThread; } /// <summary> /// 添加一個新的item到鏈表首部 /// </summary> /// <param name="item">The item to add.</param> /// <param name="updateCount">是否更新計數(shù).</param> internal void Add(T item, bool updateCount) { checked { m_count++; } Node node = new Node(item); if (m_head == null) { Debug.Assert(m_tail == null); m_head = node; m_tail = node; m_version++; // 因為進行初始化了,所以將空狀態(tài)改為非空狀態(tài) } else { // 使用頭插法 將新的元素插入鏈表 node.m_next = m_head; m_head.m_prev = node; m_head = node; } if (updateCount) // 更新計數(shù)以避免此添加同步時溢出 { m_count = m_count - m_stealCount; m_stealCount = 0; } } /// <summary> /// 從列表的頭部刪除一個item /// </summary> /// <param name="result">The removed item</param> internal void Remove(out T result) { // 雙向鏈表刪除頭結點數(shù)據(jù)的流程 Debug.Assert(m_head != null); Node head = m_head; m_head = m_head.m_next; if (m_head != null) { m_head.m_prev = null; } else { m_tail = null; } m_count--; result = head.m_value; } /// <summary> /// 返回列表頭部的元素 /// </summary> /// <param name="result">the peeked item</param> /// <returns>True if succeeded, false otherwise</returns> internal bool Peek(out T result) { Node head = m_head; if (head != null) { result = head.m_value; return true; } result = default(T); return false; } /// <summary> /// 從列表的尾部獲取一個item /// </summary> /// <param name="result">the removed item</param> /// <param name="remove">remove or peek flag</param> internal void Steal(out T result, bool remove) { Node tail = m_tail; Debug.Assert(tail != null); if (remove) // Take operation { m_tail = m_tail.m_prev; if (m_tail != null) { m_tail.m_next = null; } else { m_head = null; } // Increment the steal count m_stealCount++; } result = tail.m_value; } /// <summary> /// 獲取總計列表計數(shù), 它不是線程安全的, 如果同時調(diào)用它, 則可能提供不正確的計數(shù) /// </summary> internal int Count { get { return m_count - m_stealCount; } } }

從上面的代碼中我們可以更加驗證之前的觀點,就是ConcurentBag<T>在一個線程中存儲數(shù)據(jù)時,使用的是雙向鏈表,ThreadLocalList實現(xiàn)了一組對鏈表增刪改查的方法。

3. ConcurrentBag實現(xiàn)新增元素#

接下來我們看一看ConcurentBag<T>是如何新增元素的。

?

Copy

/// <summary> /// 嘗試獲取無主列表,無主列表是指線程已經(jīng)被暫停或者終止,但是集合中的部分數(shù)據(jù)還存儲在那里 /// 這是避免內(nèi)存泄漏的方法 /// </summary> /// <returns></returns> private ThreadLocalList GetUnownedList() { //此時必須持有全局鎖 Contract.Assert(Monitor.IsEntered(GlobalListsLock)); // 從頭線程列表開始枚舉 找到那些已經(jīng)被關閉的線程 // 將它所在的列表對象 返回 ThreadLocalList currentList = m_headList; while (currentList != null) { if (currentList.m_ownerThread.ThreadState == System.Threading.ThreadState.Stopped) { currentList.m_ownerThread = Thread.CurrentThread; // the caller should acquire a lock to make this line thread safe return currentList; } currentList = currentList.m_nextList; } return null; } /// <summary> /// 本地幫助方法,通過線程對象檢索線程線程本地列表 /// </summary> /// <param name="forceCreate">如果列表不存在,那么創(chuàng)建新列表</param> /// <returns>The local list object</returns> private ThreadLocalList GetThreadList(bool forceCreate) { ThreadLocalList list = m_locals.Value; if (list != null) { return list; } else if (forceCreate) { // 獲取用于更新操作的 m_tailList 鎖 lock (GlobalListsLock) { // 如果頭列表等于空,那么說明集合中還沒有元素 // 直接創(chuàng)建一個新的 if (m_headList == null) { list = new ThreadLocalList(Thread.CurrentThread); m_headList = list; m_tailList = list; } else { // ConcurrentBag內(nèi)的數(shù)據(jù)是以雙向鏈表的形式分散存儲在各個線程的本地區(qū)域中 // 通過下面這個方法 可以找到那些存儲有數(shù)據(jù) 但是已經(jīng)被停止的線程 // 然后將已停止線程的數(shù)據(jù) 移交到當前線程管理 list = GetUnownedList(); // 如果沒有 那么就新建一個列表 然后更新尾指針的位置 if (list == null) { list = new ThreadLocalList(Thread.CurrentThread); m_tailList.m_nextList = list; m_tailList = list; } } m_locals.Value = list; } } else { return null; } Debug.Assert(list != null); return list; } /// <summary> /// Adds an object to the <see cref="ConcurrentBag{T}"/>. /// </summary> /// <param name="item">The object to be added to the /// <see cref="ConcurrentBag{T}"/>. The value can be a null reference /// (Nothing in Visual Basic) for reference types.</param> public void Add(T item) { // 獲取該線程的本地列表, 如果此線程不存在, 則創(chuàng)建一個新列表 (第一次調(diào)用 add) ThreadLocalList list = GetThreadList(true); // 實際的數(shù)據(jù)添加操作 在AddInternal中執(zhí)行 AddInternal(list, item); } /// <summary> /// </summary> /// <param name="list"></param> /// <param name="item"></param> private void AddInternal(ThreadLocalList list, T item) { bool lockTaken = false; try { #pragma warning disable 0420 Interlocked.Exchange(ref list.m_currentOp, (int)ListOperation.Add); #pragma warning restore 0420 // 同步案例: // 如果列表計數(shù)小于兩個, 因為是雙向鏈表的關系 為了避免與任何竊取線程發(fā)生沖突 必須獲取鎖 // 如果設置了 m_needSync, 這意味著有一個線程需要凍結包 也必須獲取鎖 if (list.Count < 2 || m_needSync) { // 將其重置為None 以避免與竊取線程的死鎖 list.m_currentOp = (int)ListOperation.None; // 鎖定當前對象 Monitor.Enter(list, ref lockTaken); } // 調(diào)用 ThreadLocalList.Add方法 將數(shù)據(jù)添加到雙向鏈表中 // 如果已經(jīng)鎖定 那么說明線程安全 可以更新Count 計數(shù) list.Add(item, lockTaken); } finally { list.m_currentOp = (int)ListOperation.None; if (lockTaken) { Monitor.Exit(list); } } }

從上面代碼中,我們可以很清楚的知道Add()方法是如何運行的,其中的關鍵就是GetThreadList()方法,通過該方法可以獲取當前線程的數(shù)據(jù)存儲列表對象,假如不存在數(shù)據(jù)存儲列表,它會自動創(chuàng)建或者通過GetUnownedList()方法來尋找那些被停止但是還存儲有數(shù)據(jù)列表的線程,然后將數(shù)據(jù)列表返回給當前線程中,防止了內(nèi)存泄漏。

在數(shù)據(jù)添加的過程中,實現(xiàn)了細顆粒度的lock同步鎖,所以性能會很高。刪除和其它操作與新增類似,本文不再贅述。

4. ConcurrentBag 如何實現(xiàn)迭代器模式#

看完上面的代碼后,我很好奇ConcurrentBag<T>是如何實現(xiàn)IEnumerator來實現(xiàn)迭代訪問的,因為ConcurrentBag<T>是通過分散在不同線程中的ThreadLocalList來存儲數(shù)據(jù)的,那么在實現(xiàn)迭代器模式時,過程會比較復雜。

后面再查看了源碼之后,發(fā)現(xiàn)ConcurrentBag<T>為了實現(xiàn)迭代器模式,將分在不同線程中的數(shù)據(jù)全都存到一個List<T>集合中,然后返回了該副本的迭代器。所以每次訪問迭代器,它都會新建一個List<T>的副本,這樣雖然浪費了一定的存儲空間,但是邏輯上更加簡單了。

?

Copy

/// <summary> /// 本地幫助器方法釋放所有本地列表鎖 /// </summary> private void ReleaseAllLocks() { // 該方法用于在執(zhí)行線程同步以后 釋放掉所有本地鎖 // 通過遍歷每個線程中存儲的 ThreadLocalList對象 釋放所占用的鎖 ThreadLocalList currentList = m_headList; while (currentList != null) { if (currentList.m_lockTaken) { currentList.m_lockTaken = false; Monitor.Exit(currentList); } currentList = currentList.m_nextList; } } /// <summary> /// 從凍結狀態(tài)解凍包的本地幫助器方法 /// </summary> /// <param name="lockTaken">The lock taken result from the Freeze method</param> private void UnfreezeBag(bool lockTaken) { // 首先釋放掉 每個線程中 本地變量的鎖 // 然后釋放全局鎖 ReleaseAllLocks(); m_needSync = false; if (lockTaken) { Monitor.Exit(GlobalListsLock); } } /// <summary> /// 本地幫助器函數(shù)等待所有未同步的操作 /// </summary> private void WaitAllOperations() { Contract.Assert(Monitor.IsEntered(GlobalListsLock)); ThreadLocalList currentList = m_headList; // 自旋等待 等待其它操作完成 while (currentList != null) { if (currentList.m_currentOp != (int)ListOperation.None) { SpinWait spinner = new SpinWait(); // 有其它線程進行操作時,會將cuurentOp 設置成 正在操作的枚舉 while (currentList.m_currentOp != (int)ListOperation.None) { spinner.SpinOnce(); } } currentList = currentList.m_nextList; } } /// <summary> /// 本地幫助器方法獲取所有本地列表鎖 /// </summary> private void AcquireAllLocks() { Contract.Assert(Monitor.IsEntered(GlobalListsLock)); bool lockTaken = false; ThreadLocalList currentList = m_headList; // 遍歷每個線程的ThreadLocalList 然后獲取對應ThreadLocalList的鎖 while (currentList != null) { // 嘗試/最后 bllock 以避免在獲取鎖和設置所采取的標志之間的線程港口 try { Monitor.Enter(currentList, ref lockTaken); } finally { if (lockTaken) { currentList.m_lockTaken = true; lockTaken = false; } } currentList = currentList.m_nextList; } } /// <summary> /// Local helper method to freeze all bag operations, it /// 1- Acquire the global lock to prevent any other thread to freeze the bag, and also new new thread can be added /// to the dictionary /// 2- Then Acquire all local lists locks to prevent steal and synchronized operations /// 3- Wait for all un-synchronized operations to be done /// </summary> /// <param name="lockTaken">Retrieve the lock taken result for the global lock, to be passed to Unfreeze method</param> private void FreezeBag(ref bool lockTaken) { Contract.Assert(!Monitor.IsEntered(GlobalListsLock)); // 全局鎖定可安全地防止多線程調(diào)用計數(shù)和損壞 m_needSync Monitor.Enter(GlobalListsLock, ref lockTaken); // 這將強制同步任何將來的添加/執(zhí)行操作 m_needSync = true; // 獲取所有列表的鎖 AcquireAllLocks(); // 等待所有操作完成 WaitAllOperations(); } /// <summary> /// 本地幫助器函數(shù)返回列表中的包項, 這主要由 CopyTo 和 ToArray 使用。 /// 這不是線程安全, 應該被稱為凍結/解凍袋塊 /// 本方法是私有的 只有使用 Freeze/UnFreeze之后才是安全的 /// </summary> /// <returns>List the contains the bag items</returns> private List<T> ToList() { Contract.Assert(Monitor.IsEntered(GlobalListsLock)); // 創(chuàng)建一個新的List List<T> list = new List<T>(); ThreadLocalList currentList = m_headList; // 遍歷每個線程中的ThreadLocalList 將里面的Node的數(shù)據(jù) 添加到list中 while (currentList != null) { Node currentNode = currentList.m_head; while (currentNode != null) { list.Add(currentNode.m_value); currentNode = currentNode.m_next; } currentList = currentList.m_nextList; } return list; } /// <summary> /// Returns an enumerator that iterates through the <see /// cref="ConcurrentBag{T}"/>. /// </summary> /// <returns>An enumerator for the contents of the <see /// cref="ConcurrentBag{T}"/>.</returns> /// <remarks> /// The enumeration represents a moment-in-time snapshot of the contents /// of the bag. It does not reflect any updates to the collection after /// <see cref="GetEnumerator"/> was called. The enumerator is safe to use /// concurrently with reads from and writes to the bag. /// </remarks> public IEnumerator<T> GetEnumerator() { // Short path if the bag is empty if (m_headList == null) return new List<T>().GetEnumerator(); // empty list bool lockTaken = false; try { // 首先凍結整個 ConcurrentBag集合 FreezeBag(ref lockTaken); // 然后ToList 再拿到 List的 IEnumerator return ToList().GetEnumerator(); } finally { UnfreezeBag(lockTaken); } }

由上面的代碼可知道,為了獲取迭代器對象,總共進行了三步主要的操作。

  • 使用FreezeBag()方法,凍結整個ConcurrentBag<T>集合。因為需要生成集合的List<T>副本,生成副本期間不能有其它線程更改損壞數(shù)據(jù)。
  • 將ConcurrrentBag<T>生成List<T>副本。因為ConcurrentBag<T>存儲數(shù)據(jù)的方式比較特殊,直接實現(xiàn)迭代器模式困難,考慮到線程安全和邏輯,最佳的辦法是生成一個副本。
  • 完成以上操作以后,就可以使用UnfreezeBag()方法解凍整個集合。
  • 那么FreezeBag()方法是如何來凍結整個集合的呢?也是分為三步走。

  • 首先獲取全局鎖,通過Monitor.Enter(GlobalListsLock, ref lockTaken);這樣一條語句,這樣其它線程就不能凍結集合。
  • 然后獲取所有線程中ThreadLocalList的鎖,通過`AcquireAllLocks()方法來遍歷獲取。這樣其它線程就不能對它進行操作損壞數(shù)據(jù)。
  • 等待已經(jīng)進入了操作流程線程結束,通過WaitAllOperations()方法來實現(xiàn),該方法會遍歷每一個ThreadLocalList對象的m_currentOp屬性,確保全部處于None操作。
  • 完成以上流程后,那么就是真正的凍結了整個ConcurrentBag<T>集合,要解凍的話也類似。在此不再贅述。

    四、總結#

    下面給出一張圖,描述了ConcurrentBag<T>是如何存儲數(shù)據(jù)的。通過每個線程中的ThreadLocal來實現(xiàn)線程本地存儲,每個線程中都有這樣的結構,互不干擾。然后每個線程中的m_headList總是指向ConcurrentBag<T>的第一個列表,m_tailList指向最后一個列表。列表與列表之間通過m_locals?下的?m_nextList相連,構成一個單鏈表。

    數(shù)據(jù)存儲在每個線程的m_locals中,通過Node類構成一個雙向鏈表。
    PS: 要注意m_tailList和m_headList并不是存儲在ThreadLocal中,而是所有的線程共享一份。

    以上就是有關ConcurrentBag<T>類的實現(xiàn),筆者的一些記錄和解析。

    筆者水平有限,如果錯誤歡迎各位批評指正!

    附上ConcurrentBag<T>源碼地址:戳一戳

    作者:InCerry

    出處:https://www.cnblogs.com/InCerry/p/9497729.html

    版權:本文采用「署名 4.0 國際」知識共享許可協(xié)議進行許可。

    ?

    https://www.cnblogs.com/fancunwei/p/9442469.html

    orleans源碼里面有用過許多和線程/隊列相關的類,
    System.Collections.Concurrent下面的類,ConcurrentQueue和ConcurrentDictionary等,還有Interlocked/BlockingCollection等。

    @?它使用volatile修飾,所以它是線程安全的。有誤吧,volatile并不保證線程安全。
    可能這句話筆者描述有遺漏,因為多個線程同時訪問一個變量,允許每個線程進行本地緩存,這就導致了變量的不一致性。volatile修飾的變量,不允許線程進行本地緩存,每個線程的讀寫都是直接操作在共享內(nèi)存上,這就保證了變量始終具有一致性。引用指針的大小和CPU位數(shù)一般都是一致,所以操作使原子性的,線程安全的。

    ?

    ?

    ?

    總結

    以上是生活随笔為你收集整理的C#多线程编程系列(五)- C# ConcurrentBag的实现原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 日本久操 | 午夜精品成人毛片非洲 | 亚洲国产成人精品久久久 | 国产99久久久久久免费看 | 色综合网址 | 男人桶女人桶爽30分钟 | 国产91美女视频 | 亚洲免费福利视频 | 色一情一交一乱一区二区三区 | 国产xxxxx| 成人国产一区二区三区 | 3d动漫精品啪啪一区二区下载 | 午夜福利电影一区二区 | 在线色导航| 久久96视频 | 亚洲欧美在线视频观看 | 国产看片网站 | 67194午夜 | 国产黑丝一区二区 | 夜夜爽夜夜 | 波多野结衣一区二区三区中文字幕 | 日批在线观看 | 免费麻豆视频 | 欧美精品一区二区在线播放 | 老牛影视一区二区三区 | 夜夜福利| 天天色天天 | 亚洲一区二区三区高清 | 可以直接在线观看的av | 亚洲精品成人片在线观看精品字幕 | 影音先锋成人资源网站 | av免费观看网站 | 午夜激情成人 | 欧美一区二区在线视频观看 | 尤物一区 | 亚洲天堂男人的天堂 | 日本免费一区二区三区四区五六区 | av夜夜操 | 狠狠干天天操 | 少妇被又大又粗又爽毛片久久黑人 | 中文av免费 | 91中文字幕| 日本大尺度做爰呻吟舌吻 | 野花视频在线免费观看 | 成年人www | 鸥美毛片 | 丁香六月激情综合 | 在线免费观看成年人视频 | 亚洲av无码专区在线电影 | 日本大胆欧美人术艺术 | 日本精品一区二区三区四区 | 69天堂网 | cao死你| 91无打码 | 重口变态虐黄网站 | 天堂色播| 欧美国产第一页 | 99久久久无码国产精品免费 | av大片免费观看 | 久热免费视频 | 美女色网站 | 中文字幕乱码人妻无码久久95 | 五号特工组之偷天换月 | 精品黑人一区二区三区 | 91福利网站| 在线观看av的网站 | 天天噜日日噜 | 91蜜桃臀久久一区二区 | 无码国产精品一区二区免费式直播 | 久久视频在线播放 | 中文字幕狠狠干 | 国产精品自拍网站 | 日韩特级黄色片 | 色综合久久精品亚洲国产 | 亚洲高清在线播放 | 亚洲一级Av无码毛片久久精品 | 国产精品久久久久毛片大屁完整版 | 日韩av一区在线 | 欧美日韩黄色网 | 四虎影视永久地址 | 日av中文字幕 | 国产寡妇色xxⅹ交肉视频 | 超碰按摩| 丝袜操| 爱爱免费小视频 | 欧美 日韩 国产 亚洲 色 | 久久久久久无码午夜精品直播 | 成人免费视频网站 | 中文字幕在线观看视频一区二区 | 特级淫片裸体免费看 | 成人无码久久久久毛片 | 香港三级日本三级韩国三级 | 少妇特黄一区二区 | 一级片播放 | 欧美一区二区三区免费观看 | 久久综合一区二区 | 色窝窝无码一区二区三区成人网站 | 国产黄色精品视频 | 天天想你免费观看完整版高清电影 |