青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

elva

[轉]Linux 的多線程編程的高效開發經驗

2009 年 4 月 23 日

本文中我們針對 Linux 上多線程編程的主要特性總結出 5 條經驗,用以改善 Linux 多線程編程的習慣和避免其中的開發陷阱。在本文中,我們穿插一些 Windows 的編程用例用以對比 Linux 特性,以加深讀者印象。

背景

Linux 平臺上的多線程程序開發相對應其他平臺(比如 Windows)的多線程 API 有一些細微和隱晦的差別。不注意這些 Linux 上的一些開發陷阱,常常會導致程序問題不窮,死鎖不斷。本文中我們從 5 個方面總結出 Linux 多線程編程上的問題,并分別引出相關改善的開發經驗,用以避免這些的陷阱。我們希望這些經驗可以幫助讀者們能更好更快的熟悉 Linux 平臺的多線程編程。

我們假設讀者都已經很熟悉 Linux 平臺上基本的線程編程的 Pthread 庫 API 。其他的第三方用以線程編程的庫,如 boost,將不會在本文中提及。本文中主要涉及的題材包括線程開發中的線程管理,互斥變量,條件變量等。進程概念將不會在本文中涉及。





回頁首


Linux 上線程開發 API 的概要介紹

多線程開發在 Linux 平臺上已經有成熟的 Pthread 庫支持。其涉及的多線程開發的最基本概念主要包含三點:線程,互斥鎖,條件。其中,線程操作又分線程的創建,退出,等待 3 種。互斥鎖則包括 4 種操作,分別是創建,銷毀,加鎖和解鎖。條件操作有 5 種操作:創建,銷毀,觸發,廣播和等待。其他的一些線程擴展概念,如信號燈等,都可以通過上面的三個基本元素的基本操作封裝出來。

線程,互斥鎖,條件在 Linux 平臺上對應的 API 可以用表 1 歸納。為了方便熟悉 Windows 線程編程的讀者熟悉 Linux 多線程開發的 API,我們在表中同時也列出 Windows SDK 庫中所對應的 API 名稱。


表 1. 線程函數列表
對象 操作 Linux Pthread API Windows SDK 庫對應 API
線程 創建 pthread_create CreateThread
退出 pthread_exit ThreadExit
等待 pthread_join WaitForSingleObject
互斥鎖 創建 pthread_mutex_init CreateMutex
銷毀 pthread_mutex_destroy CloseHandle
加鎖 pthread_mutex_lock WaitForSingleObject
解鎖 pthread_mutex_unlock ReleaseMutex
條件 創建 pthread_cond_init CreateEvent
銷毀 pthread_cond_destroy CloseHandle
觸發 pthread_cond_signal SetEvent
廣播 pthread_cond_broadcast SetEvent / ResetEvent
等待 pthread_cond_wait / pthread_cond_timedwait SingleObjectAndWait

多線程開發在 Linux 平臺上已經有成熟的 Pthread 庫支持。其涉及的多線程開發的最基本概念主要包含三點:線程,互斥鎖,條件。其中,線程操作又分線程的創建,退出,等待 3 種。互斥鎖則包括 4 種操作,分別是創建,銷毀,加鎖和解鎖。條件操作有 5 種操作:創建,銷毀,觸發,廣播和等待。其他的一些線程擴展概念,如信號燈等,都可以通過上面的三個基本元素的基本操作封裝出來。





回頁首


Linux 線程編程中的 5 條經驗

盡量設置 recursive 屬性以初始化 Linux 的互斥變量

互斥鎖是多線程編程中基本的概念,在開發中被廣泛使用。其調用次序層次清晰簡單:建鎖,加鎖,解鎖,銷毀鎖。但是需要注意的是,與諸如 Windows 平臺的互斥變量不同,在默認情況下,Linux 下的同一線程無法對同一互斥鎖進行遞歸加速,否則將發生死鎖。

所謂遞歸加鎖,就是在同一線程中試圖對互斥鎖進行兩次或兩次以上的行為。其場景在 Linux 平臺上的代碼可由清單 1 所示。


