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

大龍的博客

常用鏈接

統(tǒng)計(jì)

最新評(píng)論

volatile編寫多線程程序的好幫手

來源:?作者:lphpc?2006-05-16?出處:巧巧讀書?
?新聞??效果??數(shù)據(jù)結(jié)構(gòu)??設(shè)計(jì)??操作系統(tǒng)??

?????? 并不是我故意想弄糟你的心情,但是在這期專欄里,我們將討論多線程編程這一話題。正如上一期Generic里所說的,編寫異常安全(exception-safe)的程序是非常困難的,但是和編寫多線程程序比起來,那簡(jiǎn)直就是兒戲。

?????? 多線程的程序是出了名的難編寫、難驗(yàn)證、難調(diào)試、難維護(hù),這通常是件苦差事。不正確的多線程程序可能可以運(yùn)行很多年也不出一點(diǎn)錯(cuò),直到滿足某些臨界的條件時(shí),才出現(xiàn)意想不到的奇怪錯(cuò)誤。

?????? 不用說,編寫多線程程序的程序員需要使用可能得到的所有幫助。這期專欄將專注于討論競(jìng)爭(zhēng)條件(race conditions)——這通常是多線程程序中各種麻煩的根源——深入了解它并提供一些工具來防止競(jìng)爭(zhēng)。令人驚異的是,我們將讓編譯器盡其所能來幫助你做這些事。


?????? 僅僅一個(gè)不起眼的關(guān)鍵字。
????? 盡管C和C++標(biāo)準(zhǔn)對(duì)于線程都明顯的“保持沉默”,但它們以volatile關(guān)鍵字的形式,確實(shí)為多線程保留了一點(diǎn)特權(quán)。

?????? 就象大家更熟悉的const一樣,volatile是一個(gè)類型修飾符(type modifier)。它是被設(shè)計(jì)用來修飾被不同線程訪問和修改的變量。如果沒有volatile,基本上會(huì)導(dǎo)致這樣的結(jié)果:要么無法編寫多線程程序,要么編譯器失去大量?jī)?yōu)化的機(jī)會(huì)。下面我們來一個(gè)個(gè)說明。

考慮下面的代碼:

代碼:

class Gadget
{
public:
void Wait()
{
while (!flag_)
{
Sleep(1000); // sleeps for 1000 milliseconds
}
}
void Wakeup()
{
flag_ = true;
}
...
private:
bool flag_;
};





上面代碼中Gadget::Wait的目的是每過一秒鐘去檢查一下flag_成員變量,當(dāng)flag_被另一個(gè)線程設(shè)為true時(shí),該函數(shù)才會(huì)返回。至少這是程序作者的意圖,然而,這個(gè)Wait函數(shù)是錯(cuò)誤的。
假設(shè)編譯器發(fā)現(xiàn)Sleep(1000)是調(diào)用一個(gè)外部的庫函數(shù),它不會(huì)改變成員變量flag_,那么編譯器就可以斷定它可以把flag_緩存在寄存器中,以后可以訪問該寄存器來代替訪問較慢的主板上的內(nèi)存。這對(duì)于單線程代碼來說是一個(gè)很好的優(yōu)化,但是在現(xiàn)在這種情況下,它破壞了程序的正確性:當(dāng)你調(diào)用了某個(gè)Gadget的Wait函數(shù)后,即使另一個(gè)線程調(diào)用了Wakeup,Wait還是會(huì)一直循環(huán)下去。這是因?yàn)閒lag_的改變沒有反映到緩存它的寄存器中去。編譯器的優(yōu)化未免有點(diǎn)太……樂觀了。

在大多數(shù)情況下,把變量緩存在寄存器中是一個(gè)非常有價(jià)值的優(yōu)化方法,如果不用的話很可惜。C和C++給你提供了顯式禁用這種緩存優(yōu)化的機(jī)會(huì)。如果你聲明變量是使用了volatile修飾符,編譯器就不會(huì)把這個(gè)變量緩存在寄存器里——每次訪問都將去存取變量在內(nèi)存中的實(shí)際位置。這樣你要對(duì)Gadget的Wait/Wakeup做的修改就是給flag_加上正確的修飾:

class Gadget
{
public:
... as above ...
private:
volatile bool flag_;
};

大多數(shù)關(guān)于volatile的原理和用法的解釋就到此為止,并且建議你用volatile修飾在多個(gè)線程中使用的原生類型變量。然而,你可以用volatile做更多的事,因?yàn)樗巧衿娴腃++類型系統(tǒng)的一部分。


把volatile用于自定義類型
volatile修飾不僅可以用于原生類型,也可以用于自定義類型。這時(shí)候,volatile修飾方式類似于const(你也可以對(duì)一個(gè)類型同時(shí)使用const和volatile)。

與const不同,volatile的作用對(duì)于原生類型和自定義類型是有區(qū)別的。就是說,原生類型有volatile修飾時(shí),仍然支持它們的各種操作(加、乘、賦值等等),然而對(duì)于class來說,就不是這樣。舉例來說,你可以把一個(gè)非volatile的int的值賦給一個(gè)volatile的int,但是你不能把一個(gè)非volatile的對(duì)象賦給一個(gè)volatile對(duì)象。

讓我們舉個(gè)例子來說明自定義類型的volatile是怎么工作的。
代碼:

class Gadget
{
public:
void Foo() volatile;
void Bar();
...
private:
String name_;
int state_;
};
...
Gadget regularGadget;
volatile Gadget volatileGadget;




如果你認(rèn)為volatile對(duì)于對(duì)象來說沒有什么作用的話,那你可要大吃一驚了。
volatileGadget.Foo(); // ok, volatile fun called for
// volatile object
regularGadget.Foo(); // ok, volatile fun called for
// non-volatile object
volatileGadget.Bar(); // error! Non-volatile function called for
// volatile object!

從沒有volatile修飾的類型到相應(yīng)的volatile類型的轉(zhuǎn)換是很平常的。但是,就象const一樣,你不能反過來把volatile類型轉(zhuǎn)換為非volatile類型。你必須用類型轉(zhuǎn)換運(yùn)算符:
Gadget& ref = const_cast<Gadget&>(volatileGadget);
ref.Bar(); // ok

一個(gè)有volatile修飾的類只允許訪問其接口的一個(gè)子集,這個(gè)子集由類的實(shí)現(xiàn)者來控制。用戶只有用const_cast才可以訪問這個(gè)類型的全部接口。而且,象const一樣,類的volatile屬性會(huì)傳遞給它的成員(例如,volatileGadget.name_和volatileGadget.state_也是volatile變量)。

volatile,臨界區(qū)和競(jìng)爭(zhēng)條件
多線程程序中最簡(jiǎn)單也是最常用的同步機(jī)制要算是mutex(互斥對(duì)象)了。一個(gè)mutex只提供兩個(gè)基本操作:Acquire和Release。一旦某個(gè)線程調(diào)用了Acquire,其他線程再調(diào)用Acquire時(shí)就會(huì)被阻塞。當(dāng)這個(gè)線程調(diào)用Release后,剛才阻塞在Acquire里的線程中,會(huì)有一個(gè)且僅有一個(gè)被喚醒。換句話說,對(duì)于一個(gè)給定的mutex,只有一個(gè)線程可以在Acquire和Release調(diào)用之間獲取處理器時(shí)間。在Acquire和Release調(diào)用之間執(zhí)行的代碼叫做臨界區(qū)(critical section)。(Windows的用語可能會(huì)引起一點(diǎn)混亂,因?yàn)閃indows把mutex本身叫做臨界區(qū),而Windows的mutex實(shí)際上指進(jìn)程間的mutex。如果把它們分別叫作線程mutex和進(jìn)程mutex可能會(huì)好些。)

