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

大龍的博客

常用鏈接

統(tǒng)計

最新評論

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

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

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

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

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


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

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

考慮下面的代碼:

代碼:

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

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

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

大多數(shù)關(guān)于volatile的原理和用法的解釋就到此為止,并且建議你用volatile修飾在多個線程中使用的原生類型變量。然而,你可以用volatile做更多的事,因為它是神奇的C++類型系統(tǒng)的一部分。


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

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

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

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




如果你認(rèn)為volatile對于對象來說沒有什么作用的話,那你可要大吃一驚了。
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

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

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

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

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


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

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

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

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


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

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

代碼:

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




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

LockingPtr的定義很簡單,它只是實現(xiàn)了一個單純的smart pointer。它關(guā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&);
};




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

比如說你有兩個線程需要共享一個vector<char>對象:

代碼:


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




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

代碼:

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



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

這個方法的好處是,如果你犯了錯誤,編譯器會指出它:
代碼:

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提供了一個有規(guī)則的方法來對一個volatile變量進(jìn)行const_cast。
LockingPtr有非常好的表達(dá)力。如果你只需要調(diào)用一個函數(shù),你可以創(chuàng)建一個無名的臨時LockingPtr對象,然后直接使用它:

代碼:

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




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

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

代碼:

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




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

為了防止這樣的事發(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ì)量要差一些。為什么?因為對于Counter,編譯器不會在你錯誤地直接訪問ctr_(沒有對它加鎖)時產(chǎn)生警告。雖然ctr_是volatile的,但是編譯器還是可以編譯++ctr_,盡管產(chǎn)生的代碼絕對是不正確的。編譯器不再是你的盟友了,你只有自己留意競爭條件。
那么你該怎么做呢?很簡單,你可以用一個高層的結(jié)構(gòu)來包裝原生類型的數(shù)據(jù),然后對那個結(jié)構(gòu)使用volatile。這有點自相矛盾,直接用volatile修飾原生類型是一個不好的用法,盡管這是volatile最初期望的用法!


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

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

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

代碼:

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



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

代碼:

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





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

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

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

致謝
非常感謝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里去了。隨后引起了很長很激烈的討論,如果你對這個主題有興趣,你可以去看看這個討論,它的標(biāo)題是“volatile, was: memory visibility between threads.”。

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

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

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

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

volatile mt_vector<int> vec;

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

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

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

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