清單 1. Linux 重復對互斥鎖加鎖實例
// 通過默認條件建鎖
pthread_mutex_t *theMutex = new pthread_mutex_t;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutex_init(theMutex,&attr);
pthread_mutexattr_destroy(&attr);

// 遞歸加鎖
pthread_mutex_lock (theMutex);
pthread_mutex_lock (theMutex);
pthread_mutex_unlock (theMutex);
pthread_mutex_unlock (theMutex);

在以上代碼場景中,問題將出現在第二次加鎖操作。由于在默認情況下,Linux 不允許同一線程遞歸加鎖,因此在第二次加鎖操作時線程將出現死鎖。

Linux 互斥變量這種奇怪的行為或許對于特定的某些場景會所有用處,但是對于大多數情況下看起來更像是程序的一個 bug 。畢竟,在同一線程中對同一互斥鎖進行遞歸加鎖在尤其是二次開發中經常會需要。

這個問題與互斥鎖的中的默認 recursive 屬性有關。解決問題的方法就是顯式地在互斥變量初始化時將設置起 recursive 屬性。基于此,以上代碼其實稍作修改就可以很好的運行,只需要在初始化鎖的時候加設置一個屬性。請看清單 2 。


清單 2. 設置互斥鎖 recursive 屬性實例
pthread_mutexattr_init(&attr); 
// 設置 recursive 屬性
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE_NP);
pthread_mutex_init(theMutex,&attr);

因此,建議盡量設置 recursive 屬性以初始化 Linux 的互斥鎖,這樣既可以解決同一線程遞歸加鎖的問題,又可以避免很多情況下死鎖的發生。這樣做還有一個額外的好處,就是可以讓 Windows 和 Linux 下讓鎖的表現統一。

注意 Linux 平臺上觸發條件變量的自動復位問題

條件變量的置位和復位有兩種常用模型:第一種模型是當條件變量置位(signaled)以后,如果當前沒有線程在等待,其狀態會保持為置位 (signaled),直到有等待的線程進入被觸發,其狀態才會變為復位(unsignaled),這種模型的采用以 Windows 平臺上的 Auto-set Event 為代表。其狀態變化如圖 1 所示:


圖 1. Windows 的條件變量狀態變化流程
Windows 的條件變量狀態變化流程

第二種模型則是 Linux 平臺的 Pthread 所采用的模型,當條件變量置位(signaled)以后,即使當前沒有任何線程在等待,其狀態也會恢復為復位(unsignaled)狀態。其狀態變化如圖 2 所示:


圖 2. Linux 的條件變量狀態變化流程
Linux 的條件變量狀態變化流程

具體來說,Linux 平臺上 Pthread 下的條件變量狀態變化模型是這樣工作的:調用 pthread_cond_signal() 釋放被條件阻塞的線程時,無論存不存在被阻塞的線程,條件都將被重新復位,下一個被條件阻塞的線程將不受影響。而對于 Windows,當調用 SetEvent 觸發 Auto-reset 的 Event 條件時,如果沒有被條件阻塞的線程,那么條件將維持在觸發狀態,直到有新的線程被條件阻塞并被釋放為止。

這種差異性對于那些熟悉 Windows 平臺上的條件變量狀態模型而要開發 Linux 平臺上多線程的程序員來說可能會造成意想不到的尷尬結果。試想要實現一個旅客坐出租車的程序:旅客在路邊等出租車,調用條件等待。出租車來了,將觸發條 件,旅客停止等待并上車。一個出租車只能搭載一波乘客,于是我們使用單一觸發的條件變量。這個實現邏輯在第一個模型下即使出租車先到,也不會有什么問題, 其過程如圖 3 所示:


圖 3. 采用 Windows 條件變量模型的出租車實例流程
索引使用的容量要求

然而如果按照這個思路來在 Linux 上來實現,代碼看起來可能是清單 3 這樣。


清單 3. Linux 出租車案例代碼實例
……
// 提示出租車到達的條件變量
pthread_cond_t taxiCond;