Mutex是用來避免數(shù)據(jù)出現(xiàn)競(jìng)爭(zhēng)條件。根據(jù)定義,所謂競(jìng)爭(zhēng)條件就是這樣一種情況:多個(gè)線程對(duì)數(shù)據(jù)產(chǎn)生的作用要依賴于線程的調(diào)度順序的。當(dāng)兩個(gè)線程競(jìng)相訪問同一數(shù)據(jù)時(shí),就會(huì)發(fā)生競(jìng)爭(zhēng)條件。因?yàn)橐粋€(gè)線程可以在任意一個(gè)時(shí)刻打斷其他線程,數(shù)據(jù)可能會(huì)被破壞或者被錯(cuò)誤地解釋。因此,對(duì)數(shù)據(jù)的修改操作,以及有些情況下的訪問操作,必須用臨界區(qū)保護(hù)起來。在面向?qū)ο蟮木幊讨校@通常意味著你在一個(gè)類的成員變量中保存一個(gè)mutex,然后在你訪問這個(gè)類的狀態(tài)時(shí)使用這個(gè)mutex。

多線程編程高手看了上面兩個(gè)段落,可能已經(jīng)在打哈欠了,但是它們的目的只是提供一個(gè)準(zhǔn)備練習(xí),我們現(xiàn)在要和volatile聯(lián)系起來了。我們將把C++的類型和線程的語義作一個(gè)對(duì)比。


在一個(gè)臨界區(qū)以外,任意線程會(huì)在任何時(shí)間打斷別的線程;這是不受控制的,所以被多個(gè)線程訪問的變量容易被改得面目全非。這和volatile的原意[1]是一致的——所以需要用volatile來防止編譯器無意地緩存這樣的變量。

在由一個(gè)mutex限定的臨界區(qū)里,只有一個(gè)線程可以進(jìn)入。因此,在臨界區(qū)中執(zhí)行的代碼有和單線程程序有相同的語義。被控制的變量不會(huì)再被意外改變——你可以去掉volatile修飾。

簡(jiǎn)而言之,線程間共享的數(shù)據(jù)在臨界區(qū)之外是volatile的,而在臨界區(qū)之內(nèi)則不是。

你通過對(duì)一個(gè)mutex加鎖來進(jìn)入一個(gè)臨界區(qū),然后你用const_cast去掉某個(gè)類型的volatile修飾,如果我們能成功地把這兩個(gè)操作放到一起,那么我們就在C++類型系統(tǒng)和應(yīng)用程序的線程語義建立起聯(lián)系。這樣我們可以讓編譯器來幫我們檢測(cè)競(jìng)爭(zhēng)條件。


LockingPtr
我們需要有一個(gè)工具來做mutex的獲取和const_cast兩個(gè)操作。讓我們來設(shè)計(jì)一個(gè)LockingPtr類,你需要用一個(gè)volatile的對(duì)象obj和一個(gè)mutex對(duì)象mtx來初始化它。在LockingPtr對(duì)象的生命期中,它會(huì)保證mtx處于被獲取狀態(tài),而且也提供對(duì)去掉volatile修飾的obj的訪問。對(duì)obj的訪問類似于smart pointer,是通過operator->和operator*來進(jìn)行的。const_cast是在LockingPtr內(nèi)部進(jìn)行。這個(gè)轉(zhuǎn)化在語義上是正確的,因?yàn)長(zhǎng)ockingPtr在其生存期中始終擁有mutex。

首先,我們來定義和LockingPtr一起工作的Mutex類的框架:

代碼:

class Mutex
{
public:
void Acquire();
void Release();
...
};




為了使用LockingPtr,你需要用操作系統(tǒng)提供的數(shù)據(jù)結(jié)構(gòu)和底層函數(shù)來實(shí)現(xiàn)Mutex。
LockingPtr是一個(gè)模板,用被控制變量的類型作為模板參數(shù)。例如,如果你希望控制一個(gè)Widget,你就要這樣寫LockingPtr <Widget>。

