<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                [TOC] ### 1\. 垃圾回收 **垃圾回收、三色標記原理** 垃圾回收就是對程序中不再使用的內存資源進行自動回收的操作。 #### 1.1 常見的垃圾回收算法: * 引用計數:每個對象維護一個引用計數,當被引用對象被創建或被賦值給其他對象時引用計數自動加 +1;如果這個對象被銷毀,則計數 -1 ,當計數為 0 時,回收該對象。 * 優點:對象可以很快被回收,不會出現內存耗盡或到達閥值才回收。 * 缺點:不能很好的處理循環引用 * 標記-清除:從根變量開始遍歷所有引用的對象,引用的對象標記“被引用”,沒有被標記的則進行回收。 * 優點:解決了引用計數的缺點。 * 缺點:需要 STW(stop the world),暫時停止程序運行。 * 分代收集:按照對象生命周期長短劃分不同的代空間,生命周期長的放入老年代,短的放入新生代,不同代有不同的回收算法和回收頻率。 * 優點:回收性能好 * 缺點:算法復雜 #### 1.2 三色標記法 * 初始狀態下所有對象都是白色的。 * 從根節點開始遍歷所有對象,把遍歷到的對象變成灰色對象 * 遍歷灰色對象,將灰色對象引用的對象也變成灰色對象,然后將遍歷過的灰色對象變成黑色對象。 * 循環步驟3,直到灰色對象全部變黑色。 * 通過寫屏障(write-barrier)檢測對象有變化,重復以上操作 * 收集所有白色對象(垃圾)。 #### 1.3 STW(Stop The World) * 為了避免在 GC 的過程中,對象之間的引用關系發生新的變更,使得GC的結果發生錯誤(如GC過程中新增了一個引用,但是由于未掃描到該引用導致將被引用的對象清除了),停止所有正在運行的協程。 * STW對性能有一些影響,Golang目前已經可以做到1ms以下的STW。 #### 1.4 寫屏障(Write Barrier) * 為了避免GC的過程中新修改的引用關系到GC的結果發生錯誤,我們需要進行STW。但是STW會影響程序的性能,所以我們要通過寫屏障技術盡可能地縮短STW的時間。 造成引用對象丟失的條件: 一個黑色的節點A新增了指向白色節點C的引用,并且白色節點C沒有除了A之外的其他灰色節點的引用,或者存在但是在GC過程中被刪除了。以上兩個條件需要同時滿足:滿足條件1時說明節點A已掃描完畢,A指向C的引用無法再被掃描到;滿足條件2時說明白色節點C無其他灰色節點的引用了,即掃描結束后會被忽略 。 寫屏障破壞兩個條件其一即可 * 破壞條件1:Dijistra寫屏障 滿足強三色不變性:黑色節點不允許引用白色節點 當黑色節點新增了白色節點的引用時,將對應的白色節點改為灰色 * 破壞條件2:Yuasa寫屏障 滿足弱三色不變性:黑色節點允許引用白色節點,但是該白色節點有其他灰色節點間接的引用(確保不會被遺漏) 當白色節點被刪除了一個引用時,悲觀地認為它一定會被一個黑色節點新增引用,所以將它置為灰色 ### 2\. GPM 調度 和 CSP 模型 **協程的深入剖析** #### 2.1 CSP 模型? CSP 模型是“以通信的方式來共享內存”,不同于傳統的多線程通過共享內存來通信。用于描述兩個獨立的并發實體通過共享的通訊 channel (管道)進行通信的并發模型。 #### 2.2 GPM 分別是什么、分別有多少數量? * G(Goroutine):即Go協程,每個go關鍵字都會創建一個協程。 * M(Machine):工作線程,在Go中稱為Machine,數量對應真實的CPU數(真正干活的對象)。 * P(Processor):處理器(Go中定義的一個摡念,非CPU),包含運行Go代碼的必要資源,用來調度 G 和 M 之間的關聯關系,其數量可通過 GOMAXPROCS() 來設置,默認為核心數。 M必須擁有P才可以執行G中的代碼,P含有一個包含多個G的隊列,P可以調度G交由M執行。 #### 2.3 Goroutine調度策略 * 隊列輪轉:P 會周期性的將G調度到M中執行,執行一段時間后,保存上下文,將G放到隊列尾部,然后從隊列中再取出一個G進行調度。除此之外,P還會周期性的查看全局隊列是否有G等待調度到M中執行。 * 系統調用:當G0即將進入系統調用時,M0將釋放P,進而某個空閑的M1獲取P,繼續執行P隊列中剩下的G。M1的來源有可能是M的緩存池,也可能是新建的。 * 當G0系統調用結束后,如果有空閑的P,則獲取一個P,繼續執行G0。如果沒有,則將G0放入全局隊列,等待被其他的P調度。然后M0將進入緩存池睡眠。 ![](https://www.topgoer.cn/uploads/blog/202111/attach_16b401ee1e07d54d.jpg "null") ### 3\. CHAN 原理 **chan實現原理** #### 3.1 結構體 ~~~ type hchan struct { qcount uint // 隊列中的總元素個數 dataqsiz uint // 環形隊列大小,即可存放元素的個數 buf unsafe.Pointer // 環形隊列指針 elemsize uint16 //每個元素的大小 closed uint32 //標識關閉狀態 elemtype *_type // 元素類型 sendx uint // 發送索引,元素寫入時存放到隊列中的位置 recvx uint // 接收索引,元素從隊列的該位置讀出 recvq waitq // 等待讀消息的goroutine隊列 sendq waitq // 等待寫消息的goroutine隊列 lock mutex //互斥鎖,chan不允許并發讀寫 } ~~~ #### 3.2 讀寫流程 **向 channel 寫數據:** 若等待接收隊列 recvq 不為空,則緩沖區中無數據或無緩沖區,將直接從 recvq 取出 G ,并把數據寫入,最后把該 G 喚醒,結束發送過程。 若緩沖區中有空余位置,則將數據寫入緩沖區,結束發送過程。 若緩沖區中沒有空余位置,則將發送數據寫入 G,將當前 G 加入 sendq ,進入睡眠,等待被讀 goroutine 喚醒。 **從 channel 讀數據** 若等待發送隊列 sendq 不為空,且沒有緩沖區,直接從 sendq 中取出 G ,把 G 中數據讀出,最后把 G 喚醒,結束讀取過程。 如果等待發送隊列 sendq 不為空,說明緩沖區已滿,從緩沖區中首部讀出數據,把 G 中數據寫入緩沖區尾部,把 G 喚醒,結束讀取過程。 如果緩沖區中有數據,則從緩沖區取出數據,結束讀取過程。 將當前 goroutine 加入 recvq ,進入睡眠,等待被寫 goroutine 喚醒。 **關閉 channel** 1.關閉 channel 時會將 recvq 中的 G 全部喚醒,本該寫入 G 的數據位置為 nil。將 sendq 中的 G 全部喚醒,但是這些 G 會 panic。 panic 出現的場景還有: * 關閉值為 nil 的 channel * 關閉已經關閉的 channel * 向已經關閉的 channel 中寫數據 #### 3.2 無緩沖 Chan 的發送和接收是否同步? ~~~ // 無緩沖的channel由于沒有緩沖發送和接收需要同步 ch := make(chan int) //有緩沖channel不要求發送和接收操作同步 ch := make(chan int, 2) ~~~ channel 無緩沖時,發送阻塞直到數據被接收,接收阻塞直到讀到數據;channel有緩沖時,當緩沖滿時發送阻塞,當緩沖空時接收阻塞。 ### 4\. context 結構原理 #### 4.1 用途 Context(上下文)是Golang應用開發常用的并發控制技術 ,它可以控制一組呈樹狀結構的goroutine,每個goroutine擁有相同的上下文。Context 是并發安全的,主要是用于控制多個協程之間的協作、取消操作。 ![](https://www.topgoer.cn/uploads/blog/202111/attach_16b40224aa38da32.jpg "null") #### 4.2 數據結構 Context 只定義了接口,凡是實現該接口的類都可稱為是一種 context。 并發控制神器之Context ~~~ type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} } ~~~ * 「Deadline」 方法:可以獲取設置的截止時間,返回值 deadline 是截止時間,到了這個時間,Context 會自動發起取消請求,返回值 ok 表示是否設置了截止時間。 * 「Done」 方法:返回一個只讀的 channel ,類型為 struct{}。如果這個 chan 可以讀取,說明已經發出了取消信號,可以做清理操作,然后退出協程,釋放資源。 * 「Err」 方法:返回Context 被取消的原因。 * 「Value」 方法:獲取 Context 上綁定的值,是一個鍵值對,通過 key 來獲取對應的值。 ### 5\. 競態、內存逃逸 **并發控制,同步原語 sync 包** #### 5.1 競態 資源競爭,就是在程序中,同一塊內存同時被多個 goroutine 訪問。我們使用 go build、go run、go test 命令時,添加 -race 標識可以檢查代碼中是否存在資源競爭。 解決這個問題,我們可以給資源進行加鎖,讓其在同一時刻只能被一個協程來操作。 * sync.Mutex * sync.RWMutex #### 5.2 逃逸分析 **面試官問我go逃逸場景有哪些,我???** 「逃逸分析」就是程序運行時內存的分配位置(棧或堆),是由編譯器來確定的。堆適合不可預知大小的內存分配。但是為此付出的代價是分配速度較慢,而且會形成內存碎片。 逃逸場景: * 指針逃逸 * 棧空間不足逃逸 * 動態類型逃逸 * 閉包引用對象逃逸 ### 快問快答 ### 6\. go 中除了加 Mutex 鎖以外還有哪些方式安全讀寫共享變量? Go 中 Goroutine 可以通過 Channel 進行安全讀寫共享變量。 ### 7\. golang中new和make的區別? 用new還是make?到底該如何選擇? * make 僅用來分配及初始化類型為 slice、map、chan 的數據。 * new 可分配任意類型的數據,根據傳入的類型申請一塊內存,返回指向這塊內存的指針,即類型 \*Type。 * make 返回引用,即 Type,new 分配的空間被清零, make 分配空間后,會進行初始。 ### 8\. Go中對nil的Slice和空Slice的處理是一致的嗎? 首先Go的JSON 標準庫對 nil slice 和 空 slice 的處理是不一致。 * slice := make(\[\]int,0):slice不為nil,但是slice沒有值,slice的底層的空間是空的。 * slice := \[\]int{} :slice的值是nil,可用于需要返回slice的函數,當函數出現異常的時候,保證函數依然會有nil的返回值。 ### 9\. 協程和線程和進程的區別? 并發掌握,goroutine和channel聲明與使用! * 進程: 進程是具有一定獨立功能的程序,進程是系統資源分配和調度的最小單位。每個進程都有自己的獨立內存空間,不同進程通過進程間通信來通信。由于進程比較重量,占據獨立的內存,所以上下文進程間的切換開銷(棧、寄存器、虛擬內存、文件句柄等)比較大,但相對比較穩定安全。 * 線程: 線程是進程的一個實體,線程是內核態,而且是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。線程間通信主要通過共享內存,上下文切換很快,資源開銷較少,但相比進程不夠穩定容易丟失數據。 \*\*\*\*協程: 協程是一種用戶態的輕量級線程,協程的調度完全是由用戶來控制的。協程擁有自己的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧,直接操作棧則基本沒有內核切換的開銷,可以不加鎖的訪問全局變量,所以上下文的切換非常快。 ### 10\. Golang的內存模型中為什么小對象多了會造成GC壓力? 通常小對象過多會導致GC三色法消耗過多的GPU。優化思路是,減少對象分配。 ### 11\. channel 為什么它可以做到線程安全? Channel 可以理解是一個先進先出的隊列,通過管道進行通信,發送一個數據到Channel和從Channel接收一個數據都是原子性的。不要通過共享內存來通信,而是通過通信來共享內存,前者就是傳統的加鎖,后者就是Channel。設計Channel的主要目的就是在多任務間傳遞數據的,本身就是安全的。 ### 12\. GC 的觸發條件? 主動觸發(手動觸發),通過調用 runtime.GC 來觸發GC,此調用阻塞式地等待當前GC運行完畢。 被動觸發,分為兩種方式: * 使用步調(Pacing)算法,其核心思想是控制內存增長的比例,每次內存分配時檢查當前內存分配量是否已達到閾值(環境變量GOGC):默認100%,即當內存擴大一倍時啟用GC。 * 使用系統監控,當超過兩分鐘沒有產生任何GC時,強制觸發 GC。 ### 13\. 怎么查看Goroutine的數量?怎么限制Goroutine的數量? * 在Golang中,GOMAXPROCS中控制的是未被阻塞的所有Goroutine,可以被 Multiplex 到多少個線程上運行,通過GOMAXPROCS可以查看Goroutine的數量。 * 使用通道。每次執行的go之前向通道寫入值,直到通道滿的時候就阻塞了。 ### 14\. Channel是同步的還是異步的? Channel是異步進行的, channel存在3種狀態: * nil,未初始化的狀態,只進行了聲明,或者手動賦值為nil * active,正常的channel,可讀或者可寫 * closed,已關閉,千萬不要誤認為關閉channel后,channel的值是nil | 操作 | 一個零值nil通道 | 一個非零值但已關閉的通道 | 一個非零值且尚未關閉的通道 | | --- | --- | --- | --- | | 關閉 | 產生恐慌 | 產生恐慌 | 成功關閉 | | 發送數據 | 永久阻塞 | 產生恐慌 | 阻塞或者成功發送 | | 接收數據 | 永久阻塞 | 永不阻塞 | 阻塞或者成功接收 | ### 15\. Goroutine和線程的區別? * 一個線程可以有多個協程 * 線程、進程都是同步機制,而協程是異步 * 協程可以保留上一次調用時的狀態,當過程重入時,相當于進入了上一次的調用狀態 * 協程是需要線程來承載運行的,所以協程并不能取代線程,「線程是被分割的CPU資源,協程是組織好的代碼流程」 ### 16\. Go的Struct能不能比較? * 相同struct類型的可以比較 * 不同struct類型的不可以比較,編譯都不過,類型不匹配 ### 17\. Go主協程如何等其余協程完再操作? 使用sync.WaitGroup。WaitGroup,就是用來等待一組操作完成的。WaitGroup內部實現了一個計數器,用來記錄未完成的操作個數。Add()用來添加計數;Done()用來在操作結束時調用,使計數減一;Wait()用來等待所有的操作結束,即計數變為0,該函數會在計數不為0時等待,在計數為0時立即返回。 ### 18\. Go的Slice如何擴容? **slice 實現原理** 在使用 append 向 slice 追加元素時,若 slice 空間不足則會發生擴容,擴容會重新分配一塊更大的內存,將原 slice 拷貝到新 slice ,然后返回新 slice。擴容后再將數據追加進去。 擴容操作只對容量,擴容后的 slice 長度不變,容量變化規則如下: * 若 slice 容量小于1024個元素,那么擴容的時候slice的cap就翻番,乘以2;一旦元素個數超過1024個元素,增長因子就變成1.25,即每次增加原來容量的四分之一。 * 若 slice 容量夠用,則將新元素追加進去,slice.len++,返回原 slice * 若 slice 容量不夠用,將 slice 先擴容,擴容得到新 slice,將新元素追加進新 slice,slice.len++,返回新 slice。 ### 19\. Go中的map如何實現順序讀取? Go中map如果要實現順序讀取的話,可以先把map中的key,通過sort包排序。 ### 20\. Go值接收者和指針接收者的區別? **究竟在什么情況下才使用指針?** **參數傳遞中,值、引用及指針之間的區別!** 方法的接收者: * 值類型,既可以調用值接收者的方法,也可以調用指針接收者的方法; * 指針類型,既可以調用指針接收者的方法,也可以調用值接收者的方法。 但是接口的實現,值類型接收者和指針類型接收者不一樣: * 以值類型接收者實現接口,類型本身和該類型的指針類型,都實現了該接口; * 以指針類型接收者實現接口,只有對應的指針類型才被認為實現了接口。 通常我們使用指針作為方法的接收者的理由: * 使用指針方法能夠修改接收者指向的值。 * 可以避免在每次調用方法時復制該值,在值的類型為大型結構體時,這樣做會更加高效。 ### 21\. 在Go函數中為什么會發生內存泄露? Goroutine 需要維護執行用戶代碼的上下文信息,在運行過程中需要消耗一定的內存來保存這類信息,如果一個程序持續不斷地產生新的 goroutine,且不結束已經創建的 goroutine 并復用這部分內存,就會造成內存泄漏的現象。 ### 22\. Goroutine發生了泄漏如何檢測? 可以通過Go自帶的工具pprof或者使用Gops去檢測診斷當前在系統上運行的Go進程的占用的資源。 ### 23\. Go中兩個Nil可能不相等嗎? Go中兩個Nil可能不相等。 接口(interface) 是對非接口值(例如指針,struct等)的封裝,內部實現包含 2 個字段,類型 T 和 值 V。一個接口等于 nil,當且僅當 T 和 V 處于 unset 狀態(T=nil,V is unset)。 兩個接口值比較時,會先比較 T,再比較 V。接口值與非接口值比較時,會先將非接口值嘗試轉換為接口值,再比較。 ~~~ func main() { var p *int = nil var i interface{} = p fmt.Println(i == p) // true fmt.Println(p == nil) // true fmt.Println(i == nil) // false } ~~~ * 例子中,將一個nil非接口值p賦值給接口i,此時,i的內部字段為(T=\*int, V=nil),i與p作比較時,將 p 轉換為接口后再比較,因此 i == p,p 與 nil 比較,直接比較值,所以 p == nil。 * 但是當 i 與nil比較時,會將nil轉換為接口(T=nil, V=nil),與i(T=\*int, V=nil)不相等,因此 i != nil。因此 V 為 nil ,但 T 不為 nil 的接口不等于 nil。 ### 24\. Go語言函數傳參是值類型還是引用類型? * 在Go語言中只存在值傳遞,要么是值的副本,要么是指針的副本。無論是值類型的變量還是引用類型的變量亦或是指針類型的變量作為參數傳遞都會發生值拷貝,開辟新的內存空間。 * 另外值傳遞、引用傳遞和值類型、引用類型是兩個不同的概念,不要混淆了。引用類型作為變量傳遞可以影響到函數外部是因為發生值拷貝后新舊變量指向了相同的內存地址。 ### 25\. Go語言中的內存對齊了解嗎? CPU 訪問內存時,并不是逐個字節訪問,而是以字長(word size)為單位訪問。比如 32 位的 CPU ,字長為 4 字節,那么 CPU 訪問內存的單位也是 4 字節。 CPU 始終以字長訪問內存,如果不進行內存對齊,很可能增加 CPU 訪問內存的次數,例如: ![](https://www.topgoer.cn/uploads/blog/202111/attach_16b441eb3214e65c.jpg "null") 變量 a、b 各占據 3 字節的空間,內存對齊后,a、b 占據 4 字節空間,CPU 讀取 b 變量的值只需要進行一次內存訪問。如果不進行內存對齊,CPU 讀取 b 變量的值需要進行 2 次內存訪問。第一次訪問得到 b 變量的第 1 個字節,第二次訪問得到 b 變量的后兩個字節。 也可以看到,內存對齊對實現變量的原子性操作也是有好處的,每次內存訪問是原子的,如果變量的大小不超過字長,那么內存對齊后,對該變量的訪問就是原子的,這個特性在并發場景下至關重要。 簡言之:合理的內存對齊可以提高內存讀寫的性能,并且便于實現變量操作的原子性。 ### 26\. 兩個 interface 可以比較嗎? * 判斷類型是否一樣 reflect.TypeOf(a).Kind() == reflect.TypeOf(b).Kind() * 判斷兩個interface{}是否相等 reflect.DeepEqual(a, b interface{}) * 將一個interface{}賦值給另一個interface{} reflect.ValueOf(a).Elem().Set(reflect.ValueOf(b)) ### 27\. go 打印時 %v %+v %#v 的區別? * %v 只輸出所有的值; * %+v 先輸出字段名字,再輸出該字段的值; * %#v 先輸出結構體名字值,再輸出結構體(字段名字+字段的值); ~~~ package main import "fmt" type student struct { id int32 name string } func main() { a := &student{id: 1, name: "微客鳥窩"} fmt.Printf("a=%v \n", a) // a=&{1 微客鳥窩} fmt.Printf("a=%+v \n", a) // a=&{id:1 name:微客鳥窩} fmt.Printf("a=%#v \n", a) // a=&main.student{id:1, name:"微客鳥窩"} } ~~~ ### 28\. 什么是 rune 類型? Go語言的字符有以下兩種: * uint8 類型,或者叫 byte 型,代表了 ASCII 碼的一個字符。 * rune 類型,代表一個 UTF-8 字符,當需要處理中文、日文或者其他復合字符時,則需要用到 rune 類型。rune 類型等價于 int32 類型。 ~~~ package main import "fmt" func main() { var str = "hello 你好" //思考下 len(str) 的長度是多少? //golang中string底層是通過byte數組實現的,直接求len 實際是在按字節長度計算 //所以一個漢字占3個字節算了3個長度 fmt.Println("len(str):", len(str)) // len(str): 12 //通過rune類型處理unicode字符 fmt.Println("rune:", len([]rune(str))) //rune: 8 } ~~~ ### 29\. 空 struct{} 占用空間么? 可以使用 unsafe.Sizeof 計算出一個數據類型實例需要占用的字節數: ~~~ package main import ( "fmt" "unsafe" ) func main() { fmt.Println(unsafe.Sizeof(struct{}{})) //0 } ~~~ 空結構體 struct{} 實例不占據任何的內存空間。 ### 30\. 空 struct{} 的用途? 因為空結構體不占據內存空間,因此被廣泛作為各種場景下的占位符使用。 1. 將 map 作為集合(Set)使用時,可以將值類型定義為空結構體,僅作為占位符使用即可。 ~~~ type Set map[string]struct{} func (s Set) Has(key string) bool { _, ok := s[key] return ok } func (s Set) Add(key string) { s[key] = struct{}{} } func (s Set) Delete(key string) { delete(s, key) } func main() { s := make(Set) s.Add("Tom") s.Add("Sam") fmt.Println(s.Has("Tom")) fmt.Println(s.Has("Jack")) } ~~~ 2. 不發送數據的信道(channel) 使用 channel 不需要發送任何的數據,只用來通知子協程(goroutine)執行任務,或只用來控制協程并發度。 ~~~ func worker(ch chan struct{}) { <-ch fmt.Println("do something") close(ch) } func main() { ch := make(chan struct{}) go worker(ch) ch <- struct{}{} } ~~~ 3. 結構體只包含方法,不包含任何的字段 ~~~ type Door struct{} func (d Door) Open() { fmt.Println("Open the door") } func (d Door) Close() { fmt.Println("Close the door") } ~~~
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看