// 同步鎖
pthread_mutex_t taxiMutex;

// 旅客到達等待出租車
void * traveler_arrive(void * name) {
cout<< ” Traveler: ” <<(char *)name<< ” needs a taxi now! ” <<endl;
pthread_mutex_lock(&taxiMutex);
pthread_cond_wait (&taxiCond, &taxtMutex);
pthread_mutex_unlock (&taxtMutex);
cout<< ” Traveler: ” << (char *)name << ” now got a taxi! ” <<endl;
pthread_exit( (void *)0 );
}

// 出租車到達
void * taxi_arrive(void *name) {
cout<< ” Taxi ” <<(char *)name<< ” arrives. ” <<endl;
pthread_cond_signal(&taxtCond);
pthread_exit( (void *)0 );
}

void main() {
// 初始化
taxtCond= PTHREAD_COND_INITIALIZER;
taxtMutex= PTHREAD_MUTEX_INITIALIZER;
pthread_t thread;
pthread_attr_t threadAttr;
pthread_attr_init(&threadAttr);

pthread_create(&thread, & threadAttr, taxt_arrive, (void *)( ” Jack ” ));
sleep(1);
pthread_create(&thread, &threadAttr, traveler_arrive, (void *)( ” Susan ” ));
sleep(1);
pthread_create(&thread, &threadAttr, taxi_arrive, (void *)( ” Mike ” ));
sleep(1);

return 0;
}

好的,運行一下,看看結果如清單 4 。


清單 4. 程序結果輸出
Taxi Jack arrives. 
Traveler Susan needs a taxi now!
Taxi Mike arrives.
Traveler Susan now got a taxi.

其過程如圖 4 所示:


圖 4. 采用 Linux 條件變量模型的出租車實例流程
圖 4. 采用linux條件變量模型的出租車實例流程

通過對比結果,你會發現同樣的邏輯,在 Linux 平臺上運行的結果卻完全是兩樣。對于在 Windows 平臺上的模型一, Jack 開著出租車到了站臺,觸發條件變量。如果沒顧客,條件變量將維持觸發狀態,也就是說 Jack 停下車在那里等著。直到 Susan 小姐來了站臺,執行等待條件來找出租車。 Susan 搭上 Jack 的出租車離開,同時條件變量被自動復位。

但是到了 Linux 平臺,問題就來了,Jack 到了站臺一看沒人,觸發的條件變量被直接復位,于是 Jack 排在等待隊列里面。來遲一秒的 Susan 小姐到了站臺卻看不到在那里等待的 Jack,只能等待,直到 Mike 開車趕到,重新觸發條件變量,Susan 才上了 Mike 的車。這對于在排隊系統前面的 Jack 是不公平的,而問題癥結是在于 Linux 平臺上條件變量觸發的自動復位引起的一個 Bug 。

條件變量在 Linux 平臺上的這種模型很難說好壞。但是在實際開發中,我們可以對代碼稍加改進就可以避免這種差異的發生。由于這種差異只發生在觸發沒有被線程等待在條件變量的 時刻,因此我們只需要掌握好觸發的時機即可。最簡單的做法是增加一個計數器記錄等待線程的個數,在決定觸發條件變量前檢查下該變量即可。改進后 Linux 函數如清單 5 所示。


清單 5. Linux 出租車案例代碼實例
……
// 提示出租車到達的條件變量
pthread_cond_t taxiCond;

// 同步鎖
pthread_mutex_t taxiMutex;

// 旅客人數,初始為 0
int travelerCount=0;

// 旅客到達等待出租車
void * traveler_arrive(void * name) {
cout<< ” Traveler: ” <<(char *)name<< ” needs a taxi now! ” <<endl;
pthread_mutex_lock(&taxiMutex);

// 提示旅客人數增加
travelerCount++;
pthread_cond_wait (&taxiCond, &taxiMutex);
pthread_mutex_unlock (&taxiMutex);
cout<< ” Traveler: ” << (char *)name << ” now got a taxi! ” <<endl;
pthread_exit( (void *)0 );
}