LockingPtr的定義很簡(jiǎn)單,它只是實(shí)現(xiàn)了一個(gè)單純的smart pointer。它關(guān)注的焦點(diǎn)只是在于把const_cast和臨界區(qū)操作放在一起。

代碼:

template <typename T>
class LockingPtr {
public:
// Constructors/destructors
LockingPtr(volatile T& obj, Mutex& mtx)
: pObj_(const_cast<T*>(&obj)),
pMtx_(&mtx)
{ mtx.Lock(); }
~LockingPtr()
{ pMtx_->Unlock(); }
// Pointer behavior
T& operator*()
{ return *pObj_; }
T* operator->()
{ return pObj_; }
private:
T* pObj_;
Mutex* pMtx_;
LockingPtr(const LockingPtr&);
LockingPtr& operator=(const LockingPtr&);
};




盡管很簡(jiǎn)單,LockingPtr對(duì)于編寫正確的多線程代碼非常有用。你應(yīng)該把線程間共享的對(duì)象聲明為volatile,但是永遠(yuǎn)不要對(duì)它們使用const_cast——你應(yīng)該始終是用LockingPtr的自動(dòng)對(duì)象(automatic objects)。讓我們舉例來說明。

比如說你有兩個(gè)線程需要共享一個(gè)vector<char>對(duì)象:

代碼:


class SyncBuf {
public:
void Thread1();
void Thread2();
private:
typedef vector<char> BufT;
volatile BufT buffer_;
Mutex mtx_; // controls access to buffer_
};




在一個(gè)線程的函數(shù)里,你只需要簡(jiǎn)單地使用一個(gè)LockingPtr<BufT>對(duì)象來獲取對(duì)buffer_成員變量的受控訪問:

代碼:

void SyncBuf::Thread1() {
LockingPtr<BufT> lpBuf(buffer_, mtx_);
BufT::iterator i = lpBuf->begin();
for (; i != lpBuf->end(); ++i) {
... use *i ...
}
}



這樣的代碼很容易編寫,也很容易理解——每當(dāng)你需要使用buffer_時(shí),你必須創(chuàng)建一個(gè)LockingPtr<BufT>來指向它。當(dāng)你這樣做了以后,你就可以訪問vector的全部接口。

這個(gè)方法的好處是,如果你犯了錯(cuò)誤,編譯器會(huì)指出它:
代碼:

void SyncBuf::Thread2() {
// Error! Cannot access 'begin' for a volatile object
BufT::iterator i = buffer_.begin();
// Error! Cannot access 'end' for a volatile object
for (; i != lpBuf->end(); ++i) {
... use *i ...
}
}




你不能訪問buffer_的任何函數(shù),除非你進(jìn)行了const_cast或者用LockingPtr。這兩者的區(qū)別是LockingPtr提供了一個(gè)有規(guī)則的方法來對(duì)一個(gè)volatile變量進(jìn)行const_cast。
LockingPtr有非常好的表達(dá)力。如果你只需要調(diào)用一個(gè)函數(shù),你可以創(chuàng)建一個(gè)無名的臨時(shí)LockingPtr對(duì)象,然后直接使用它:

代碼:

unsigned int SyncBuf::Size() {
return LockingPtr<BufT>(buffer_, mtx_)->size();
}




回到原生類型
我們已經(jīng)看到了volatile對(duì)于保護(hù)對(duì)象免于不受控的訪問是多么出色,并且看到了LockingPtr是怎么提供了一個(gè)簡(jiǎn)單有效的辦法來編寫線程安全的代碼。現(xiàn)在讓我們回到原生類型,volatile對(duì)它們的作用方式是不同的。

讓我們來考慮一個(gè)多個(gè)線程共享一個(gè)int變量的例子。

代碼:

class Counter
{
public:
...
void Increment() { ++ctr_; }
void Decrement() { --ctr_; }
private:
int ctr_;
};




如果Increment和Decrement是在不同的線程里被調(diào)用的,上面的代碼片斷里就有bug。首先,ctr_必須是volatile的。其次,即使是一個(gè)看上去是原子的操作,比如++ctr_,實(shí)際上也分為三個(gè)階段。內(nèi)存本身是沒有運(yùn)算功能的,當(dāng)對(duì)一個(gè)變量進(jìn)行增量操作時(shí),處理器會(huì):
把變量讀入寄存器
對(duì)寄存器里的值加1
把結(jié)果寫回內(nèi)存
這個(gè)三步操作稱為RMW(Read-Modify-Write)。在一個(gè)RMW操作的Modify階段,大多數(shù)處理器都會(huì)釋放內(nèi)存總線,以使其他處理器能夠訪問內(nèi)存。
如果在這個(gè)時(shí)候另一個(gè)處理器對(duì)同一個(gè)變量也進(jìn)行RMW操作,我們就遇到了一個(gè)競(jìng)爭(zhēng)條件:第二次寫入會(huì)覆蓋掉第一次的值。

為了防止這樣的事發(fā)生,你又要用到LockingPtr:

代碼:

class Counter
{
public:
...
void Increment() { ++*LockingPtr<int>(ctr_, mtx_); }
void Decrement() { --*LockingPtr<int>(ctr_, mtx_); }
private:
volatile int ctr_;
Mutex mtx_;
};



現(xiàn)在這段代碼正確了,但是和SyncBuf相比,這段代碼的質(zhì)量要差一些。為什么?因?yàn)閷?duì)于Counter,編譯器不會(huì)在你錯(cuò)誤地直接訪問ctr_(沒有對(duì)它加鎖)時(shí)產(chǎn)生警告。雖然ctr_是volatile的,但是編譯器還是可以編譯++ctr_,盡管產(chǎn)生的代碼絕對(duì)是不正確的。編譯器不再是你的盟友了,你只有自己留意競(jìng)爭(zhēng)條件。
那么你該怎么做呢?很簡(jiǎn)單,你可以用一個(gè)高層的結(jié)構(gòu)來包裝原生類型的數(shù)據(jù),然后對(duì)那個(gè)結(jié)構(gòu)使用volatile。這有點(diǎn)自相矛盾,直接用volatile修飾原生類型是一個(gè)不好的用法,盡管這是volatile最初期望的用法!


volatile成員函數(shù)
到現(xiàn)在為止,我們討論了具有volatile數(shù)據(jù)成員的類;現(xiàn)在讓我們來考慮設(shè)計(jì)這樣的類,它會(huì)作為更大的對(duì)象的一部分并且在線程間共享。這里,volatile的成員函數(shù)可以幫很大的忙。

在設(shè)計(jì)類的時(shí)候,你只對(duì)那些線程安全的成員函數(shù)加volatile修飾。你必須假定外面的代碼會(huì)在任何地方任何時(shí)間調(diào)用volatile成員函數(shù)。不要忘記:volatile相當(dāng)于自由的多線程代碼,并且沒有臨界區(qū);非volatile相當(dāng)于單線程的環(huán)境或者在臨界區(qū)內(nèi)。

比如說,你定義了一個(gè)Widget類,它用兩個(gè)方法實(shí)現(xiàn)了同一個(gè)操作——一個(gè)線程安全的方法和一個(gè)快速的不受保護(hù)的方法。

代碼:

class Widget
{
public:
void Operation() volatile;
void Operation();
...
private:
Mutex mtx_;
};



注意這里的重載(overloading)用法。現(xiàn)在Widget的用戶可以用一致的語法調(diào)用Operation,對(duì)于volatile對(duì)象可以得到線程安全性,對(duì)于普通對(duì)象可以得到速度。用戶必須注意把共享的Widget對(duì)象定義為volatile。
在實(shí)現(xiàn)volatile成員函數(shù)時(shí),第一個(gè)操作通常是用LockingPtr對(duì)this進(jìn)行加鎖,然后其余工作可以交給非volatile的同名函數(shù)做:

代碼:

void Widget::Operation() volatile
{
LockingPtr<Widget> lpThis(*this, mtx_);
lpThis->Operation(); // invokes the non-volatile function
}





小結(jié)
在編寫對(duì)線程程序的時(shí)候,使用volatile將對(duì)你十分有益。你必須堅(jiān)持下面的規(guī)則:

把所有共享對(duì)象聲明為volatile
不要對(duì)原生類型直接使用volatile
定義共享類時(shí),用volatile成員函數(shù)來表示它的線程安全性。
如果你這么做了,而且用了簡(jiǎn)單的通用組件LockingPtr,你就可以寫出線程安全的代碼,并且大大減少對(duì)競(jìng)爭(zhēng)條件的擔(dān)心,因?yàn)榫幾g器會(huì)替你操心,并且勤勤懇懇地為你指出哪里錯(cuò)了。

在我參與的幾個(gè)項(xiàng)目中,使用volatile和LockingPtr產(chǎn)生了很大效果。代碼十分整潔,也容易理解。我記得遇到過一些死鎖的情況,但是相對(duì)于競(jìng)爭(zhēng)條件,我寧愿對(duì)付死鎖的情況,因?yàn)樗鼈冋{(diào)試起來容易多了。那些項(xiàng)目實(shí)際上根本沒有碰到過有關(guān)競(jìng)爭(zhēng)條件的問題。

致謝
非常感謝James Kanze和Sorin Jianu提供了很有洞察力的意見。


致謝
非常感謝James Kanze和Sorin Jianu提供了很有洞察力的意見。

附:濫用volatile的本質(zhì)?[2]
在上一期的專欄《Generic<Programming>: volatile — Multithreaded Programmer's Best Friend》發(fā)表以后,我收到很多反饋意見。就像是注定的一樣,大部分稱贊都是私人信件,而抱怨都發(fā)到USENET新聞組comp.lang.c++.moderated和 comp.programming.threads里去了。隨后引起了很長(zhǎng)很激烈的討論,如果你對(duì)這個(gè)主題有興趣,你可以去看看這個(gè)討論,它的標(biāo)題是“volatile, was: memory visibility between threads.”。

我知道我從這個(gè)討論中學(xué)到了很多東西。比如說,文章開頭的Widget的例子不太切題。長(zhǎng)話短說,在很多系統(tǒng)(比如POSIX兼容的系統(tǒng))中,volatile修飾是不需要的,而在另一些系統(tǒng)中,即使加了volatile也沒有用,程序還是不正確。

關(guān)于volatile correctness,最重要的一個(gè)問題是它依賴于類似POSIX的mutex,如果在多處理器系統(tǒng)上,光靠mutex就不夠了——你必須用memory barriers。

另一個(gè)更哲理性的問題是:嚴(yán)格來說通過類型轉(zhuǎn)換把變量的volatile屬性去掉是不合法的,即使volatile屬性是你自己為了volatile correctness而加上去的。正如Anthony Williams指出的,可以想象一個(gè)系統(tǒng)可能把volatile數(shù)據(jù)放在一個(gè)不同于非volatile數(shù)據(jù)的存儲(chǔ)區(qū)中,在這種情況下,進(jìn)行地址變換會(huì)有不確定的行為。

另一個(gè)批評(píng)是volatile correctness雖然可以在一個(gè)較低層次上解決競(jìng)爭(zhēng)條件,但是不能正確的檢測(cè)出高層的、邏輯的競(jìng)爭(zhēng)條件。例如,你有一個(gè)mt_vector模版類,用來模擬std::vector,成員函數(shù)經(jīng)過正確的線程同步修正。考慮這段代碼:

volatile mt_vector<int> vec;

if (!vec.empty()) {
vec.pop_back();
}

這段代碼的目的是刪除vector里的最后一個(gè)元素,如果它存在的話。在單線程環(huán)境里,他工作地很好。然而如果你把它用在多線程程序里,這段代碼還是有可能拋出異常,盡管empty和pop_back都有正確的線程同步行為。雖然底層的數(shù)據(jù)(vec)的一致性有保證,但是高層操作的結(jié)果還是不確定的。
無論如何,經(jīng)過辯論之后,我還是保持我的建議,在有類POSIX的mutex的系統(tǒng)上,volatile correctness還是檢測(cè)競(jìng)爭(zhēng)條件的一個(gè)有價(jià)值的工具。但是如果你在一個(gè)支持內(nèi)存訪問重新排序的多處理器系統(tǒng)上,你首先需要仔細(xì)閱讀你的編譯器的文檔。你必須知己知彼。

最后,Kenneth Chiu提到了一篇非常有趣的文章http://theory.stanford.edu/~freunds/race.ps,猜猜題目是什么?“Type-Based Race Detection for Java”。這篇文章講了怎么對(duì)Java的類型系統(tǒng)作一點(diǎn)小小的補(bǔ)充,從而讓編譯器和程序員一起在編譯時(shí)檢測(cè)競(jìng)爭(zhēng)條件。

posted on 2006-12-05 12:06 大龍 閱讀(177) 評(píng)論(0)  編輯 收藏 引用


只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。
網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            欧美日韩精品一区二区三区| 一区二区三区国产盗摄| 亚洲一区二区欧美| 亚洲免费av片| 欧美美女福利视频| 亚洲精品乱码久久久久久日本蜜臀 | 国产日韩欧美一二三区| 亚洲免费成人av电影| 亚洲黑丝在线| 在线视频国内自拍亚洲视频| 国产自产精品| 韩国av一区二区三区| 国产在线乱码一区二区三区| 一区二区在线不卡| 亚洲欧洲一区二区三区在线观看 | 国产乱码精品一区二区三| 亚洲美女av在线播放| 亚洲国产精品成人一区二区| 亚洲第一在线视频| 美玉足脚交一区二区三区图片| 久久视频在线视频| 欧美国产日韩一区二区| 欧美片在线播放| 欧美性生交xxxxx久久久| 国产精品永久免费视频| 一区二区视频免费完整版观看| 亚洲国内精品| 亚洲欧美日韩国产综合在线| 久久精品女人的天堂av| 欧美激情精品久久久久久久变态| 亚洲黄色毛片| 在线视频日韩精品| 欧美亚洲综合在线| 免费久久久一本精品久久区| 久久午夜av| 欧美大片91| 一本高清dvd不卡在线观看| 欧美一区二区三区四区夜夜大片 | 亚洲欧美日韩一区二区在线 | 日韩视频免费观看| 亚洲精品一区二区三区四区高清| 亚洲先锋成人| 美玉足脚交一区二区三区图片| 亚洲精品激情| 欧美专区在线观看一区| 欧美日韩三级一区二区| 亚洲国产毛片完整版| 性久久久久久久| 欧美jjzz| 香港久久久电影| 国产精品久久久久影院色老大 | 日韩一级免费| 久久久97精品| 国产精品私拍pans大尺度在线 | 欧美sm视频| 久久在线视频在线| 亚洲久久一区| 亚洲二区在线视频| 午夜精品成人在线| 欧美99久久| 日韩午夜电影| 久久精品国产2020观看福利| 欧美日在线观看| 国内外成人免费激情在线视频| 日韩午夜电影在线观看| 久久综合久色欧美综合狠狠| 一区二区三区成人| 欧美a级理论片| 亚洲欧美日韩一区二区三区在线| 久久手机精品视频| 国产日韩精品在线播放| 亚洲男人的天堂在线aⅴ视频| 欧美成人性网| 久久精品一二三区| 欧美成人四级电影| 欧美一区二区三区在线视频 | 欧美日韩免费观看中文| 在线成人欧美| 蜜桃av噜噜一区| 亚洲另类春色国产| 欧美一级二区| 欧美视频在线看| 亚洲午夜女主播在线直播| 欧美高清一区二区| 中文国产成人精品| 国产精品久久久久久妇女6080| 日韩一级成人av| 最新日韩av| 欧美久久视频| 亚洲一区二区三区视频播放| 亚洲美女av电影| 国产精品国产a| 久久动漫亚洲| 久久国产精品久久久| 黄色精品一区| 免费一级欧美片在线播放| 久久免费视频在线观看| 亚洲国产一区二区三区a毛片| 欧美黄在线观看| 欧美在线三区| 亚洲高清网站| 日韩亚洲在线| 国产日韩欧美精品综合| 麻豆成人在线观看| 欧美精品入口| 亚洲欧美日韩另类精品一区二区三区 | 亚洲欧美日韩区| 欧美日韩网址| 久久精品视频免费观看| 麻豆精品精华液| 亚洲女同同性videoxma| 欧美在线www| 亚洲精品乱码久久久久久蜜桃91| 亚洲黄色天堂| 国产精品一区在线观看| 美女视频黄 久久| 国产精品久久国产愉拍 | 香蕉国产精品偷在线观看不卡| 亚洲欧美国产一区二区三区| 黄色在线一区| 一本久道久久综合狠狠爱| 国产精品理论片| 欧美91大片| 日韩性生活视频| 国产美女精品视频| 亚洲大片一区二区三区| 国产精品毛片| 欧美第十八页| 欧美高清视频一区二区| 欧美成人情趣视频| 国产亚洲人成a一在线v站| 日韩视频一区二区| 亚洲国产精品一区在线观看不卡| 亚洲专区国产精品| 亚洲一区二区在线播放| 欧美激情在线| 91久久精品久久国产性色也91| 欧美午夜精品久久久久久孕妇| 久久中文精品| 国产亚洲欧美激情| 亚洲午夜未删减在线观看| 亚洲精品美女久久久久| 久久久久久久久伊人| 久久精品视频在线看| 亚洲第一精品在线| 国产裸体写真av一区二区| 老司机精品福利视频| 欧美激情精品久久久久久大尺度 | 亚洲国产精品va在线看黑人动漫 | 亚洲欧美综合网| 亚洲自拍啪啪| 欧美日韩一区自拍| 亚洲精品久久久久| 日韩一级在线观看| 欧美精品日韩| 久久最新视频| aa级大片欧美| 国产精品国产三级国产专播品爱网| 91久久中文| 亚洲深夜av| 国产精品欧美久久| 先锋影音网一区二区| 久久人体大胆视频| 国产日韩欧美在线| 欧美成人中文| 艳女tv在线观看国产一区| 亚洲一区二区三区在线看| 国产精品亚洲网站| 欧美中文在线字幕| 免费观看日韩av| 亚洲精品国产精品国产自| 免费中文日韩| 亚洲欧美在线看| 欧美日韩精品一区二区| 99热免费精品| 欧美日韩综合一区| 亚洲一二三区视频在线观看| 久久国产精品高清| 亚洲国产成人91精品| 欧美精品少妇一区二区三区| 99视频精品在线| 欧美中在线观看| 尤物网精品视频| 欧美日本在线一区| 亚洲欧美影院| 欧美福利电影在线观看| 在线亚洲美日韩| 国产欧美日韩91| 欧美成人r级一区二区三区| 99视频精品免费观看| 欧美a级片一区| 欧美亚洲综合在线| 国产免费观看久久黄| 久久人人爽国产| 亚洲视频免费在线| 午夜精品久久久久久久久久久久久| 亚洲三级免费观看| 国产在线视频不卡二| 欧美色中文字幕| 久久男女视频|