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

elva

[轉]Linux 的多線程編程的高效開發(fā)經(jīng)驗

2009 年 4 月 23 日

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

背景

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

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





回頁首


Linux 上線程開發(fā) API 的概要介紹

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

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


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

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





回頁首


Linux 線程編程中的 5 條經(jīng)驗

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

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

所謂遞歸加鎖,就是在同一線程中試圖對互斥鎖進行兩次或兩次以上的行為。其場景在 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);

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

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

這個問題與互斥鎖的中的默認 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 的互斥鎖,這樣既可以解決同一線程遞歸加鎖的問題,又可以避免很多情況下死鎖的發(fā)生。這樣做還有一個額外的好處,就是可以讓 Windows 和 Linux 下讓鎖的表現(xiàn)統(tǒng)一。

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

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


圖 1. Windows 的條件變量狀態(tài)變化流程
Windows 的條件變量狀態(tài)變化流程

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


圖 2. Linux 的條件變量狀態(tài)變化流程
Linux 的條件變量狀態(tài)變化流程

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

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


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

然而如果按照這個思路來在 Linux 上來實現(xiàn),代碼看起來可能是清單 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條件變量模型的出租車實例流程

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

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

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


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

// 同步鎖
pthread_mutex_t taxiMutex;

// 旅客人數(shù),初始為 0
int travelerCount=0;

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

// 提示旅客人數(shù)增加
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);

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

pthread_exit( (void *)0 );
}

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

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

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

Linux 平臺上的 pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) 函數(shù)返回時,互斥鎖 mutex 將處于鎖定狀態(tài)。因此之后如果需要對臨界區(qū)數(shù)據(jù)進行重新訪問,則沒有必要對 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 平臺多線程開發(fā)的開發(fā)者來說尤為重要。 Windows 上的 SignalObjectAndWait() 函數(shù)是常與 Linux 平臺上的 pthread_cond_wait() 函數(shù)被看作是跨平臺編程時的一對等價函數(shù)。但是需要注意的是,兩個函數(shù)退出時的狀態(tài)是不一樣的。在 Windows 平臺上,SignalObjectAndWait(HANDLE a, HANDLE b, …… ) 方法在調用結束返回時的狀態(tài)是 a 和 b 都是置位(signaled)狀態(tài),在普遍的使用方法中,a 經(jīng)常是一個 Mutex 變量,在這種情況下,當返回時,Mutex a 處于解鎖狀態(tài)(signaled),Event b 處于置位狀態(tài)(signaled), 因此,對于 Mutex a 而言,我們不需要考慮解鎖的問題。而且,在 SignalObjectAndWait() 之后,如果需要對臨界區(qū)數(shù)據(jù)進行重新訪問,都需要調用 WaitForSingleObject() 重新加鎖。這一點剛好與 Linux 下的 pthread_cond_wait() 完全相反。

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

等待的絕對時間問題

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


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

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

假設我們指定相對的超時時間參數(shù)如 dwMilliseconds (單位毫秒)來調用和超時相關的函數(shù),這樣就需要將 dwMilliseconds 轉化為 Linux 下的絕對時間參數(shù) 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 的絕對時間看似簡單明了,卻是開發(fā)中一個非常隱晦的陷阱。而且一旦你忘了時間轉換,可以想象,等待你的錯誤將是多么的令人頭疼:如果忘了把相對時間轉換成 絕對時間,相當于你告訴系統(tǒng)你所等待的超時時間是過去式的 1970 年 1 月 1 號某個時間段,于是操作系統(tǒng)毫不猶豫馬上送給你一個 timeout 的返回值,然后你會舉著拳頭抱怨為什么另外一個同步線程耗時居然如此之久,并一頭扎進尋找耗時原因的深淵里。

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

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


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

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

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


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

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


清單 11. 創(chuàng)建 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 狀態(tài),否著就需要調用 pthread_join() 函數(shù)來對其進行資源回收。





回頁首


總結與補充

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

1. Boost 庫

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

2. ACE

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


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