// 出租車到達
void * taxi_arrive(void *name)
{
cout<< ” Taxi ” <<(char *)name<< ” arrives. ” <<endl;

while(true)
{
pthread_mutex_lock(&taxiMutex);

// 當發現已經有旅客在等待時,才觸發條件變量
if(travelerCount>0)
{
pthread_cond_signal(&taxtCond);
pthread_mutex_unlock (&taxiMutex);
break;
}
pthread_mutex_unlock (&taxiMutex);
}

pthread_exit( (void *)0 );
}

因此我們建議在 Linux 平臺上要出發條件變量之前要檢查是否有等待的線程,只有當有線程在等待時才對條件變量進行觸發。

注意條件返回時互斥鎖的解鎖問題

在 Linux 調用 pthread_cond_wait 進行條件變量等待操作時,我們增加一個互斥變量參數是必要的,這是為了避免線程間的競爭和饑餓情況。但是當條件等待返回時候,需要注意的是一定不要遺漏對互斥變量進行解鎖。

Linux 平臺上的 pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) 函數返回時,互斥鎖 mutex 將處于鎖定狀態。因此之后如果需要對臨界區數據進行重新訪問,則沒有必要對 mutex 就行重新加鎖。但是,隨之而來的問題是,每次條件等待以后需要加入一步手動的解鎖操作。正如前文中乘客等待出租車的 Linux 代碼如清單 6 所示:


清單 6. 條件變量返回后的解鎖實例
void * traveler_arrive(void * name) { 
cout<< ” Traveler: ” <<(char *)name<< ” needs a taxi now! ” <<endl;
pthread_mutex_lock(&taxiMutex);
pthread_cond_wait (&taxiCond, &taxtMutex);
pthread_mutex_unlock (&taxtMutex);
cout<< ” Traveler: ” << (char *)name << ” now got a taxi! ” <<endl;
pthread_exit( (void *)0 );
}

這一點對于熟悉 Windows 平臺多線程開發的開發者來說尤為重要。 Windows 上的 SignalObjectAndWait() 函數是常與 Linux 平臺上的 pthread_cond_wait() 函數被看作是跨平臺編程時的一對等價函數。但是需要注意的是,兩個函數退出時的狀態是不一樣的。在 Windows 平臺上,SignalObjectAndWait(HANDLE a, HANDLE b, …… ) 方法在調用結束返回時的狀態是 a 和 b 都是置位(signaled)狀態,在普遍的使用方法中,a 經常是一個 Mutex 變量,在這種情況下,當返回時,Mutex a 處于解鎖狀態(signaled),Event b 處于置位狀態(signaled), 因此,對于 Mutex a 而言,我們不需要考慮解鎖的問題。而且,在 SignalObjectAndWait() 之后,如果需要對臨界區數據進行重新訪問,都需要調用 WaitForSingleObject() 重新加鎖。這一點剛好與 Linux 下的 pthread_cond_wait() 完全相反。

Linux 對于 Windows 的這一點額外解鎖的操作區別很重要,一定得牢記。否則從 Windows 移植到 Linux 上的條件等待操作一旦忘了結束后的解鎖操作,程序將肯定會發生死鎖。

等待的絕對時間問題

超時是多線程編程中一個常見的概念。例如,當你在 Linux 平臺下使用 pthread_cond_timedwait() 時就需要指定超時這個參數,以便這個 API 的調用者最多只被阻塞指定的時間間隔。但是如果你是第一次使用這個 API 時,首先你需要了解的就是這個 API 當中超時參數的特殊性(就如本節標題所提示的那樣)。我們首先來看一下這個 API 的定義。 pthread_cond_timedwait() 定義請看清單 7 。


清單 7. pthread_cond_timedwait() 函數定義
int pthread_cond_timedwait(pthread_cond_t *restrict cond, 
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);