只有注冊用戶登錄后才能發(fā)表評論。
網(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>
            国内精品美女在线观看| 99精品热6080yy久久| 亚洲青涩在线| 一区二区三区在线观看视频| 国产精品美女久久久久aⅴ国产馆| 欧美高清视频在线| 欧美黄色aa电影| 欧美日韩亚洲网| 国产精品久久久一本精品| 欧美三级视频在线播放| 国产精品久久福利| 国产真实乱偷精品视频免| 激情文学一区| 日韩午夜剧场| 欧美一级大片在线免费观看| 久久久国产一区二区三区| 六月天综合网| 亚洲精品视频一区二区三区| 一二三四社区欧美黄| 亚洲一区精品在线| 亚洲黄色成人网| 亚洲精品一区二区网址| 亚洲一级高清| 久久综合九色| 欧美日韩中字| 在线高清一区| 亚洲综合色丁香婷婷六月图片| 欧美一区二区三区四区在线观看地址| 浪潮色综合久久天堂| 亚洲人屁股眼子交8| 午夜精品999| 欧美高清视频www夜色资源网| 国产精品初高中精品久久| 黄色成人在线观看| 这里只有精品电影| 亚洲激情视频在线观看| 亚洲欧美日韩一区二区三区在线| 亚洲欧美日韩一区二区| 欧美成人综合| 国产欧美va欧美va香蕉在| 亚洲精选视频免费看| 欧美伊人久久| 日韩一级精品| 男男成人高潮片免费网站| 国产精品亚发布| 一本色道久久88亚洲综合88| 美女精品网站| 欧美伊人影院| 国产精品欧美风情| 在线视频亚洲欧美| 亚洲国产小视频| 久久久综合视频| 国产午夜精品全部视频播放| 亚洲免费影视| 一本久久青青| 欧美激情亚洲精品| 亚洲肉体裸体xxxx137| 另类天堂av| 久久国产精品一区二区| 国产精品成人一区二区艾草| 日韩一区二区久久| 欧美成人精品高清在线播放| 亚洲一区www| 欧美日韩国内自拍| 欧美日韩一视频区二区| 99riav1国产精品视频| 久久久久九九九九| 99视频在线观看一区三区| 裸体一区二区| 好看的亚洲午夜视频在线| 亚洲欧美日韩一区二区| 亚洲精品久久久久久下一站 | 欧美一区二区三区婷婷月色 | 国产精品日本精品| 亚洲最新视频在线| 欧美电影在线观看完整版| 性欧美激情精品| 国产精品久久久久久久久久免费| 亚洲日本激情| 欧美激情视频一区二区三区免费| 久久成人免费网| 国产伪娘ts一区| 久久国产成人| 午夜国产欧美理论在线播放| 久久精品国产第一区二区三区最新章节 | 亚洲激情二区| 欧美1区免费| 国产精品福利久久久| aa国产精品| 亚洲激情在线观看| 欧美高清在线视频观看不卡| 亚洲福利视频网站| 欧美激情中文字幕在线| 久久久久青草大香线综合精品| 国内精品伊人久久久久av影院 | 久久视频国产精品免费视频在线| 亚洲精品一区二区三区蜜桃久| 欧美黑人在线观看| 亚洲天堂网在线观看| 国产精品99久久久久久久vr| 欧美亚韩一区| 先锋影音国产精品| 欧美一区在线看| 亚洲福利在线视频| 亚洲精品乱码久久久久久蜜桃麻豆 | 亚洲乱码久久| 国产精品视频一区二区高潮| 久久久精品国产一区二区三区 | 国产精品免费网站| 久久伊人亚洲| 欧美精品激情在线| 午夜在线一区二区| 一本色道久久88亚洲综合88 | 欧美日韩人人澡狠狠躁视频| 一本久道久久久| 午夜精品久久久久久久白皮肤| 黄色一区二区在线| 亚洲欧洲精品一区二区精品久久久| 欧美日本一道本| 久久国产日本精品| 欧美成人精品三级在线观看| 亚洲欧美另类在线| 久久免费午夜影院| 在线一区视频| 久久久久免费| 亚洲韩国一区二区三区| 亚洲国产精品999| 国产精品一卡| 亚洲理伦电影| 在线国产日韩| 亚洲欧美不卡| 久久人人97超碰人人澡爱香蕉| 久久经典综合| 欧美视频国产精品| 亚洲黄色尤物视频| 国产曰批免费观看久久久| av成人福利| 亚洲精品乱码久久久久| 欧美一区二区三区播放老司机| 精品白丝av| 国产精品99久久久久久www| 亚洲国产欧洲综合997久久| 午夜精品国产| 香蕉久久夜色精品| 欧美日一区二区在线观看 | 亚洲专区一区| 欧美日韩一区二区三| 欧美激情一二三区| 影音先锋另类| 欧美在线免费视屏| 欧美一区二区免费视频| 奶水喷射视频一区| 久久综合色综合88| 国产婷婷色综合av蜜臀av| av成人老司机| av成人激情| 欧美三级乱码| 99精品欧美一区二区蜜桃免费| 亚洲人www| 欧美黄色aaaa| 久久午夜视频| 99re成人精品视频| 欧美人妖另类| av72成人在线| 小处雏高清一区二区三区| 欧美视频一二三区| 亚洲午夜黄色| 午夜精品在线看| 国产精品成人aaaaa网站| 中文亚洲欧美| 性欧美暴力猛交69hd| 国产模特精品视频久久久久| 亚洲一区在线观看视频| 性欧美大战久久久久久久免费观看 | 久久av在线看| 在线观看亚洲视频| 久久午夜精品一区二区| 女人天堂亚洲aⅴ在线观看| 亚洲国产另类精品专区| 欧美国产乱视频| 一本色道久久综合| 欧美一区观看| 在线日韩中文字幕| 久久狠狠久久综合桃花| 免费欧美电影| 夜夜爽夜夜爽精品视频| 国产精品劲爆视频| 久久精品一区| 亚洲精品中文字幕有码专区| 亚洲综合久久久久| 国内外成人在线视频| 亚洲欧美久久久| 欧美一区二区三区精品| 国内揄拍国内精品少妇国语| 欧美大片免费观看在线观看网站推荐 | 欧美11—12娇小xxxx| 亚洲欧美日韩国产成人| 欧美成人精品在线视频| 中文精品在线| 国产一区二区日韩精品|