posted on 2010-08-09 21:08 葉子 閱讀(1458) 評論(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>
            亚洲国产欧美一区二区三区同亚洲 | 久久久99国产精品免费| 欧美片在线观看| 中文精品视频| 一区二区高清| 国产精品婷婷午夜在线观看| 中文欧美在线视频| 亚洲视频观看| 国模叶桐国产精品一区| 你懂的网址国产 欧美| 美女诱惑一区| 一区二区欧美亚洲| 亚洲欧美精品suv| 精品成人一区二区三区四区| 亚洲第一在线视频| 欧美日韩在线直播| 久久av在线| 浪潮色综合久久天堂| 日韩视频一区二区三区在线播放免费观看 | 国产精品久久久久7777婷婷| 欧美亚洲日本国产| 久久精品中文字幕一区二区三区| 亚洲国产精品一区在线观看不卡 | 一区二区三区欧美在线| 国产麻豆综合| 亚洲电影观看| 国产精品手机在线| 欧美成人一区二区三区片免费| 欧美激情国产日韩精品一区18| 亚洲欧美日韩精品| 久久午夜视频| 欧美一区二区久久久| 免费在线成人| 久久精彩免费视频| 欧美美女喷水视频| 久久综合狠狠综合久久综合88| 欧美激情四色 | 久久国产精品亚洲va麻豆| 老司机久久99久久精品播放免费| 亚洲香蕉在线观看| 美国成人毛片| 久久久综合免费视频| 欧美日韩在线播| 欧美大片一区二区| 国产欧美日韩麻豆91| 亚洲精品在线观| 亚洲国产另类精品专区| 篠田优中文在线播放第一区| 99re视频这里只有精品| 久久久人成影片一区二区三区观看 | 欧美中文在线视频| 欧美日韩精品是欧美日韩精品| 噜噜噜噜噜久久久久久91| 国产精品对白刺激久久久| 欧美激情一区二区在线| 激情五月***国产精品| 亚洲嫩草精品久久| 亚洲综合三区| 欧美日韩一区在线视频| 亚洲区国产区| 亚洲美女毛片| 欧美激情第1页| 亚洲福利av| 亚洲精品国产视频| 欧美不卡在线| 亚洲人成网站999久久久综合| 亚洲黄色高清| 欧美成人免费在线| 亚洲国产精品久久精品怡红院 | 国产欧美三级| 性伦欧美刺激片在线观看| 亚洲欧美日韩视频一区| 国产精品毛片va一区二区三区| 99riav1国产精品视频| 亚洲图片激情小说| 国产精品porn| 亚洲欧美一区二区三区极速播放| 午夜精品久久久久久久白皮肤| 国产精品入口尤物| 午夜精品一区二区三区在线视| 欧美亚洲一区| 国外成人在线| 欧美高清不卡在线| 亚洲精品无人区| 亚洲欧美日韩电影| 国产婷婷色一区二区三区在线 | 亚洲免费小视频| 久久人人97超碰精品888| 黄色精品在线看| 欧美激情第10页| 亚洲午夜羞羞片| 久久美女性网| 亚洲精选在线观看| 国产精品入口| 久久久夜精品| 亚洲另类黄色| 久久激情视频免费观看| 亚洲国产美女精品久久久久∴| 欧美人成在线视频| 午夜精品久久久久久久99水蜜桃| 久久天堂精品| 亚洲午夜高清视频| 在线观看日韩国产| 欧美日韩精品一区二区三区| 亚洲欧美怡红院| 亚洲国产精品99久久久久久久久| 亚洲伊人网站| 亚洲高清不卡在线| 国产精品自拍视频| 欧美xart系列高清| 欧美一级淫片aaaaaaa视频| 亚洲成人资源网| 久久精品99国产精品| 亚洲免费观看| 影视先锋久久| 国产精品美女久久久久久久| 免费不卡在线视频| 欧美在线视频免费| 亚洲精品在线免费观看视频| 另类国产ts人妖高潮视频| 亚洲尤物在线| 亚洲人被黑人高潮完整版| 国产在线视频欧美| 国产精品美女一区二区| 欧美精品 国产精品| 久久国产一区| 欧美一区二区三区在线播放| 一区二区三区四区五区精品| 欧美激情第8页| 免费成人高清| 久久一区精品| 久久久久久高潮国产精品视| 亚洲综合视频1区| 亚洲一卡久久| 亚洲一区国产| 宅男在线国产精品| 99精品欧美一区二区三区| 亚洲韩国日本中文字幕| 国产有码在线一区二区视频| 国产精品视频福利| 国产精品人人爽人人做我的可爱 | 国产精品看片你懂得| 欧美日韩大片一区二区三区| 欧美成人精品影院| 欧美大秀在线观看| 欧美高清在线一区| 欧美激情在线狂野欧美精品| 欧美黄污视频| 欧美日韩精品是欧美日韩精品| 欧美日韩hd| 国产精品国产成人国产三级| 欧美三级网址| 国产欧美日韩亚洲一区二区三区| 国产精品免费看片| 国产日本欧美在线观看| 国产女主播在线一区二区| 国产乱人伦精品一区二区 | 国产日韩精品一区观看| 国产精品午夜在线| 国产午夜精品在线观看| 韩国欧美一区| 亚洲二区三区四区| 99在线热播精品免费| 亚洲欧美成人网| 久久久久国产一区二区三区四区| 久久九九有精品国产23| 麻豆精品精品国产自在97香蕉| 欧美国产视频在线| 亚洲裸体俱乐部裸体舞表演av| 亚洲日本乱码在线观看| 亚洲线精品一区二区三区八戒| 欧美中文字幕在线视频| 欧美激情国产高清| 国产精品试看| 亚洲三级毛片| 欧美一区二区精品久久911| 久热精品视频在线| 日韩亚洲国产欧美| 欧美一区二粉嫩精品国产一线天| 欧美+亚洲+精品+三区| 欧美午夜激情小视频| 国产一区二区三区自拍| 夜夜嗨av一区二区三区四季av| 欧美在线视频一区二区| 亚洲国产精品一区二区三区| 亚洲午夜伦理| 欧美精品123区| 国内精品久久久久国产盗摄免费观看完整版 | 国产美女精品免费电影| 最新精品在线| 欧美一区久久| 亚洲精品资源| 久久亚洲一区| 国产欧美亚洲精品| 99精品视频免费全部在线| 久久网站热最新地址| 一区二区三区四区五区精品| 欧美99久久| 激情综合色综合久久| 欧美一区二区三区四区高清 |