參數 abstime 在這里用來表示和超時時間相關的一個參數,但是需要注意的是它所表示的是一個絕對時間,而不是一個時間間隔數值,只有當系統的當前時間達到或者超過 abstime 所表示的時間時,才會觸發超時事件。這對于擁有 Windows 平臺線程開發經驗的人來說可能尤為困惑。因為 Windows 平臺下所有的 API 等待參數(如 SignalObjectAndWait,等)都是相對時間,

假設我們指定相對的超時時間參數如 dwMilliseconds (單位毫秒)來調用和超時相關的函數,這樣就需要將 dwMilliseconds 轉化為 Linux 下的絕對時間參數 abstime 使用。常用的轉換方法如清單 8 所示:


清單 8. 相對時間到絕對時間轉換實例
/* get the current time */ 
struct timeval now;
gettimeofday(&now, NULL);

/* add the offset to get timeout value */
abstime ->tv_nsec = now.tv_usec * 1000 + (dwMilliseconds % 1000) * 1000000;
abstime ->tv_sec = now.tv_sec + dwMilliseconds / 1000;

Linux 的絕對時間看似簡單明了,卻是開發中一個非常隱晦的陷阱。而且一旦你忘了時間轉換,可以想象,等待你的錯誤將是多么的令人頭疼:如果忘了把相對時間轉換成 絕對時間,相當于你告訴系統你所等待的超時時間是過去式的 1970 年 1 月 1 號某個時間段,于是操作系統毫不猶豫馬上送給你一個 timeout 的返回值,然后你會舉著拳頭抱怨為什么另外一個同步線程耗時居然如此之久,并一頭扎進尋找耗時原因的深淵里。

正確處理 Linux 平臺下的線程結束問題

在 Linux 平臺下,當處理線程結束時需要注意的一個問題就是如何讓一個線程善始善終,讓其所占資源得到正確釋放。在 Linux 平臺默認情況下,雖然各個線程之間是相互獨立的,一個線程的終止不會去通知或影響其他的線程。但是已經終止的線程的資源并不會隨著線程的終止而得到釋放, 我們需要調用 pthread_join() 來獲得另一個線程的終止狀態并且釋放該線程所占的資源。 Pthread_join() 函數的定義如清單 9 。


清單 9. pthread_join 函數定義
int pthread_join(pthread_t th, void **thread_return);

調用該函數的線程將掛起,等待 th 所表示的線程的結束。 thread_return 是指向線程 th 返回值的指針。需要注意的是 th 所表示的線程必須是 joinable 的,即處于非 detached(游離)狀態;并且只可以有唯一的一個線程對 th 調用 pthread_join() 。如果 th 處于 detached 狀態,那么對 th 的 pthread_join() 調用將返回錯誤。

如果你壓根兒不關心一個線程的結束狀態,那么也可以將一個線程設置為 detached 狀態,從而來讓操作系統在該線程結束時來回收它所占的資源。將一個線程設置為 detached 狀態可以通過兩種方式來實現。一種是調用 pthread_detach() 函數,可以將線程 th 設置為 detached 狀態。其申明如清單 10 。


清單 10. pthread_detach 函數定義
int pthread_detach(pthread_t th);

另一種方法是在創建線程時就將它設置為 detached 狀態,首先初始化一個線程屬性變量,然后將其設置為 detached 狀態,最后將它作為參數傳入線程創建函數 pthread_create(),這樣所創建出來的線程就直接處于 detached 狀態。方法如清單 11 。


清單 11. 創建 detach 線程代碼實例
………………………………… .. 
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, THREAD_FUNCTION, arg);

總之為了在使用 Pthread 時避免線程的資源在線程結束時不能得到正確釋放,從而避免產生潛在的內存泄漏問題,在對待線程結束時,要確保該線程處于 detached 狀態,否著就需要調用 pthread_join() 函數來對其進行資源回收。





回頁首


總結與補充

本文以上部分詳細介紹了 Linux 的多線程編程的 5 條高效開發經驗。另外你也可以考慮嘗試其他一些開源類庫來進行線程開發。

1. Boost 庫

Boost 庫來自于由 C++ 標準委員會類庫工作組成員發起,致力于為 C++ 開發新的類庫的 Boost 組織。雖然該庫本身并不是針對多線程而產生,但是發展至今,其已提供了比較全面的多線程編程的 API 支持。 Boost 庫對于多線程支持的 API 風格上更類似于 Linux 的 Pthread 庫,差別在于其將線程,互斥鎖,條件等線程開發概念都封裝成了 C++ 類,以方便開發調用。 Boost 庫目前對跨平臺支持的很不錯,不僅支持 Windows 和 Linux ,還支持各種商用的 Unix 版本。如果開發者想使用高穩定性的統一線程編程接口減輕跨平臺開發的難度, Boost 庫將是首選。

2. ACE

ACE 全稱是 ADAPTIVE Communication Environment,它是一個免費的,開源的,面向對象的工具框架,用以開發并發訪問的軟件。由于 ACE 最初是面向網絡服務端的編程開發,因此對于線程開發的工具庫它也能提供很全面的支持。其支持的平臺也很全面,包括 Windows,Linux 和各種版本 Unix 。 ACE 的唯一問題是如果僅僅是用于線程編程,其似乎顯得有些過于重量級。而且其較復雜的配置也讓其部署對初學者而言并非易事。


原文地址:
http://www.ibm.com/developerworks/cn/linux/l-cn-mthreadps/index.html

posted on 2010-08-09 21:08 葉子 閱讀(1452) 評論(0)  編輯 收藏 引用 所屬分類: C\C++

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美在线影院| 欧美激情一区二区| 最新亚洲一区| 国产一区二区三区视频在线观看| 欧美久久影院| 久久男人资源视频| 嫩草成人www欧美| 欧美综合国产精品久久丁香| 亚洲一区二区三区视频播放| 亚洲性线免费观看视频成熟| 这里只有精品视频| 亚洲理论在线| 日韩小视频在线观看| 亚洲一区二区在线播放| 一本色道久久综合亚洲91| 亚洲精品综合在线| 欧美一区二区私人影院日本| 午夜精品偷拍| 久久久av毛片精品| 欧美精品播放| 欧美午夜视频一区二区| 国产精品久久久久av| 亚洲国产精品传媒在线观看 | 国产日产欧美精品| 国产精品久久福利| 久久人人97超碰精品888| 欧美不卡一区| 欧美精品色综合| 欧美日韩精品国产| 狠狠色伊人亚洲综合成人| 悠悠资源网久久精品| 91久久在线| 亚洲性夜色噜噜噜7777| 免费观看30秒视频久久| 亚洲国产精品黑人久久久| 亚洲人被黑人高潮完整版| 午夜日韩电影| 浪潮色综合久久天堂| 另类激情亚洲| 欧美日韩亚洲一区二区三区| 亚洲国产福利在线| 99视频一区二区| 午夜视频在线观看一区二区| 欧美成人高清视频| 一区二区三区视频观看| 欧美在线播放一区| 亚洲综合国产精品| 欧美三级午夜理伦三级中视频| 国产乱码精品一区二区三区忘忧草 | 久久精品国产99国产精品| 玖玖视频精品| 国产精品久久久久久av下载红粉 | 欧美在线视频全部完| 欧美freesex8一10精品| 国产精品久久久久国产a级| 一本色道久久综合狠狠躁篇的优点 | 久久九九全国免费精品观看| 91久久综合亚洲鲁鲁五月天| 性欧美8khd高清极品| 国产精品久久久久久久久免费樱桃 | 久久精品国产综合精品| 亚洲自拍偷拍色片视频| 欧美激情综合| 亚洲少妇最新在线视频| 久久久久99精品国产片| 亚洲人成网站999久久久综合| 久久久免费精品| 国产精品入口| 一区二区三区视频在线观看| 亚洲小说欧美另类社区| 亚洲精品视频一区二区三区| 久久久国产成人精品| 国产精品久久精品日日| 久久久久在线| 亚洲成人在线网| 欧美一级视频精品观看| 9人人澡人人爽人人精品| 欧美涩涩视频| 亚洲午夜羞羞片| 亚洲国产日韩欧美在线动漫| 久久视频在线视频| 亚洲精品之草原avav久久| 欧美第一黄色网| 久久人体大胆视频| 99视频超级精品| 亚洲激情啪啪| 欧美激情视频一区二区三区在线播放| 国产性色一区二区| 狼人社综合社区| 久久精品视频免费| 韩国免费一区| 老司机凹凸av亚洲导航| 欧美国产视频日韩| 亚洲精品日韩精品| 亚洲精品国产视频| 久久久久久久波多野高潮日日| 最新成人在线| 亚洲精品在线视频观看| 欧美视频二区36p| 亚洲视频欧美在线| 亚洲一区区二区| 国产在线精品一区二区中文| 久久婷婷国产综合精品青草| 欧美激情 亚洲a∨综合| 亚洲一区二区三区精品动漫| 亚洲特级片在线| 国产视频一区在线观看一区免费| 久久精品国产一区二区电影| 欧美啪啪一区| 久久精品99无色码中文字幕 | 欧美成人黑人xx视频免费观看| 日韩午夜免费| 亚洲欧美不卡| 亚洲国产一二三| 免费亚洲网站| 欧美性猛片xxxx免费看久爱| 久久久久国产精品一区| 欧美高清你懂得| 午夜一级久久| 久久综合五月天婷婷伊人| 久久福利影视| 免费视频一区二区三区在线观看| 欧美日韩国产一级片| 午夜精品久久久久久久99黑人| 午夜精品久久久久99热蜜桃导演| 亚洲美洲欧洲综合国产一区| 久久久久久久成人| 久久av红桃一区二区小说| 欧美精品九九99久久| 欧美电影在线观看完整版| 国产一区二区三区久久久久久久久| 99视频在线精品国自产拍免费观看 | 91久久线看在观草草青青| 亚洲欧美一级二级三级| 亚洲欧美日韩综合国产aⅴ| 欧美日本一道本在线视频| 亚洲电影自拍| 亚洲国产精品一区在线观看不卡| 久久国产精品久久久久久久久久| 久久爱www.| 国产一区二区三区久久| 性欧美办公室18xxxxhd| 久久久久久穴| 精品9999| 久久综合狠狠综合久久综合88| 老司机久久99久久精品播放免费| 极品少妇一区二区| 久热成人在线视频| 亚洲国产欧洲综合997久久| 亚洲伦理网站| 欧美日韩国产综合网 | 欧美黄色片免费观看| 日韩视频免费观看| 性久久久久久久久久久久| 国产老女人精品毛片久久| 久久国产精品黑丝| 欧美国产精品v| 一片黄亚洲嫩模| 国产精品久久久久久久久久尿| 午夜精品久久久久久99热| 久久在线播放| 99精品国产在热久久| 国产精品国色综合久久| 久久国产婷婷国产香蕉| 欧美激情中文字幕乱码免费| 一区二区三区日韩在线观看 | 另类亚洲自拍| 亚洲精品在线观看视频| 欧美一区二区女人| 亚洲国产成人在线视频| 欧美午夜宅男影院在线观看| 欧美一区高清| 亚洲乱码国产乱码精品精天堂| 欧美一级久久| 亚洲精品一区二区三区不| 国产精品一区二区久久国产| 久久综合久久久| 亚洲天堂av图片| 欧美激情欧美狂野欧美精品| 亚洲欧美日韩国产综合| 亚洲激情自拍| 国产欧美亚洲视频| 欧美日韩八区| 美女脱光内衣内裤视频久久影院 | 欧美不卡高清| 亚洲午夜羞羞片| 欧美va亚洲va香蕉在线| 亚洲综合电影一区二区三区| 在线日韩成人| 国产精品久久网站| 欧美激情综合网| 久久久久久久999| 亚洲综合日韩在线| 亚洲美女av黄| 欧美国产综合视频| 久久精品一区二区三区不卡| 亚洲午夜久久久久久久久电影网| 亚洲激情小视频| 亚洲成人资源网| 国内一区二区在线视频观看|