久久精品国产精品国产精品污,男人扒开添女人下部免费视频,一级国产69式性姿势免费视频,夜鲁夜鲁很鲁在线视频 视频,欧美丰满少妇一区二区三区,国产偷国产偷亚洲高清人乐享,中文 在线 日韩 亚洲 欧美,熟妇人妻无乱码中文字幕真矢织江,一区二区三区人妻制服国产

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Go 高性能编程技法

發布時間:2024/2/28 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Go 高性能编程技法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者:dablelv,騰訊 IEGggG 后臺開發工程師

代碼的穩健、可讀和高效是我們每一個 coder 的共同追求。本文將結合 Go 語言特性,為書寫效率更高的代碼,從常用數據結構、內存管理和并發,三個方面給出相關建議。話不多說,讓我們一起學習 Go 高性能編程的技法吧。

常用數據結構

1.反射雖好,切莫貪杯

標準庫 reflect 為 Go 語言提供了運行時動態獲取對象的類型和值以及動態創建對象的能力。反射可以幫助抽象和簡化代碼,提高開發效率。

Go 語言標準庫以及很多開源軟件中都使用了 Go 語言的反射能力,例如用于序列化和反序列化的 json、ORM 框架 gorm、xorm 等。

1.1 優先使用 strconv 而不是 fmt

基本數據類型與字符串之間的轉換,優先使用 strconv 而不是 fmt,因為前者性能更佳。

//?Bad for?i?:=?0;?i?<?b.N;?i++?{s?:=?fmt.Sprint(rand.Int()) }BenchmarkFmtSprint-4????143?ns/op????2?allocs/op//?Good for?i?:=?0;?i?<?b.N;?i++?{s?:=?strconv.Itoa(rand.Int()) }BenchmarkStrconv-4????64.2?ns/op????1?allocs/op

為什么性能上會有兩倍多的差距,因為 fmt 實現上利用反射來達到范型的效果,在運行時進行類型的動態判斷,所以帶來了一定的性能損耗。

1.2 少量的重復不比反射差

有時,我們需要一些工具函數。比如從 uint64 切片過濾掉指定的元素。

利用反射,我們可以實現一個類型泛化支持擴展的切片過濾函數。

// DeleteSliceElms 從切片中過濾指定元素。注意:不修改原切片。 func?DeleteSliceElms(i?interface{},?elms?...interface{})?interface{}?{//?構建 map set。m?:=?make(map[interface{}]struct{},?len(elms))for?_,?v?:=?range?elms?{m[v]?=?struct{}{}}//?創建新切片,過濾掉指定元素。v?:=?reflect.ValueOf(i)t?:=?reflect.MakeSlice(reflect.TypeOf(i),?0,?v.Len())for?i?:=?0;?i?<?v.Len();?i++?{if?_,?ok?:=?m[v.Index(i).Interface()];?!ok?{t?=?reflect.Append(t,?v.Index(i))}}return?t.Interface() }

很多時候,我們可能只需要操作一個類型的切片,利用反射實現的類型泛化擴展的能力壓根沒用上。退一步說,如果我們真地需要對 uint64 以外類型的切片進行過濾,拷貝一次代碼又何妨呢?可以肯定的是,絕大部份場景,根本不會對所有類型的切片進行過濾,那么反射帶來好處我們并沒有充分享受,但卻要為其帶來的性能成本買單。

// DeleteU64liceElms 從?[]uint64 過濾指定元素。注意:不修改原切片。 func?DeleteU64liceElms(i?[]uint64,?elms?...uint64)?[]uint64?{//?構建 map set。m?:=?make(map[uint64]struct{},?len(elms))for?_,?v?:=?range?elms?{m[v]?=?struct{}{}}//?創建新切片,過濾掉指定元素。t?:=?make([]uint64,?0,?len(i))for?_,?v?:=?range?i?{if?_,?ok?:=?m[v];?!ok?{t?=?append(t,?v)}}return?t }

下面看一下二者的性能對比。

func?BenchmarkDeleteSliceElms(b?*testing.B)?{slice?:=?[]uint64{1,?2,?3,?4,?5,?6,?7,?8,?9}elms?:=?[]interface{}{uint64(1),?uint64(3),?uint64(5),?uint64(7),?uint64(9)}for?i?:=?0;?i?<?b.N;?i++?{_?=?DeleteSliceElms(slice,?elms...)} }func?BenchmarkDeleteU64liceElms(b?*testing.B)?{slice?:=?[]uint64{1,?2,?3,?4,?5,?6,?7,?8,?9}elms?:=?[]uint64{1,?3,?5,?7,?9}for?i?:=?0;?i?<?b.N;?i++?{_?=?DeleteU64liceElms(slice,?elms...)} }

運行上面的基準測試。

go?test?-bench=.?-benchmem?main/reflect? goos:?darwin goarch:?amd64 pkg:?main/reflect cpu:?Intel(R)?Core(TM)?i7-9750H?CPU?@?2.60GHz BenchmarkDeleteSliceElms-12??????????????1226868???????????????978.2?ns/op???????????296?B/op?????????16?allocs/op BenchmarkDeleteU64liceElms-12????????????8249469???????????????145.3?ns/op????????????80?B/op??????????1?allocs/op PASS ok??????main/reflect????3.809s

可以看到,反射涉及了額外的類型判斷和大量的內存分配,導致其對性能的影響非常明顯。隨著切片元素的遞增,每一次判斷元素是否在 map 中,因為 map 的 key 是不確定的類型,會發生變量逃逸,觸發堆內存的分配。所以,可預見的是當元素數量增加時,性能差異會越來大。

當使用反射時,請問一下自己,我真地需要它嗎?

1.3 慎用 binary.Read 和 binary.Write

binary.Read 和 binary.Write 使用反射并且很慢。如果有需要用到這兩個函數的地方,我們應該手動實現這兩個函數的相關功能,而不是直接去使用它們。

encoding/binary 包實現了數字和字節序列之間的簡單轉換以及 varints 的編碼和解碼。varints 是一種使用可變字節表示整數的方法。其中數值本身越小,其所占用的字節數越少。Protocol Buffers 對整數采用的便是這種編碼方式。

其中數字與字節序列的轉換可以用如下三個函數:

// Read 從結構化二進制數據 r 讀取到 data。data 必須是指向固定大小值的指針或固定大小值的切片。 func?Read(r?io.Reader,?order?ByteOrder,?data?interface{})?error // Write 將 data 的二進制表示形式寫入 w。data 必須是固定大小的值或固定大小值的切片,或指向此類數據的指針。 func?Write(w?io.Writer,?order?ByteOrder,?data?interface{})?error // Size 返回 Wirte 函數將 v 寫入到 w 中的字節數。 func?Size(v?interface{})?int

下面以我們熟知的 C 標準庫函數 ntohl() 函數為例,看看 Go 利用 binary 包如何實現。

// Ntohl 將網絡字節序的 uint32 轉為主機字節序。 func?Ntohl(bys?[]byte)?uint32?{r?:=?bytes.NewReader(bys)err?=?binary.Read(buf,?binary.BigEndian,?&num) }//?如將?IP?127.0.0.1?網絡字節序解析到?uint32 fmt.Println(Ntohl([]byte{0x7f,?0,?0,?0x1}))?//?2130706433?<nil>

如果我們針對 uint32 類型手動實現一個 ntohl() 呢?

func?NtohlNotUseBinary(bys?[]byte)?uint32?{return?uint32(bys[3])?|?uint32(bys[2])<<8?|?uint32(bys[1])<<16?|?uint32(bys[0])<<24 }//?如將?IP?127.0.0.1?網絡字節序解析到?uint32 fmt.Println(NtohlNotUseBinary([]byte{0x7f,?0,?0,?0x1}))?//?2130706433

該函數也是參考了 encoding/binary 包針對大端字節序將字節序列轉為 uint32 類型時的實現。

下面看下剝去反射前后二者的性能差異。

func?BenchmarkNtohl(b?*testing.B)?{for?i?:=?0;?i?<?b.N;?i++?{_,?_?=?Ntohl([]byte{0x7f,?0,?0,?0x1})} }func?BenchmarkNtohlNotUseBinary(b?*testing.B)?{for?i?:=?0;?i?<?b.N;?i++?{_?=?NtohlNotUseBinary([]byte{0x7f,?0,?0,?0x1})} }

運行上面的基準測試,結果如下:

go?test?-bench=BenchmarkNtohl.*?-benchmem?main/reflect goos:?darwin goarch:?amd64 pkg:?main/reflect cpu:?Intel(R)?Core(TM)?i7-9750H?CPU?@?2.60GHz BenchmarkNtohl-12???????????????????????13026195????????????????81.96?ns/op???????????60?B/op??????????4?allocs/op BenchmarkNtohlNotUseBinary-12???????????1000000000???????????????0.2511?ns/op??????????0?B/op??????????0?allocs/op PASS ok??????main/reflect????1.841s

可見使用反射實現的 encoding/binary 包的性能相較于針對具體類型實現的版本,性能差異非常大。

2.避免重復的字符串到字節切片的轉換

不要反復從固定字符串創建字節 slice,因為重復的切片初始化會帶來性能損耗。相反,請執行一次轉換并捕獲結果。

//?Bad for?i?:=?0;?i?<?b.N;?i++?{w.Write([]byte("Hello?world")) }BenchmarkBad-4???50000000???22.2?ns/op//?Good data?:=?[]byte("Hello?world") for?i?:=?0;?i?<?b.N;?i++?{w.Write(data) }BenchmarkGood-4??500000000???3.25?ns/op

3.指定容器容量

盡可能指定容器容量,以便為容器預先分配內存。這將在后續添加元素時減少通過復制來調整容器大小。

3.1 指定 map 容量提示

在盡可能的情況下,在使用 make() 初始化的時候提供容量信息。

make(map[T1]T2,?hint)

向 make() 提供容量提示會在初始化時嘗試調整 map 的大小,這將減少在將元素添加到 map 時為 map 重新分配內存。

注意,與 slice 不同。map capacity 提示并不保證完全的搶占式分配,而是用于估計所需的 hashmap bucket 的數量。因此,在將元素添加到 map 時,甚至在指定 map 容量時,仍可能發生分配。

//?Bad m?:=?make(map[string]os.FileInfo)files,?_?:=?ioutil.ReadDir("./files") for?_,?f?:=?range?files?{m[f.Name()]?=?f } // m 是在沒有大小提示的情況下創建的;?在運行時可能會有更多分配。//?Good files,?_?:=?ioutil.ReadDir("./files")m?:=?make(map[string]os.FileInfo,?len(files)) for?_,?f?:=?range?files?{m[f.Name()]?=?f } // m 是有大小提示創建的;在運行時可能會有更少的分配。
3.2 指定切片容量

在盡可能的情況下,在使用 make() 初始化切片時提供容量信息,特別是在追加切片時。

make([]T,?length,?capacity)

與 map 不同,slice capacity 不是一個提示:編譯器將為提供給 make() 的 slice 的容量分配足夠的內存,這意味著后續的 append() 操作將導致零分配(直到 slice 的長度與容量匹配,在此之后,任何 append 都可能調整大小以容納其他元素)。

const?size?=?1000000//?Bad for?n?:=?0;?n?<?b.N;?n++?{data?:=?make([]int,?0)for?k?:=?0;?k?<?size;?k++?{data?=?append(data,?k)} }BenchmarkBad-4????219????5202179?ns/op//?Good for?n?:=?0;?n?<?b.N;?n++?{data?:=?make([]int,?0,?size)for?k?:=?0;?k?<?size;?k++?{data?=?append(data,?k)} }BenchmarkGood-4???706????1528934?ns/op

執行基準測試:

go?test?-bench=^BenchmarkJoinStr?-benchmem? BenchmarkJoinStrWithOperator-8????66930670????17.81?ns/op????0?B/op????0?allocs/op BenchmarkJoinStrWithSprintf-8??????7032921????166.0?ns/op????64?B/op???4?allocs/op

4.字符串拼接方式的選擇

4.1 行內拼接字符串推薦使用運算符+

行內拼接字符串為了書寫方便快捷,最常用的兩個方法是:

  • 運算符+

  • fmt.Sprintf()

行內字符串的拼接,主要追求的是代碼的簡潔可讀。fmt.Sprintf() 能夠接收不同類型的入參,通過格式化輸出完成字符串的拼接,使用非常方便。但因其底層實現使用了反射,性能上會有所損耗。

運算符 + 只能簡單地完成字符串之間的拼接,非字符串類型的變量需要單獨做類型轉換。行內拼接字符串不會產生內存分配,也不涉及類型地動態轉換,所以性能上優于fmt.Sprintf()。

從性能出發,兼顧易用可讀,如果待拼接的變量不涉及類型轉換且數量較少(<=5),行內拼接字符串推薦使用運算符 +,反之使用 fmt.Sprintf()。

下面看下二者的性能對比。

//?Good func?BenchmarkJoinStrWithOperator(b?*testing.B)?{s1,?s2,?s3?:=?"foo",?"bar",?"baz"for?i?:=?0;?i?<?b.N;?i++?{_?=?s1?+?s2?+?s3} }//?Bad func?BenchmarkJoinStrWithSprintf(b?*testing.B)?{s1,?s2,?s3?:=?"foo",?"bar",?"baz"for?i?:=?0;?i?<?b.N;?i++?{_?=?fmt.Sprintf("%s%s%s",?s1,?s2,?s3)} }

執行基準測試結果如下:

go?test?-bench=^BenchmarkJoinStr?-benchmem?. BenchmarkJoinStrWithOperator-8????70638928????17.53?ns/op?????0?B/op????0?allocs/op BenchmarkJoinStrWithSprintf-8??????7520017????157.2?ns/op????64?B/op????4?allocs/op
4.2 非行內拼接字符串推薦使用 strings.Builder

字符串拼接還有其他的方式,比如strings.Join()、strings.Builder、bytes.Buffer和byte[],這幾種不適合行內使用。當待拼接字符串數量較多時可考慮使用。

先看下其性能測試的對比。

func?BenchmarkJoinStrWithStringsJoin(b?*testing.B)?{s1,?s2,?s3?:=?"foo",?"bar",?"baz"for?i?:=?0;?i?<?b.N;?i++?{_?=?strings.Join([]string{s1,?s2,?s3},?"")} }func?BenchmarkJoinStrWithStringsBuilder(b?*testing.B)?{s1,?s2,?s3?:=?"foo",?"bar",?"baz"for?i?:=?0;?i?<?b.N;?i++?{var?builder?strings.Builder_,?_?=?builder.WriteString(s1)_,?_?=?builder.WriteString(s2)_,?_?=?builder.WriteString(s3)} }func?BenchmarkJoinStrWithBytesBuffer(b?*testing.B)?{s1,?s2,?s3?:=?"foo",?"bar",?"baz"for?i?:=?0;?i?<?b.N;?i++?{var?buffer?bytes.Buffer_,?_?=?buffer.WriteString(s1)_,?_?=?buffer.WriteString(s2)_,?_?=?buffer.WriteString(s3)} }func?BenchmarkJoinStrWithByteSlice(b?*testing.B)?{s1,?s2,?s3?:=?"foo",?"bar",?"baz"for?i?:=?0;?i?<?b.N;?i++?{var?bys?[]bytebys=?append(bys,?s1...)bys=?append(bys,?s2...)_?=?append(bys,?s3...)} }func?BenchmarkJoinStrWithByteSlicePreAlloc(b?*testing.B)?{s1,?s2,?s3?:=?"foo",?"bar",?"baz"for?i?:=?0;?i?<?b.N;?i++?{bys:=?make([]byte,?0,?9)bys=?append(bys,?s1...)bys=?append(bys,?s2...)_?=?append(bys,?s3...)} }

基準測試結果如下:

go?test?-bench=^BenchmarkJoinStr?. goos:?windows goarch:?amd64 pkg:?main/perf cpu:?Intel(R)?Core(TM)?i7-9700?CPU?@?3.00GHz BenchmarkJoinStrWithStringsJoin-8???????????????31543916????????????????36.39?ns/op BenchmarkJoinStrWithStringsBuilder-8????????????30079785????????????????40.60?ns/op BenchmarkJoinStrWithBytesBuffer-8???????????????31663521????????????????39.58?ns/op BenchmarkJoinStrWithByteSlice-8?????????????????30748495????????????????37.34?ns/op BenchmarkJoinStrWithByteSlicePreAlloc-8?????????665341896???????????????1.813?ns/op

從結果可以看出,strings.Join()、strings.Builder、bytes.Buffer和byte[] 的性能相近。如果結果字符串的長度是可預知的,使用 byte[] 且預先分配容量的拼接方式性能最佳。

所以如果對性能要求非常嚴格,或待拼接的字符串數量足夠多時,建議使用 ?byte[] 預先分配容量這種方式。

綜合易用性和性能,一般推薦使用strings.Builder來拼接字符串。

string.Builder也提供了預分配內存的方式 Grow:

func?BenchmarkJoinStrWithStringsBuilderPreAlloc(b?*testing.B)?{s1,?s2,?s3?:=?"foo",?"bar",?"baz"for?i?:=?0;?i?<?b.N;?i++?{var?builder?strings.Builderbuilder.Grow(9)_,?_?=?builder.WriteString(s1)_,?_?=?builder.WriteString(s2)_,?_?=?builder.WriteString(s3)} }

使用了 Grow 優化后的版本的性能測試結果如下??梢钥闯鱿噍^于不預先分配空間的方式,性能提升了很多。

BenchmarkJoinStrWithStringsBuilderPreAlloc-8????60079003????????????????20.95?ns/op

5.遍歷 []struct{} 使用下標而不是 range

Go 中遍歷切片或數組有兩種方式,一種是通過下標,一種是 range。二者在功能上沒有區別,但是在性能上會有區別嗎?

5.1 []int

首先看一下遍歷基本類型切片時二者的性能差別,以 []int 為例。

//?genRandomIntSlice?生成指定長度的隨機?[]int?切片 func?genRandomIntSlice(n?int)?[]int?{rand.Seed(time.Now().UnixNano())nums?:=?make([]int,?0,?n)for?i?:=?0;?i?<?n;?i++?{nums?=?append(nums,?rand.Int())}return?nums }func?BenchmarkIndexIntSlice(b?*testing.B)?{nums?:=?genRandomIntSlice(1024)for?i?:=?0;?i?<?b.N;?i++?{var?tmp?intfor?k?:=?0;?k?<?len(nums);?k++?{tmp?=?nums[k]}_?=?tmp} }func?BenchmarkRangeIntSlice(b?*testing.B)?{nums?:=?genRandomIntSlice(1024)for?i?:=?0;?i?<?b.N;?i++?{var?tmp?intfor?_,?num?:=?range?nums?{tmp?=?num}_?=?tmp} }

運行測試結果如下:

go?test?-bench=IntSlice$?. goos:?windows goarch:?amd64 pkg:?main/perf cpu:?Intel(R)?Core(TM)?i7-9700?CPU?@?3.00GHz BenchmarkIndexIntSlice-8?????????5043324???????????????236.2?ns/op BenchmarkRangeIntSlice-8?????????5076255???????????????239.1?ns/op

genRandomIntSlice() 函數用于生成指定長度元素類型為 int 的切片。從最終的結果可以看到,遍歷 []int 類型的切片,下標與 range 遍歷性能幾乎沒有區別。

5.2 []struct{}

那么對于稍微復雜一點的 []struct 類型呢?

type?Item?struct?{id??intval?[1024]byte }func?BenchmarkIndexStructSlice(b?*testing.B)?{var?items?[1024]Itemfor?i?:=?0;?i?<?b.N;?i++?{var?tmp?intfor?j?:=?0;?j?<?len(items);?j++?{tmp?=?items[j].id}_?=?tmp} }func?BenchmarkRangeIndexStructSlice(b?*testing.B)?{var?items?[1024]Itemfor?i?:=?0;?i?<?b.N;?i++?{var?tmp?intfor?k?:=?range?items?{tmp?=?items[k].id}_?=?tmp} }func?BenchmarkRangeStructSlice(b?*testing.B)?{var?items?[1024]Itemfor?i?:=?0;?i?<?b.N;?i++?{var?tmp?intfor?_,?item?:=?range?items?{tmp?=?item.id}_?=?tmp} }

運行測試結果如下:

go?test?-bench=StructSlice$?. goos:?windows goarch:?amd64 pkg:?main/perf cpu:?Intel(R)?Core(TM)?i7-9700?CPU?@?3.00GHz BenchmarkIndexStructSlice-8??????????????5079468???????????????234.9?ns/op BenchmarkRangeIndexStructSlice-8?????????5087448???????????????236.2?ns/op BenchmarkRangeStructSlice-8????????????????38716???????????????32265?ns/op

可以看出,兩種通過 index 遍歷 []struct 性能沒有差別,但是 range 遍歷 []struct 中元素時,性能非常差。

range 只遍歷 []struct 下標時,性能比 range 遍歷 ?[]struct 值好很多。從這里我們應該能夠知道二者性能差別之大的原因。

Item 是一個結構體類型 ,Item 由兩個字段構成,一個類型是 int,一個是類型是 [1024]byte,如果每次遍歷 []Item,都會進行一次值拷貝,所以帶來了性能損耗。

此外,因為 range 時獲取的是值拷貝的副本,所以對副本的修改,是不會影響到原切片。

5.3 []*struct

那如果切片中是指向結構體的指針,而不是結構體呢?

//?genItems?生成指定長度?[]*Item?切片 func?genItems(n?int)?[]*Item?{items?:=?make([]*Item,?0,?n)for?i?:=?0;?i?<?n;?i++?{items?=?append(items,?&Item{id:?i})}return?items }func?BenchmarkIndexPointer(b?*testing.B)?{items?:=?genItems(1024)for?i?:=?0;?i?<?b.N;?i++?{var?tmp?intfor?k?:=?0;?k?<?len(items);?k++?{tmp?=?items[k].id}_?=?tmp} }func?BenchmarkRangePointer(b?*testing.B)?{items?:=?genItems(1024)for?i?:=?0;?i?<?b.N;?i++?{var?tmp?intfor?_,?item?:=?range?items?{tmp?=?item.id}_?=?tmp} }

執行性能測試結果:

go?test?-bench=Pointer$?main/perf goos:?windows goarch:?amd64 pkg:?main/perf cpu:?Intel(R)?Core(TM)?i7-9700?CPU?@?3.00GHz BenchmarkIndexPointer-8???????????773634??????????????1521?ns/op BenchmarkRangePointer-8???????????752077??????????????1514?ns/op

切片元素從結構體 Item 替換為指針 *Item 后,for 和 range 的性能幾乎是一樣的。而且使用指針還有另一個好處,可以直接修改指針對應的結構體的值。

5.4 小結

range 在迭代過程中返回的是元素的拷貝,index 則不存在拷貝。

如果 range 迭代的元素較小,那么 index 和 range 的性能幾乎一樣,如基本類型的切片 []int。但如果迭代的元素較大,如一個包含很多屬性的 struct 結構體,那么 index 的性能將顯著地高于 range,有時候甚至會有上千倍的性能差異。對于這種場景,建議使用 index。如果使用 range,建議只迭代下標,通過下標訪問元素,這種使用方式和 index 就沒有區別了。如果想使用 range 同時迭代下標和值,則需要將切片/數組的元素改為指針,才能不影響性能。

內存管理

1.使用空結構體節省內存

1.1 不占內存空間

在 Go 中,我們可以使用 unsafe.Sizeof 計算出一個數據類型實例需要占用的字節數。

package?mainimport?("fmt""unsafe" )func?main()?{fmt.Println(unsafe.Sizeof(struct{}{})) }

運行上面的例子將會輸出:

go?run?main.go 0

可以看到,Go 中空結構體 struct{} 是不占用內存空間,不像 C/C++ 中空結構體仍占用 1 字節。

1.2 用法

因為空結構體不占據內存空間,因此被廣泛作為各種場景下的占位符使用。一是節省資源,二是空結構體本身就具備很強的語義,即這里不需要任何值,僅作為占位符,達到的代碼即注釋的效果。

1.2.1 實現集合(Set)

Go 語言標準庫沒有提供 Set 的實現,通常使用 map 來代替。事實上,對于集合來說,只需要 map 的鍵,而不需要值。即使是將值設置為 bool 類型,也會多占據 1 個字節,那假設 map 中有一百萬條數據,就會浪費 1MB 的空間。

因此呢,將 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("foo")s.Add("bar")fmt.Println(s.Has("foo"))fmt.Println(s.Has("bar")) }

如果想使用 Set 的完整功能,如初始化(通過切片構建一個 Set)、Add、Del、Clear、Contains 等操作,可以使用開源庫 golang-set。

1.2.2 不發送數據的信道
func?worker(ch?chan?struct{})?{<-chfmt.Println("do?something") }func?main()?{ch?:=?make(chan?struct{})go?worker(ch)ch?<-?struct{}{}close(ch) }

有時候使用 channel 不需要發送任何的數據,只用來通知子協程(goroutine)執行任務,或只用來控制協程的并發。這種情況下,使用空結構體作為占位符就非常合適了。

1.2.3 僅包含方法的結構體
type?Door?struct{}func?(d?Door)?Open()?{fmt.Println("Open?the?door") }func?(d?Door)?Close()?{fmt.Println("Close?the?door") }

在部分場景下,結構體只包含方法,不包含任何的字段。例如上面例子中的 Door,在這種情況下,Door 事實上可以用任何的數據結構替代。

type?Door?int type?Door?bool

無論是 int 還是 bool 都會浪費額外的內存,因此呢,這種情況下,聲明為空結構體最合適。

2. struct 布局要考慮內存對齊

2.1 為什么需要內存對齊

CPU 訪問內存時,并不是逐個字節訪問,而是以字長(word size)為單位訪問。比如 32 位的 CPU ,字長為 4 字節,那么 CPU 訪問內存的單位也是 4 字節。

這么設計的目的,是減少 CPU 訪問內存的次數,加大 CPU 訪問內存的吞吐量。比如同樣讀取 8 個字節的數據,一次讀取 4 個字節那么只需要讀取 2 次。

CPU 始終以字長訪問內存,如果不進行內存對齊,很可能增加 CPU 訪問內存的次數,例如:

變量 a、b 各占據 3 字節的空間,內存對齊后,a、b 占據 4 字節空間,CPU 讀取 b 變量的值只需要進行一次內存訪問。如果不進行內存對齊,CPU 讀取 b 變量的值需要進行 2 次內存訪問。第一次訪問得到 b 變量的第 1 個字節,第二次訪問得到 b 變量的后兩個字節。

從這個例子中也可以看到,內存對齊對實現變量的原子性操作也是有好處的,每次內存訪問是原子的,如果變量的大小不超過字長,那么內存對齊后,對該變量的訪問就是原子的,這個特性在并發場景下至關重要。

簡言之:合理的內存對齊可以提高內存讀寫的性能,并且便于實現變量操作的原子性。

2.2 Go 內存對齊規則

編譯器一般為了減少 CPU 訪存指令周期,提高內存的訪問效率,會對變量進行內存對齊。Go 作為一門追求高性能的后臺編程語言,當然也不例外。

Go Language Specification 中 Size and alignment guarantees 描述了內存對齊的規則。

1.For a variable x of any type: unsafe.Alignof(x) is at least 1. 2.For a variable x of struct type: unsafe.Alignof(x) is the largest of all the values unsafe.Alignof(x.f) for each field f of x, but at least 1. 3.For a variable x of array type: unsafe.Alignof(x) is the same as the alignment of a variable of the array's element type.

  • 對于任意類型的變量 x ,unsafe.Alignof(x) 至少為 1。

  • 對于結構體類型的變量 x,計算 x 每一個字段 f 的 unsafe.Alignof(x.f),unsafe.Alignof(x) 等于其中的最大值。

  • 對于數組類型的變量 x,unsafe.Alignof(x) 等于構成數組的元素類型的對齊系數。

其中函數 unsafe.Alignof 用于獲取變量的對齊系數。對齊系數決定了字段的偏移和變量的大小,兩者必須是對齊系數的整數倍。

2.3 合理的 struct 布局

因為內存對齊的存在,合理的 struct 布局可以減少內存占用,提高程序性能。

type?demo1?struct?{a?int8b?int16c?int32 }type?demo2?struct?{a?int8c?int32b?int16 }func?main()?{fmt.Println(unsafe.Sizeof(demo1{}))?//?8fmt.Println(unsafe.Sizeof(demo2{}))?//?12 }

可以看到,同樣的字段,因字段排列順序不同,最終會導致不一樣的結構體大小。

每個字段按照自身的對齊系數來確定在內存中的偏移量,一個字段因偏移而浪費的大小也不同。

接下來逐個分析,首先是 demo1:a 是第一個字段,默認是已經對齊的,從第 0 個位置開始占據 1 字節。b 是第二個字段,對齊系數為 2,因此,必須空出 1 個字節,偏移量才是 2 的倍數,從第 2 個位置開始占據 2 字節。c 是第三個字段,對齊倍數為 4,此時,內存已經是對齊的,從第 4 個位置開始占據 4 字節即可。

因此 demo1 的內存占用為 8 字節。

對于 demo2:a 是第一個字段,默認是已經對齊的,從第 0 個位置開始占據 1 字節。c 是第二個字段,對齊倍數為 4,因此,必須空出 3 個字節,偏移量才是 4 的倍數,從第 4 個位置開始占據 4 字節。b 是第三個字段,對齊倍數為 2,從第 8 個位置開始占據 2 字節。

demo2 的對齊系數由 c 的對齊系數決定,也是 4,因此,demo2 的內存占用為 12 字節。

因此,在對內存特別敏感的結構體的設計上,我們可以通過調整字段的順序,將字段寬度從小到大由上到下排列,來減少內存的占用。

2.4 空結構與空數組對內存對齊的影響

空結構與空數組在 Go 中比較特殊。沒有任何字段的空 struct{} 和沒有任何元素的 array 占據的內存空間大小為 0。

因為這一點,空 struct{} 或空 array 作為其他 struct 的字段時,一般不需要內存對齊。但是有一種情況除外:即當 struct{} 或空 array 作為結構體最后一個字段時,需要內存對齊。因為如果有指針指向該字段,返回的地址將在結構體之外,如果此指針一直存活不釋放對應的內存,就會有內存泄露的問題(該內存不因結構體釋放而釋放)。

type?demo3?struct?{a?struct{}b?int32 } type?demo4?struct?{b?int32a?struct{} }func?main()?{fmt.Println(unsafe.Sizeof(demo3{}))?//?4fmt.Println(unsafe.Sizeof(demo4{}))?//?8 }

可以看到,demo3{} 的大小為 4 字節,與字段 b 占據空間一致,而 demo4{} 的大小為 8 字節,即額外填充了 4 字節的空間。

3.減少逃逸,將變量限制在棧上

變量逃逸一般發生在如下幾種情況:

  • 變量較大

  • 變量大小不確定

  • 變量類型不確定

  • 返回指針

  • 返回引用

  • 閉包

知道變量逃逸的原因后,我們可以有意識的控制變量不發生逃逸,將其控制在棧上,減少堆變量的分配,降低 GC 成本,提高程序性能。

3.1 小的拷貝好過引用

小的拷貝好過引用,什么意思呢,就是盡量使用棧變量而不是堆變量。下面舉一個反常識的例子,來證明小的拷貝比在堆上創建引用變量要好。

我們都知道 Go 里面的 Array 以 pass-by-value 方式傳遞后,再加上其長度不可擴展,考慮到性能我們一般很少使用它。實際上,凡事無絕對。有時使用數組進行拷貝傳遞,比使用切片要好。

//?copy/copy.goconst?capacity?=?1024func?arrayFibonacci()?[capacity]int?{var?d?[capacity]intfor?i?:=?0;?i?<?len(d);?i++?{if?i?<=?1?{d[i]?=?1continue}d[i]?=?d[i-1]?+?d[i-2]}return?d }func?sliceFibonacci()?[]int?{d?:=?make([]int,?capacity)for?i?:=?0;?i?<?len(d);?i++?{if?i?<=?1?{d[i]?=?1continue}d[i]?=?d[i-1]?+?d[i-2]}return?d }

下面看一下性能對比。

func?BenchmarkArray(b?*testing.B)?{for?i?:=?0;?i?<?b.N;?i++?{_?=?arrayFibonacci()} }func?BenchmarkSlice(b?*testing.B)?{for?i?:=?0;?i?<?b.N;?i++?{_?=?sliceFibonacci()} }

運行上面的基準測試,將得到如下結果。

go?test?-bench=.?-benchmem?-gcflags="-l"?main/copy goos:?darwin goarch:?amd64 pkg:?main/copy cpu:?Intel(R)?Core(TM)?i7-9750H?CPU?@?2.60GHz BenchmarkArray-12?????????692400??????????????1708?ns/op???????????????0?B/op??????????0?allocs/op BenchmarkSlice-12?????????464974??????????????2242?ns/op????????????8192?B/op??????????1?allocs/op PASS ok??????main/copy???????3.908s

從測試結果可以看出,對數組的拷貝性能卻比使用切片要好。為什么會這樣呢?

sliceFibonacci() 函數中分配的局部變量切片因為要返回到函數外部,所以發生了逃逸,需要在堆上申請內存空間。從測試也過也可以看出,arrayFibonacci() 函數沒有內存分配,完全在棧上完成數組的創建。這里說明了對于一些短小的對象,棧上復制的成本遠小于在堆上分配和回收操作。

需要注意,運行上面基準測試時,傳遞了禁止內聯的編譯選項 "-l",如果發生內聯,那么將不會出現變量的逃逸,就不存在堆上分配內存與回收的操作了,二者將看不出性能差異。

編譯時可以借助選項 -gcflags=-m 查看編譯器對上面兩個函數的優化決策。

go?build??-gcflags=-m?copy/copy.go #?command-line-arguments copy/copy.go:5:6:?can?inline?arrayFibonacci copy/copy.go:17:6:?can?inline?sliceFibonacci copy/copy.go:18:11:?make([]int,?capacity)?escapes?to?heap

可以看到,arrayFibonacci() 和 sliceFibonacci() 函數均可內聯。sliceFibonacci() 函數中定義的局部變量切片逃逸到了堆。

那么多大的變量才算是小變量呢?對 Go 編譯器而言,超過一定大小的局部變量將逃逸到堆上,不同的 Go 版本的大小限制可能不一樣。一般是 <64KB,局部變量將不會逃逸到堆上。

3.2 返回值 VS 返回指針

值傳遞會拷貝整個對象,而指針傳遞只會拷貝地址,指向的對象是同一個。返回指針可以減少值的拷貝,但是會導致內存分配逃逸到堆中,增加垃圾回收(GC)的負擔。在對象頻繁創建和刪除的場景下,傳遞指針導致的 GC 開銷可能會嚴重影響性能。

一般情況下,對于需要修改原對象值,或占用內存比較大的結構體,選擇返回指針。對于只讀的占用內存較小的結構體,直接返回值能夠獲得更好的性能。

3.3 返回值使用確定的類型

如果變量類型不確定,那么將會逃逸到堆上。所以,函數返回值如果能確定的類型,就不要使用 interface{}。

我們還是以上面斐波那契數列函數為例,看下返回值為確定類型和 interface{} 的性能差別。

const?capacity?=?1024func?arrayFibonacci()?[capacity]int?{var?d?[capacity]intfor?i?:=?0;?i?<?len(d);?i++?{if?i?<=?1?{d[i]?=?1continue}d[i]?=?d[i-1]?+?d[i-2]}return?d }func?arrayFibonacciIfc()?interface{}?{var?d?[capacity]intfor?i?:=?0;?i?<?len(d);?i++?{if?i?<=?1?{d[i]?=?1continue}d[i]?=?d[i-1]?+?d[i-2]}return?d }func?BenchmarkArray(b?*testing.B)?{for?i?:=?0;?i?<?b.N;?i++?{_?=?arrayFibonacci()} }func?BenchmarkIfc(b?*testing.B)?{for?i?:=?0;?i?<?b.N;?i++?{_?=?arrayFibonacciIfc()} }

運行上面的基準測試結果如下:

go?test?-bench=.?-benchmem?main/copy goos:?darwin goarch:?amd64 pkg:?main/copy cpu:?Intel(R)?Core(TM)?i7-9750H?CPU?@?2.60GHz BenchmarkArray-12?????????832418??????????????1427?ns/op???????????????0?B/op??????????0?allocs/op BenchmarkIfc-12???????????380626??????????????2861?ns/op????????????8192?B/op??????????1?allocs/op PASS ok??????main/copy???????3.742s

可見,函數返回值使用 interface{} 返回時,編譯器無法確定返回值的具體類型,導致返回值逃逸到堆上。當發生了堆上內存的申請與回收時,性能會差一點。

4.sync.Pool 復用對象

4.1 簡介

sync.Pool 是 sync 包下的一個組件,可以作為保存臨時取還對象的一個“池子”。個人覺得它的名字有一定的誤導性,因為 Pool 里裝的對象可以被無通知地被回收,可能 sync.Cache 是一個更合適的名字。

sync.Pool 是可伸縮的,同時也是并發安全的,其容量僅受限于內存的大小。存放在池中的對象如果不活躍了會被自動清理。

4.2 作用

對于很多需要重復分配、回收內存的地方,sync.Pool 是一個很好的選擇。頻繁地分配、回收內存會給 GC 帶來一定的負擔,嚴重的時候會引起 CPU 的毛刺,而 sync.Pool 可以將暫時不用的對象緩存起來,待下次需要的時候直接使用,不用再次經過內存分配,復用對象的內存,減輕 GC 的壓力,提升系統的性能。

一句話總結:用來保存和復用臨時對象,減少內存分配,降低 GC 壓力。

4.3 如何使用

sync.Pool 的使用方式非常簡單,只需要實現 New 函數即可。對象池中沒有對象時,將會調用 New 函數創建。

假設我們有一個“學生”結構體,并復用改結構體對象。

type?Student?struct?{Name???stringAge????int32Remark?[1024]byte }var?studentPool?=?sync.Pool{New:?func()?interface{}?{?return?new(Student)?}, }

然后調用 Pool 的 Get() 和 Put() 方法來獲取和放回池子中。

stu?:=?studentPool.Get().(*Student) json.Unmarshal(buf,?stu) studentPool.Put(stu)
  • Get() 用于從對象池中獲取對象,因為返回值是 interface{},因此需要類型轉換。

  • Put() 則是在對象使用完畢后,放回到對象池。

4.4 性能差異

我們以 bytes.Buffer 字節緩沖器為例,利用 sync.Pool 復用 bytes.Buffer 對象,避免重復創建與回收內存,來看看對性能的提升效果。

var?bufferPool?=?sync.Pool{New:?func()?interface{}?{return?&bytes.Buffer{}}, }var?data?=?make([]byte,?10000)func?BenchmarkBufferWithPool(b?*testing.B)?{for?n?:=?0;?n?<?b.N;?n++?{buf?:=?bufferPool.Get().(*bytes.Buffer)buf.Write(data)buf.Reset()bufferPool.Put(buf)} }func?BenchmarkBuffer(b?*testing.B)?{for?n?:=?0;?n?<?b.N;?n++?{var?buf?bytes.Bufferbuf.Write(data)} }

測試結果如下:

go?test?-bench=.?-benchmem?main/pool goos:?darwin goarch:?amd64 pkg:?main/pool cpu:?Intel(R)?Core(TM)?i7-9750H?CPU?@?2.60GHz BenchmarkBufferWithPool-12??????11987966????????????????97.12?ns/op????????????0?B/op??????????0?allocs/op BenchmarkBuffer-12???????????????1246887??????????????1020?ns/op???????????10240?B/op??????????1?allocs/op PASS ok??????main/pool???????3.510s

這個例子創建了一個 bytes.Buffer 對象池,每次只執行 Write 操作,及做一次數據拷貝,耗時幾乎可以忽略。而內存分配和回收的耗時占比較多,因此對程序整體的性能影響更大。從測試結果也可以看出,使用了 Pool 復用對象,每次操作不再有內存分配。

4.5 在標準庫中的應用

Go 標準庫也大量使用了 sync.Pool,例如 fmt 和 encoding/json。以 fmt 包為例,我們看下其是如何使用 sync.Pool 的。

我們可以看一下最常用的標準格式化輸出函數 Printf() 函數。

//?Printf?formats?according?to?a?format?specifier?and?writes?to?standard?output. //?It?returns?the?number?of?bytes?written?and?any?write?error?encountered. func?Printf(format?string,?a?...interface{})?(n?int,?err?error)?{return?Fprintf(os.Stdout,?format,?a...) }

繼續看 Fprintf() 的定義。

//?Fprintf?formats?according?to?a?format?specifier?and?writes?to?w. //?It?returns?the?number?of?bytes?written?and?any?write?error?encountered. func?Fprintf(w?io.Writer,?format?string,?a?...interface{})?(n?int,?err?error)?{p?:=?newPrinter()p.doPrintf(format,?a)n,?err?=?w.Write(p.buf)p.free()return }

Fprintf() 函數的參數是一個 io.Writer,Printf() 傳的是 os.Stdout,相當于直接輸出到標準輸出。這里的 newPrinter 用的就是 sync.Pool。

//?go?version?go1.17?darwin/amd64//?pp?is?used?to?store?a?printer's?state?and?is?reused?with?sync.Pool?to?avoid?allocations. type?pp?struct?{buf?buffer... }var?ppFree?=?sync.Pool{New:?func()?interface{}?{?return?new(pp)?}, }//?newPrinter?allocates?a?new?pp?struct?or?grabs?a?cached?one. func?newPrinter()?*pp?{p?:=?ppFree.Get().(*pp)p.panicking?=?falsep.erroring?=?falsep.wrapErrs?=?falsep.fmt.init(&p.buf)return?p }//?free?saves?used?pp?structs?in?ppFree;?avoids?an?allocation?per?invocation. func?(p?*pp)?free()?{//?Proper?usage?of?a?sync.Pool?requires?each?entry?to?have?approximately//?the?same?memory?cost.?To?obtain?this?property?when?the?stored?type//?contains?a?variably-sized?buffer,?we?add?a?hard?limit?on?the?maximum?buffer//?to?place?back?in?the?pool.////?See?https://golang.org/issue/23199if?cap(p.buf)?>?64<<10?{return}p.buf?=?p.buf[:0]p.arg?=?nilp.value?=?reflect.Value{}p.wrappedErr?=?nilppFree.Put(p) }

fmt.Printf() 的調用是非常頻繁的,利用 sync.Pool 復用 pp 對象能夠極大地提升性能,減少內存占用,同時降低 GC 壓力。

并發編程

1.關于鎖

1.1 無鎖化

加鎖是為了避免在并發環境下,同時訪問共享資源產生的安全問題。那么,在并發環境下,是否必須加鎖?答案是否定的。并非所有的并發都需要加鎖。適當地降低鎖的粒度,甚至采用無鎖化的設計,更能提升并發能力。

無鎖化主要有兩種實現,無鎖數據結構和串行無鎖。

1.1.1 無鎖數據結構

利用硬件支持的原子操作可以實現無鎖的數據結構,原子操作可以在 lock-free 的情況下保證并發安全,并且它的性能也能做到隨 CPU 個數的增多而線性擴展。很多語言都提供 CAS 原子操作(如 Go 中的 atomic 包和 C++11 中的 atomic 庫),可以用于實現無鎖數據結構,如無鎖鏈表。

我們以一個簡單的線程安全單向鏈表的插入操作來看下無鎖編程和普通加鎖的區別。

package?listimport?("fmt""sync""sync/atomic""golang.org/x/sync/errgroup" )//?Node?鏈表節點 type?Node?struct?{Value?interface{}Next??*Node }// //?有鎖單向鏈表的簡單實現 ////?WithLockList?有鎖單向鏈表 type?WithLockList?struct?{Head?*Nodemu???sync.Mutex }//?Push?將元素插入到鏈表的首部 func?(l?*WithLockList)?Push(v?interface{})?{l.mu.Lock()defer?l.mu.Unlock()n?:=?&Node{Value:?v,Next:??l.Head,}l.Head?=?n }//?String?有鎖鏈表的字符串形式輸出 func?(l?WithLockList)?String()?string?{s?:=?""cur?:=?l.Headfor?{if?cur?==?nil?{break}if?s?!=?""?{s?+=?","}s?+=?fmt.Sprintf("%v",?cur.Value)cur?=?cur.Next}return?s }// //?無鎖單向鏈表的簡單實現 ////?LockFreeList?無鎖單向鏈表 type?LockFreeList?struct?{Head?atomic.Value }//?Push?有鎖 func?(l?*LockFreeList)?Push(v?interface{})?{for?{head?:=?l.Head.Load()headNode,?_?:=?head.(*Node)n?:=?&Node{Value:?v,Next:??headNode,}if?l.Head.CompareAndSwap(head,?n)?{break}} }//?String?有鎖鏈表的字符串形式輸出 func?(l?LockFreeList)?String()?string?{s?:=?""cur?:=?l.Head.Load().(*Node)for?{if?cur?==?nil?{break}if?s?!=?""?{s?+=?","}s?+=?fmt.Sprintf("%v",?cur.Value)cur?=?cur.Next}return?s }

上面的實現有幾點需要注意一下:

(1)無鎖單向鏈表實現時在插入時需要進行 CAS 操作,即調用CompareAndSwap()方法進行插入,如果插入失敗則進行 for 循環多次嘗試,直至成功。

(2)為了方便打印鏈表內容,實現一個String()方法遍歷鏈表,且使用值作為接收者,避免打印對象指針時無法生效。

  • If an operand implements method String() string, that method will be invoked to convert the object to a string, which will then be formatted as required by the verb (if any).

  • 我們分別對兩種鏈表做一個并發寫入的操作驗證一下其功能。

    package?mainimport?("fmt""main/list" )//?ConcurWriteWithLockList?并發寫入有鎖鏈表 func?ConcurWriteWithLockList(l?*WithLockList)?{var?g?errgroup.Group//?10?個協程并發寫入鏈表for?i?:=?0;?i?<?10;?i++?{i?:=?ig.Go(func()?error?{l.Push(i)return?nil})}_?=?g.Wait() }//?ConcurWriteLockFreeList?并發寫入無鎖鏈表 func?ConcurWriteLockFreeList(l?*LockFreeList)?{var?g?errgroup.Group//?10?個協程并發寫入鏈表for?i?:=?0;?i?<?10;?i++?{i?:=?ig.Go(func()?error?{l.Push(i)return?nil})}_?=?g.Wait() }func?main()?{//?并發寫入與遍歷打印有鎖鏈表l1?:=?&list.WithLockList{}list.ConcurWriteWithLockList(l1)fmt.Println(l1)//?并發寫入與遍歷打印無鎖鏈表l2?:=?&list.LockFreeList{}list.ConcurWriteLockFreeList(l2)fmt.Println(l2) }

    注意,多次運行上面的main()函數的結果可能會不相同,因為并發是無序的。

    8,7,6,9,5,4,3,1,2,0 9,8,7,6,5,4,3,2,0,1

    下面再看一下鏈表 Push 操作的基準測試,對比一下有鎖與無鎖的性能差異。

    func?BenchmarkWriteWithLockList(b?*testing.B)?{l?:=?&WithLockList{}for?n?:=?0;?n?<?b.N;?n++?{l.Push(n)} } BenchmarkWriteWithLockList-8????14234166????????????????83.58?ns/opfunc?BenchmarkWriteLockFreeList(b?*testing.B)?{l?:=?&LockFreeList{}for?n?:=?0;?n?<?b.N;?n++?{l.Push(n)} } BenchmarkWriteLockFreeList-8????15219405????????????????73.15?ns/op

    可以看出無鎖版本比有鎖版本性能高一些。

    1.1.2 串行無鎖

    串行無鎖是一種思想,就是避免對共享資源的并發訪問,改為每個并發操作訪問自己獨占的資源,達到串行訪問資源的效果,來避免使用鎖。不同的場景有不同的實現方式。比如網絡 I/O 場景下將單 Reactor 多線程模型改為主從 Reactor 多線程模型,避免對同一個消息隊列鎖讀取。

    這里我介紹的是后臺微服務開發經常遇到的一種情況。我們經常需要并發拉取多方面的信息,匯聚到一個變量上。那么此時就存在對同一個變量互斥寫入的情況。比如批量并發拉取用戶信息寫入到一個 map。此時我們可以將每個協程拉取的結果寫入到一個臨時對象,這樣便將并發地協程與同一個變量解綁,然后再將其匯聚到一起,這樣便可以不用使用鎖。即獨立處理,然后合并。

    為了模擬上面的情況,簡單地寫個示例程序,對比下性能。

    import?("sync""golang.org/x/sync/errgroup" )//?ConcurWriteMapWithLock?有鎖并發寫入?map func?ConcurWriteMapWithLock()?map[int]int?{m?:=?make(map[int]int)var?mu?sync.Mutexvar?g?errgroup.Group//?10?個協程并發寫入?mapfor?i?:=?0;?i?<?10;?i++?{i?:=?ig.Go(func()?error?{mu.Lock()defer?mu.Unlock()m[i]?=?i?*?ireturn?nil})}_?=?g.Wait()return?m }//?ConcurWriteMapLockFree?無鎖并發寫入?map func?ConcurWriteMapLockFree()?map[int]int?{m?:=?make(map[int]int)//?每個協程獨占一?valuevalues?:=?make([]int,?10)//?10?個協程并發寫入?mapvar?g?errgroup.Groupfor?i?:=?0;?i?<?10;?i++?{i?:=?ig.Go(func()?error?{values[i]?=?i?*?ireturn?nil})}_?=?g.Wait()//?匯聚結果到?mapfor?i,?v?:=?range?values?{m[i]?=?v}return?m }

    看下二者的性能差異:

    func?BenchmarkConcurWriteMapWithLock(b?*testing.B)?{for?n?:=?0;?n?<?b.N;?n++?{_?=?ConcurWriteMapWithLock()} } BenchmarkConcurWriteMapWithLock-8?????????218673??????????????5089?ns/opfunc?BenchmarkConcurWriteMapLockFree(b?*testing.B)?{for?n?:=?0;?n?<?b.N;?n++?{_?=?ConcurWriteMapLockFree()} } BenchmarkConcurWriteMapLockFree-8?????????316635??????????????4048?ns/op
    1.2 減少鎖競爭

    如果加鎖無法避免,則可以采用分片的形式,減少對資源加鎖的次數,這樣也可以提高整體的性能。

    比如 Golang 優秀的本地緩存組件 ?bigcache 、go-cache、freecache 都實現了分片功能,每個分片一把鎖,采用分片存儲的方式減少加鎖的次數從而提高整體性能。

    以一個簡單的示例,通過對map[uint64]struct{}分片前后并發寫入的對比,來看下減少鎖競爭帶來的性能提升。

    var?(num?=?1000000m0??=?make(map[int]struct{},?num)mu0?=?sync.RWMutex{}m1??=?make(map[int]struct{},?num)mu1?=?sync.RWMutex{} )// ConWriteMapNoShard 不分片寫入一個 map。 func?ConWriteMapNoShard()?{g?:=?errgroup.Group{}for?i?:=?0;?i?<?num;?i++?{g.Go(func()?error?{mu0.Lock()defer?mu0.Unlock()m0[i]?=?struct{}{}return?nil})}_?=?g.Wait() }// ConWriteMapTwoShard 分片寫入兩個 map。 func?ConWriteMapTwoShard()?{g?:=?errgroup.Group{}for?i?:=?0;?i?<?num;?i++?{g.Go(func()?error?{if?i&1?==?0?{mu0.Lock()defer?mu0.Unlock()m0[i]?=?struct{}{}return?nil}mu1.Lock()defer?mu1.Unlock()m1[i]?=?struct{}{}return?nil})}_?=?g.Wait() }

    看下二者的性能差異:

    func?BenchmarkConWriteMapNoShard(b?*testing.B)?{for?i?:=?0;?i?<?b.N;?i++?{ConWriteMapNoShard()} } BenchmarkConWriteMapNoShard-12?????????????????3?????????472063245?ns/opfunc?BenchmarkConWriteMapTwoShard(b?*testing.B)?{for?i?:=?0;?i?<?b.N;?i++?{ConWriteMapTwoShard()} } BenchmarkConWriteMapTwoShard-12????????????????4?????????310588155?ns/op

    可以看到,通過對分共享資源的分片處理,減少了鎖競爭,能明顯地提高程序的并發性能??梢灶A見的是,隨著分片粒度地變小,性能差距會越來越大。當然,分片粒度不是越小越好。因為每一個分片都要配一把鎖,那么會帶來很多額外的不必要的開銷??梢赃x擇一個不太大的值,在性能和花銷上尋找一個平衡。

    1.3 優先使用共享鎖而非互斥鎖

    如果并發無法做到無鎖化,優先使用共享鎖而非互斥鎖。

    所謂互斥鎖,指鎖只能被一個 Goroutine 獲得。共享鎖指可以同時被多個 Goroutine 獲得的鎖。

    Go 標準庫 sync 提供了兩種鎖,互斥鎖(sync.Mutex)和讀寫鎖(sync.RWMutex),讀寫鎖便是共享鎖的一種具體實現。

    1.3.1 sync.Mutex

    互斥鎖的作用是保證共享資源同一時刻只能被一個 Goroutine 占用,一個 Goroutine 占用了,其他的 Goroutine 則阻塞等待。

    sync.Mutex 提供了兩個導出方法用來使用鎖。

    Lock()???//?加鎖 Unlock()???//?釋放鎖

    我們可以通過在訪問共享資源前前用 Lock 方法對資源進行上鎖,在訪問共享資源后調用 Unlock 方法來釋放鎖,也可以用 defer 語句來保證互斥鎖一定會被解鎖。在一個 Go 協程調用 Lock 方法獲得鎖后,其他請求鎖的協程都會阻塞在 Lock 方法,直到鎖被釋放。

    1.3.2 sync.RWMutex

    讀寫鎖是一種共享鎖,也稱之為多讀單寫鎖 (multiple readers, single writer lock)。在使用鎖時,對獲取鎖的目的操作做了區分,一種是讀操作,一種是寫操作。因為同一時刻允許多個 Gorouine 獲取讀鎖,所以是一種共享鎖。但寫鎖是互斥的。

    一般來說,有如下幾種情況:

    • 讀鎖之間不互斥,沒有寫鎖的情況下,讀鎖是無阻塞的,多個協程可以同時獲得讀鎖。

    • 寫鎖之間是互斥的,存在寫鎖,其他寫鎖阻塞。

    • 寫鎖與讀鎖是互斥的,如果存在讀鎖,寫鎖阻塞,如果存在寫鎖,讀鎖阻塞。

    sync.RWMutex 提供了五個導出方法用來使用鎖。

    Lock()????//?加寫鎖 Unlock()???//?釋放寫鎖 RLock()????//?加讀鎖 RUnlock()???//?釋放讀鎖 RLocker()?Locker?//?返回讀鎖,使用?Lock()?和?Unlock()?進行?RLock()?和?RUnlock()

    讀寫鎖的存在是為了解決讀多寫少時的性能問題,讀場景較多時,讀寫鎖可有效地減少鎖阻塞的時間。

    1.3.3 性能對比

    大部分業務場景是讀多寫少,所以使用讀寫鎖可有效提高對共享數據的訪問效率。最壞的情況,只有寫請求,那么讀寫鎖頂多退化成互斥鎖。所以優先使用讀寫鎖而非互斥鎖,可以提高程序的并發性能。

    接下來,我們測試三種情景下,互斥鎖和讀寫鎖的性能差異。

    • 讀多寫少(讀占 80%)

    • 讀寫一致(各占 50%)

    • 讀少寫多(讀占 20%)

    首先根據互斥鎖和讀寫鎖分別實現對共享 map 的并發讀寫。

    // OpMapWithMutex 使用互斥鎖讀寫 map。 // rpct 為讀操作占比。 func?OpMapWithMutex(rpct?int)?{m?:=?make(map[int]struct{})mu?:=?sync.Mutex{}var?wg?sync.WaitGroupfor?i?:=?0;?i?<?100;?i++?{i?:=?iwg.Add(1)go?func()?{defer?wg.Done()mu.Lock()defer?mu.Unlock()//?寫操作。if?i?>=?rpct?{m[i]?=?struct{}{}time.Sleep(time.Microsecond)return}//?讀操作。_?=?m[i]time.Sleep(time.Microsecond)}()}wg.Wait() }// OpMapWithRWMutex 使用讀寫鎖讀寫 map。 // rpct 為讀操作占比。 func?OpMapWithRWMutex(rpct?int)?{m?:=?make(map[int]struct{})mu?:=?sync.RWMutex{}var?wg?sync.WaitGroupfor?i?:=?0;?i?<?100;?i++?{i?:=?iwg.Add(1)go?func()?{defer?wg.Done()//?寫操作。if?i?>=?rpct?{mu.Lock()defer?mu.Unlock()m[i]?=?struct{}{}time.Sleep(time.Microsecond)return}//?讀操作。mu.RLock()defer?mu.RUnlock()_?=?m[i]time.Sleep(time.Microsecond)}()}wg.Wait() }

    入參 rpct 用來調節讀操作的占比,來模擬讀寫占比不同的場景。rpct 設為 80 表示讀多寫少(讀占 80%),rpct 設為 50 表示讀寫一致(各占 50%),rpct 設為 20 表示讀少寫多(讀占 20%)。

    func?BenchmarkMutexReadMore(b?*testing.B)?{for?i?:=?0;?i?<?b.N;?i++?{OpMapWithMutex(80)} }func?BenchmarkRWMutexReadMore(b?*testing.B)?{for?i?:=?0;?i?<?b.N;?i++?{OpMapWithRWMutex(80)} }func?BenchmarkMutexRWEqual(b?*testing.B)?{for?i?:=?0;?i?<?b.N;?i++?{OpMapWithMutex(50)} }func?BenchmarkRWMutexRWEqual(b?*testing.B)?{for?i?:=?0;?i?<?b.N;?i++?{OpMapWithRWMutex(50)} }func?BenchmarkMutexWriteMore(b?*testing.B)?{for?i?:=?0;?i?<?b.N;?i++?{OpMapWithMutex(20)} }func?BenchmarkRWMutexWriteMore(b?*testing.B)?{for?i?:=?0;?i?<?b.N;?i++?{OpMapWithRWMutex(20)} }

    執行當前包下的所有基準測試,結果如下:

    dablelv@DABLELV-MB0?mutex?%?go?test?-bench=. goos:?darwin goarch:?amd64 pkg:?main/mutex cpu:?Intel(R)?Core(TM)?i7-9750H?CPU?@?2.60GHz BenchmarkMutexReadMore-12???????????????????2462????????????485917?ns/op BenchmarkRWMutexReadMore-12?????????????????8074????????????145690?ns/op BenchmarkMutexRWEqual-12????????????????????2406????????????498673?ns/op BenchmarkRWMutexRWEqual-12??????????????????4124????????????303693?ns/op BenchmarkMutexWriteMore-12??????????????????1906????????????532350?ns/op BenchmarkRWMutexWriteMore-12????????????????2462????????????432386?ns/op PASS ok??????main/mutex??????9.532s

    可見讀多寫少的場景,使用讀寫鎖并發性能會更優??梢灶A見的是如果寫占比更低,那么讀寫鎖帶的并發效果會更優。

    這里需要注意的是,因為每次讀寫 map 的操作耗時很短,所以每次睡眠一微秒(百萬分之一秒)來增加耗時,不然對共享資源的訪問耗時,小于鎖處理的本身耗時,那么使用讀寫鎖帶來的性能優化效果將變得不那么明顯,甚至會降低性能。

    2.限制協程數量

    2.1 協程數過多的問題
    2.1.1 程序崩潰

    Go 程(goroutine)是由 Go 運行時管理的輕量級線程。通過它我們可以輕松實現并發編程。但是當我們無限開辟協程時,將會遇到致命的問題。

    func?main()?{var?wg?sync.WaitGroupfor?i?:=?0;?i?<?math.MaxInt32;?i++?{wg.Add(1)go?func(i?int)?{defer?wg.Done()fmt.Println(i)time.Sleep(time.Second)}(i)}wg.Wait() }

    這個例子實現了 math.MaxInt32 個協程的并發,2^31 - 1 約為 20 億個,每個協程內部幾乎沒有做什么事情。正常的情況下呢,這個程序會亂序輸出 0 ~ 2^31-1 個數字。

    程序會像預期的那樣順利的運行嗎?

    go?run?main.go ... 108668 1142025 panic:?too?many?concurrent?operations?on?a?single?file?or?socket?(max?1048575)goroutine?1158408?[running]: internal/poll.(*fdMutex).rwlock(0xc0000ae060,?0x0)/usr/local/go/src/internal/poll/fd_mutex.go:147?+0x11b internal/poll.(*FD).writeLock(...)/usr/local/go/src/internal/poll/fd_mutex.go:239 internal/poll.(*FD).Write(0xc0000ae060,?{0xc12cadf690,?0x8,?0x8})/usr/local/go/src/internal/poll/fd_unix.go:262?+0x72 os.(*File).write(...)/usr/local/go/src/os/file_posix.go:49 os.(*File).Write(0xc0000ac008,?{0xc12cadf690,?0x1,?0xc12ea62f50})/usr/local/go/src/os/file.go:176?+0x65 fmt.Fprintln({0x10c00e0,?0xc0000ac008},?{0xc12ea62f90,?0x1,?0x1})/usr/local/go/src/fmt/print.go:265?+0x75 fmt.Println(...)/usr/local/go/src/fmt/print.go:274 main.main.func1(0x0)/Users/dablelv/work/code/test/main.go:16?+0x8f ...

    運行的結果是程序直接崩潰了,關鍵的報錯信息是:

    panic:?too?many?concurrent?operations?on?a?single?file?or?socket?(max?1048575)

    對單個 file/socket 的并發操作個數超過了系統上限,這個報錯是 fmt.Printf 函數引起的,fmt.Printf 將格式化后的字符串打印到屏幕,即標準輸出。在 Linux 系統中,標準輸出也可以視為文件,內核(Kernel)利用文件描述符(File Descriptor)來訪問文件,標準輸出的文件描述符為 1,錯誤輸出文件描述符為 2,標準輸入的文件描述符為 0。

    簡而言之,系統的資源被耗盡了。

    那如果我們將 fmt.Printf 這行代碼去掉呢?那程序很可能會因為內存不足而崩潰。這一點更好理解,每個協程至少需要消耗 2KB 的空間,那么假設計算機的內存是 4GB,那么至多允許 4GB/2KB = 1M 個協程同時存在。那如果協程中還存在著其他需要分配內存的操作,那么允許并發執行的協程將會數量級地減少。

    2.1.2 協程的代價

    前面的例子過于極端,一般情況下程序也不會無限開辟協程,旨在說明協程數量是有限制的,不能無限開辟。

    如果我們開辟很多協程,但不會導致程序崩潰,可以嗎?如果真要這么做的話,我們應該清楚地知道,協程雖然輕量,但仍有開銷。

    Go 的開銷主要是三個方面:創建(占用內存)、調度(增加調度器負擔)和刪除(增加 GC 壓力)。

    • 內存開銷

    空間上,一個 Go 程占用約 2K 的內存,在源碼 src/runtime/runtime2.go里面,我們可以找到 Go 程的結構定義type g struct。

    • 調度開銷

    時間上,協程調度也會有 CPU 開銷。我們可以利用runntime.Gosched()讓當前協程主動讓出 CPU 去執行另外一個協程,下面看一下協程之間切換的耗時。

    const?NUM?=?10000func?cal()?{for?i?:=?0;?i?<?NUM;?i++?{runtime.Gosched()} }func?main()?{//?只設置一個?Processorruntime.GOMAXPROCS(1)start?:=?time.Now().UnixNano()go?cal()for?i?:=?0;?i?<?NUM;?i++?{runtime.Gosched()}end?:=?time.Now().UnixNano()fmt.Printf("total?%vns?per?%vns",?end-start,?(end-start)/NUM) }

    運行輸出:

    total?997200ns?per?99ns

    可見一次協程的切換,耗時大概在 100ns,相對于線程的微秒級耗時切換,性能表現非常優秀,但是仍有開銷。

    • GC 開銷 創建 Go 程到運行結束,占用的內存資源是需要由 GC 來回收,如果無休止地創建大量 Go 程后,勢必會造成對 GC 的壓力。

    package?mainimport?("fmt""runtime""runtime/debug""sync""time" )func?createLargeNumGoroutine(num?int,?wg?*sync.WaitGroup)?{wg.Add(num)for?i?:=?0;?i?<?num;?i++?{go?func()?{defer?wg.Done()}()} }func?main()?{//?只設置一個?Processor?保證?Go?程串行執行runtime.GOMAXPROCS(1)//?關閉GC改為手動執行debug.SetGCPercent(-1)var?wg?sync.WaitGroupcreateLargeNumGoroutine(1000,?&wg)wg.Wait()t?:=?time.Now()runtime.GC()?//?手動GCcost?:=?time.Since(t)fmt.Printf("GC?cost?%v?when?goroutine?num?is?%v\n",?cost,?1000)createLargeNumGoroutine(10000,?&wg)wg.Wait()t?=?time.Now()runtime.GC()?//?手動GCcost?=?time.Since(t)fmt.Printf("GC?cost?%v?when?goroutine?num?is?%v\n",?cost,?10000)createLargeNumGoroutine(100000,?&wg)wg.Wait()t?=?time.Now()runtime.GC()?//?手動GCcost?=?time.Since(t)fmt.Printf("GC?cost?%v?when?goroutine?num?is?%v\n",?cost,?100000) }

    運行輸出:

    GC?cost?0s?when?goroutine?num?is?1000 GC?cost?2.0027ms?when?goroutine?num?is?10000 GC?cost?30.9523ms?when?goroutine?num?is?100000

    當創建的 Go 程數量越多,GC 耗時越大。

    上面的分析目的是為了盡可能地量化 Goroutine 的開銷。雖然官方宣稱用 Golang 寫并發程序的時候隨便起個成千上萬的 Goroutine 毫無壓力,但當我們起十萬、百萬甚至千萬個 Goroutine 呢?Goroutine 輕量的開銷將被放大。

    2.2 限制協程數量

    系統地資源是有限,協程是有代價的,為了保護程序,提高性能,我們應主動限制并發的協程數量。

    可以利用信道 channel 的緩沖區大小來實現。

    func?main()?{var?wg?sync.WaitGroupch?:=?make(chan?struct{},?3)for?i?:=?0;?i?<?10;?i++?{ch?<-?struct{}{}wg.Add(1)go?func(i?int)?{defer?wg.Done()log.Println(i)time.Sleep(time.Second)<-ch}(i)}wg.Wait() }

    上例中創建了緩沖區大小為 3 的 channel,在沒有被接收的情況下,至多發送 3 個消息則被阻塞。開啟協程前,調用ch <- struct{}{},若緩存區滿,則阻塞。協程任務結束,調用 <-ch 釋放緩沖區。

    sync.WaitGroup 并不是必須的,例如 Http 服務,每個請求天然是并發的,此時使用 channel 控制并發處理的任務數量,就不需要 sync.WaitGroup。

    運行結果如下:

    2022/03/06?20:37:02?0 2022/03/06?20:37:02?2 2022/03/06?20:37:02?1 2022/03/06?20:37:03?3 2022/03/06?20:37:03?4 2022/03/06?20:37:03?5 2022/03/06?20:37:04?6 2022/03/06?20:37:04?7 2022/03/06?20:37:04?8 2022/03/06?20:37:05?9

    從日志中可以很容易看到,每秒鐘只并發執行了 3 個任務,達到了協程并發控制的目的。

    2.3 協程池化

    上面的例子只是簡單地限制了協程開辟的數量。在此基礎之上,基于對象復用的思想,我們可以重復利用已開辟的協程,避免協程的重復創建銷毀,達到池化的效果。

    協程池化,我們可以自己寫一個協程池,但不推薦這么做。因為已經有成熟的開源庫可供使用,無需再重復造輪子。目前有很多第三方庫實現了協程池,可以很方便地用來控制協程的并發數量,比較受歡迎的有:

    • Jeffail/tunny

    • panjf2000/ants

    下面以 panjf2000/ants 為例,簡單介紹其使用。

    ants 是一個簡單易用的高性能 Goroutine 池,實現了對大規模 Goroutine 的調度管理和復用,允許使用者在開發并發程序的時候限制 Goroutine 數量,復用協程,達到更高效執行任務的效果。

    package?mainimport?("fmt""time""github.com/panjf2000/ants" )func?main()?{//?Use?the?common?poolfor?i?:=?0;?i?<?10;?i++?{i?:=?iants.Submit(func()?{fmt.Println(i)})}time.Sleep(time.Second) }

    使用 ants,我們簡單地使用其默認的協程池,直接將任務提交并發執行。默認協程池的缺省容量 math.MaxInt32。

    如果自定義協程池容量大小,可以調用 NewPool 方法來實例化具有給定容量的池,如下所示:

    //?Set?10000?the?size?of?goroutine?pool p,?_?:=?ants.NewPool(10000)
    2.4 小結

    Golang 為并發而生。Goroutine 是由 Go 運行時管理的輕量級線程,通過它我們可以輕松實現并發編程。Go 雖然輕量,但天下沒有免費的午餐,無休止地開辟大量 Go 程勢必會帶來性能影響,甚至程序崩潰。所以,我們應盡可能的控制協程數量,如果有需要,請復用它。

    3.使用 sync.Once 避免重復執行

    3.1 簡介

    sync.Once 是 Go 標準庫提供的使函數只執行一次的實現,常應用于單例模式,例如初始化配置、保持數據庫連接等。作用與 init 函數類似,但有區別。

    • init 函數是當所在的 package 首次被加載時執行,若遲遲未被使用,則既浪費了內存,又延長了程序加載時間。

    • sync.Once 可以在代碼的任意位置初始化和調用,因此可以延遲到使用時再執行,并發場景下是線程安全的。

    在多數情況下,sync.Once 被用于控制變量的初始化,這個變量的讀寫滿足如下三個條件:

    • 當且僅當第一次訪問某個變量時,進行初始化(寫);

    • 變量初始化過程中,所有讀都被阻塞,直到初始化完成;

    • 變量僅初始化一次,初始化完成后駐留在內存里。

    3.2 原理

    sync.Once 用來保證函數只執行一次。要達到這個效果,需要做到兩點:

    • 計數器,統計函數執行次數;

    • 線程安全,保障在多 Go 程的情況下,函數仍然只執行一次,比如鎖。

    3.2.1 源碼

    下面看一下 sync.Once 結構,其有兩個變量。使用 done 統計函數執行次數,使用鎖 m 實現線程安全。果不其然,和上面的猜想一致。

    //?Once?is?an?object?that?will?perform?exactly?one?action. // //?A?Once?must?not?be?copied?after?first?use. type?Once?struct?{//?done?indicates?whether?the?action?has?been?performed.//?It?is?first?in?the?struct?because?it?is?used?in?the?hot?path.//?The?hot?path?is?inlined?at?every?call?site.//?Placing?done?first?allows?more?compact?instructions?on?some?architectures?(amd64/386),//?and?fewer?instructions?(to?calculate?offset)?on?other?architectures.done?uint32m????Mutex }

    sync.Once 僅提供了一個導出方法 Do(),參數 f 是只會被執行一次的函數,一般為對象初始化函數。

    //?go?version?go1.17?darwin/amd64//?Do?calls?the?function?f?if?and?only?if?Do?is?being?called?for?the //?first?time?for?this?instance?of?Once.?In?other?words,?given //??var?once?Once //?if?once.Do(f)?is?called?multiple?times,?only?the?first?call?will?invoke?f, //?even?if?f?has?a?different?value?in?each?invocation.?A?new?instance?of //?Once?is?required?for?each?function?to?execute. // //?Do?is?intended?for?initialization?that?must?be?run?exactly?once.?Since?f //?is?niladic,?it?may?be?necessary?to?use?a?function?literal?to?capture?the //?arguments?to?a?function?to?be?invoked?by?Do: //??config.once.Do(func()?{?config.init(filename)?}) // //?Because?no?call?to?Do?returns?until?the?one?call?to?f?returns,?if?f?causes //?Do?to?be?called,?it?will?deadlock. // //?If?f?panics,?Do?considers?it?to?have?returned;?future?calls?of?Do?return //?without?calling?f. // func?(o?*Once)?Do(f?func())?{//?Note:?Here?is?an?incorrect?implementation?of?Do:////?if?atomic.CompareAndSwapUint32(&o.done,?0,?1)?{//??f()//?}////?Do?guarantees?that?when?it?returns,?f?has?finished.//?This?implementation?would?not?implement?that?guarantee://?given?two?simultaneous?calls,?the?winner?of?the?cas?would//?call?f,?and?the?second?would?return?immediately,?without//?waiting?for?the?first's?call?to?f?to?complete.//?This?is?why?the?slow?path?falls?back?to?a?mutex,?and?why//?the?atomic.StoreUint32?must?be?delayed?until?after?f?returns.if?atomic.LoadUint32(&o.done)?==?0?{//?Outlined?slow-path?to?allow?inlining?of?the?fast-path.o.doSlow(f)} }func?(o?*Once)?doSlow(f?func())?{o.m.Lock()defer?o.m.Unlock()if?o.done?==?0?{defer?atomic.StoreUint32(&o.done,?1)f()} }

    拋去大段的注釋,可以看到 sync.Once 實現非常簡潔。Do() 函數中,通過對成員變量 done 的判斷,來決定是否執行傳入的任務函數。執行任務函數前,通過鎖保證任務函數的執行和 done 的修改是一個互斥操作。在執行任務函數前,對 done 做一個二次判斷,來保證任務函數只會被執行一次,done 只會被修改一次。

    3.2.2 ?done 為什么是第一個字段

    從字段 done 前有一段注釋,說明了done 為什么是第一個字段。

    done 在熱路徑中,done 放在第一個字段,能夠減少 CPU 指令,也就是說,這樣做能夠提升性能。

    熱路徑(hot path)是程序非常頻繁執行的一系列指令,sync.Once 絕大部分場景都會訪問 o.done,在熱路徑上是比較好理解的。如果 hot path 編譯后的機器碼指令更少,更直接,必然是能夠提升性能的。

    為什么放在第一個字段就能夠減少指令呢?因為結構體第一個字段的地址和結構體的指針是相同的,如果是第一個字段,直接對結構體的指針解引用即可。如果是其他的字段,除了結構體指針外,還需要計算與第一個值的偏移(calculate offset)。在機器碼中,偏移量是隨指令傳遞的附加值,CPU 需要做一次偏移值與指針的加法運算,才能獲取要訪問的值的地址。因為,訪問第一個字段的機器代碼更緊湊,速度更快。

    參考 What does “hot path” mean in the context of sync.Once? - StackOverflow

    3.3 性能差異

    我們以一個簡單示例,來說明使用 sync.Once 保證函數只會被執行一次和多次執行,二者的性能差異。

    考慮一個簡單的場景,函數 ReadConfig 需要讀取環境變量,并轉換為對應的配置。環境變量在程序執行前已經確定,執行過程中不會發生改變。ReadConfig 可能會被多個協程并發調用,為了提升性能(減少執行時間和內存占用),使用 sync.Once 是一個比較好的方式。

    type?Config?struct?{GoRoot?stringGoPath?string }var?(once???sync.Onceconfig?*Config )func?ReadConfigWithOnce()?*Config?{once.Do(func()?{config?=?&Config{GoRoot:?os.Getenv("GOROOT"),GoPath:?os.Getenv("GOPATH"),}})return?config }func?ReadConfig()?*Config?{return?&Config{GoRoot:?os.Getenv("GOROOT"),GoPath:?os.Getenv("GOPATH"),} }

    我們看下二者的性能差異。

    func?BenchmarkReadConfigWithOnce(b?*testing.B)?{for?i?:=?0;?i?<?b.N;?i++?{_?=?ReadConfigWithOnce()} }func?BenchmarkReadConfig(b?*testing.B)?{for?i?:=?0;?i?<?b.N;?i++?{_?=?ReadConfig()} }

    執行測試結果如下:

    go?test?-bench=.?main/once goos:?darwin goarch:?amd64 pkg:?main/once cpu:?Intel(R)?Core(TM)?i7-9750H?CPU?@?2.60GHz BenchmarkReadConfigWithOnce-12??????????670438965????????????????1.732?ns/op BenchmarkReadConfig-12??????????????????13339154????????????????87.46?ns/op PASS ok??????main/once???????3.006s

    sync.Once 中保證了 Config 初始化函數僅執行了一次,避免了多次重復初始化,在并發環境下很有用。

    4.使用 sync.Cond 通知協程

    4.1 簡介

    sync.Cond 是基于互斥鎖/讀寫鎖實現的條件變量,用來協調想要訪問共享資源的那些 Goroutine,當共享資源的狀態發生變化的時候,sync.Cond 可以用來通知等待條件發生而阻塞的 Goroutine。

    sync.Cond 基于互斥鎖/讀寫鎖,它和互斥鎖的區別是什么呢?

    互斥鎖 sync.Mutex 通常用來保護共享的臨界資源,條件變量 sync.Cond 用來協調想要訪問共享資源的 Goroutine。當共享資源的狀態發生變化時,sync.Cond 可以用來通知被阻塞的 Goroutine。

    4.2 使用場景

    sync.Cond 經常用在多個 Goroutine 等待,一個 Goroutine 通知(事件發生)的場景。如果是一個通知,一個等待,使用互斥鎖或 channel 就能搞定了。

    我們想象一個非常簡單的場景:

    有一個協程在異步地接收數據,剩下的多個協程必須等待這個協程接收完數據,才能讀取到正確的數據。在這種情況下,如果單純使用 chan 或互斥鎖,那么只能有一個協程可以等待,并讀取到數據,沒辦法通知其他的協程也讀取數據。

    這個時候,就需要有個全局的變量來標志第一個協程數據是否接受完畢,剩下的協程,反復檢查該變量的值,直到滿足要求。或者創建多個 channel,每個協程阻塞在一個 channel 上,由接收數據的協程在數據接收完畢后,逐個通知??傊?#xff0c;需要額外的復雜度來完成這件事。

    Go 語言在標準庫 sync 中內置一個 sync.Cond 用來解決這類問題。

    4.3 原理

    sync.Cond 內部維護了一個等待隊列,隊列中存放的是所有在等待這個 sync.Cond 的 Go 程,即保存了一個通知列表。sync.Cond 可以用來喚醒一個或所有因等待條件變量而阻塞的 Go 程,以此來實現多個 Go 程間的同步。

    sync.Cond 的定義如下:

    //?Cond?implements?a?condition?variable,?a?rendezvous?point //?for?goroutines?waiting?for?or?announcing?the?occurrence //?of?an?event. // //?Each?Cond?has?an?associated?Locker?L?(often?a?*Mutex?or?*RWMutex), //?which?must?be?held?when?changing?the?condition?and //?when?calling?the?Wait?method. // //?A?Cond?must?not?be?copied?after?first?use. type?Cond?struct?{noCopy?noCopy//?L?is?held?while?observing?or?changing?the?conditionL?Lockernotify??notifyListchecker?copyChecker }

    每個 Cond 實例都會關聯一個鎖 L(互斥鎖 *Mutex,或讀寫鎖 *RWMutex),當修改條件或者調用 Wait 方法時,必須加鎖。

    sync.Cond 的四個成員函數定義如下:

    //?NewCond?returns?a?new?Cond?with?Locker?l. func?NewCond(l?Locker)?*Cond?{return?&Cond{L:?l} }

    NewCond 創建 Cond 實例時,需要關聯一個鎖。

    //?Wait?atomically?unlocks?c.L?and?suspends?execution //?of?the?calling?goroutine.?After?later?resuming?execution, //?Wait?locks?c.L?before?returning.?Unlike?in?other?systems, //?Wait?cannot?return?unless?awoken?by?Broadcast?or?Signal. // //?Because?c.L?is?not?locked?when?Wait?first?resumes,?the?caller //?typically?cannot?assume?that?the?condition?is?true?when //?Wait?returns.?Instead,?the?caller?should?Wait?in?a?loop: // //????c.L.Lock() //????for?!condition()?{ //????????c.Wait() //????} //????...?make?use?of?condition?... //????c.L.Unlock() // func?(c?*Cond)?Wait()?{c.checker.check()t?:=?runtime_notifyListAdd(&c.notify)c.L.Unlock()runtime_notifyListWait(&c.notify,?t)c.L.Lock() }

    Wait 用于阻塞調用者,等待通知。調用 Wait 會自動釋放鎖 c.L,并掛起調用者所在的 goroutine。如果其他協程調用了 Signal 或 Broadcast 喚醒了該協程,那么 Wait 方法在結束阻塞時,會重新給 c.L 加鎖,并且繼續執行 Wait 后面的代碼。

    對條件的檢查,使用了 for !condition() 而非 if,是因為當前協程被喚醒時,條件不一定符合要求,需要再次 Wait 等待下次被喚醒。為了保險起,使用 for 能夠確保條件符合要求后,再執行后續的代碼。

    //?Signal?wakes?one?goroutine?waiting?on?c,?if?there?is?any. // //?It?is?allowed?but?not?required?for?the?caller?to?hold?c.L //?during?the?call. func?(c?*Cond)?Signal()?{c.checker.check()runtime_notifyListNotifyOne(&c.notify) }//?Broadcast?wakes?all?goroutines?waiting?on?c. // //?It?is?allowed?but?not?required?for?the?caller?to?hold?c.L //?during?the?call. func?(c?*Cond)?Broadcast()?{c.checker.check()runtime_notifyListNotifyAll(&c.notify) }

    Signal 只喚醒任意 1 個等待條件變量 c 的 goroutine,無需鎖保護。Broadcast 喚醒所有等待條件變量 c 的 goroutine,無需鎖保護。

    4.4 使用示例

    我們實現一個簡單的例子,三個協程調用 Wait() 等待,另一個協程調用 Broadcast() 喚醒所有等待的協程。

    var?done?=?falsefunc?read(name?string,?c?*sync.Cond)?{c.L.Lock()for?!done?{c.Wait()}log.Println(name,?"starts?reading")c.L.Unlock() }func?write(name?string,?c?*sync.Cond)?{log.Println(name,?"starts?writing")time.Sleep(time.Second)done?=?truelog.Println(name,?"wakes?all")c.Broadcast() }func?main()?{cond?:=?sync.NewCond(&sync.Mutex{})go?read("reader1",?cond)go?read("reader2",?cond)go?read("reader3",?cond)write("writer",?cond)time.Sleep(time.Second?*?3) }
    • done 即多個 Goroutine 阻塞等待的條件。

    • read() 調用 Wait() 等待通知,直到 done 為 true。

    • write() 接收數據,接收完成后,將 done 置為 true,調用 Broadcast() 通知所有等待的協程。

    • write() 中的暫停了 1s,一方面是模擬耗時,另一方面是確保前面的 3 個 read 協程都執行到 Wait(),處于等待狀態。main 函數最后暫停了 3s,確保所有操作執行完畢。

    運行輸出:

    go?run?main.go 2022/03/07?17:20:09?writer?starts?writing 2022/03/07?17:20:10?writer?wakes?all 2022/03/07?17:20:10?reader3?starts?reading 2022/03/07?17:20:10?reader1?starts?reading 2022/03/07?17:20:10?reader2?starts?reading

    更多關于 sync.Cond 的討論可參考 How to correctly use sync.Cond? - StackOverflow。

    4.5 注意事項
    • sync.Cond 不能被復制

    sync.Cond 不能被復制的原因,并不是因為其內部嵌套了 Locker。因為 NewCond 時傳入的 Mutex/RWMutex 指針,對于 Mutex 指針復制是沒有問題的。

    主要原因是 sync.Cond 內部是維護著一個 Goroutine 通知隊列 notifyList。如果這個隊列被復制的話,那么就在并發場景下導致不同 Goroutine 之間操作的 notifyList.wait、notifyList.notify 并不是同一個,這會導致出現有些 Goroutine 會一直阻塞。

    • 喚醒順序

    從等待隊列中按照順序喚醒,先進入等待隊列,先被喚醒。

    • 調用 Wait() 前要加鎖

    調用 Wait() 函數前,需要先獲得條件變量的成員鎖,原因是需要互斥地變更條件變量的等待隊列。在 Wait() 返回前,會重新上鎖。

    參考文獻

    • github.com/uber-go/guide

    • go-proverbs

    • github/dgryski/go-perfbook

    • High Performance Go Workshop - Dave Cheney

    • atomic 的原理與使用場景

    • 極客兔兔.Go 語言高性能編程

    • 深度解密Go 語言之sync.Pool - Stefno - 博客園

    最近好文:

    在鵝廠工作1到11年的程序媛

    技術她力量,鵝廠女博士的尋“豹”之旅

    微信全文搜索技術優化

    總結

    以上是生活随笔為你收集整理的Go 高性能编程技法的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    中文字幕+乱码+中文字幕一区 | 中文久久乱码一区二区 | 十八禁真人啪啪免费网站 | 欧美三级不卡在线观看 | 国产精品鲁鲁鲁 | 少妇性荡欲午夜性开放视频剧场 | 麻豆果冻传媒2021精品传媒一区下载 | 欧美自拍另类欧美综合图片区 | 精品无码国产一区二区三区av | 国产精品人妻一区二区三区四 | 国产精品99久久精品爆乳 | 蜜桃无码一区二区三区 | 国产激情无码一区二区 | 亚洲の无码国产の无码影院 | 亚洲娇小与黑人巨大交 | 国产午夜福利100集发布 | 狠狠亚洲超碰狼人久久 | 久久人人爽人人爽人人片av高清 | 又大又硬又黄的免费视频 | 国产无遮挡又黄又爽免费视频 | 国产激情无码一区二区 | 激情人妻另类人妻伦 | 97资源共享在线视频 | 一二三四在线观看免费视频 | 国产在线无码精品电影网 | 国产精品第一国产精品 | 精品国产乱码久久久久乱码 | 国产偷自视频区视频 | 国产精品无码久久av | 国产精品亚洲五月天高清 | 日本乱人伦片中文三区 | 亚洲高清偷拍一区二区三区 | 欧美老妇与禽交 | 国精品人妻无码一区二区三区蜜柚 | 精品久久久久久人妻无码中文字幕 | 天堂亚洲2017在线观看 | 成人亚洲精品久久久久软件 | 伊人久久大香线蕉av一区二区 | 天天拍夜夜添久久精品大 | 色狠狠av一区二区三区 | 亚洲精品成人av在线 | 久久人人爽人人爽人人片av高清 | 亚洲熟熟妇xxxx | 国精产品一品二品国精品69xx | 无码人中文字幕 | 欧美黑人乱大交 | 亚洲第一网站男人都懂 | 玩弄人妻少妇500系列视频 | 国产午夜无码视频在线观看 | 日本欧美一区二区三区乱码 | 亚洲一区二区三区无码久久 | 精品久久久久香蕉网 | 最近中文2019字幕第二页 | 色综合久久久久综合一本到桃花网 | 少妇被黑人到高潮喷出白浆 | 激情人妻另类人妻伦 | 无套内谢的新婚少妇国语播放 | 18精品久久久无码午夜福利 | 无码一区二区三区在线 | 人妻互换免费中文字幕 | 无码人妻丰满熟妇区五十路百度 | 一本无码人妻在中文字幕免费 | 国产又爽又猛又粗的视频a片 | 成人一在线视频日韩国产 | 欧美 日韩 人妻 高清 中文 | 国产精品怡红院永久免费 | 亚洲娇小与黑人巨大交 | 奇米影视7777久久精品人人爽 | 国内精品久久毛片一区二区 | 天天躁夜夜躁狠狠是什么心态 | 性做久久久久久久久 | 人人澡人人透人人爽 | 久久视频在线观看精品 | 午夜时刻免费入口 | 国产免费无码一区二区视频 | 18黄暴禁片在线观看 | 水蜜桃色314在线观看 | 国内少妇偷人精品视频 | 国产精品美女久久久久av爽李琼 | 5858s亚洲色大成网站www | 人人妻人人澡人人爽人人精品浪潮 | 国产激情无码一区二区 | 亚洲另类伦春色综合小说 | 久久国内精品自在自线 | 中文字幕无码免费久久99 | 娇妻被黑人粗大高潮白浆 | 国产精品久久久久久久影院 | 日本熟妇乱子伦xxxx | 日韩精品久久久肉伦网站 | 国产成人无码专区 | 久久97精品久久久久久久不卡 | 国产做国产爱免费视频 | 亚洲精品成a人在线观看 | 久久国产自偷自偷免费一区调 | 人妻少妇精品无码专区二区 | 精品人妻中文字幕有码在线 | 无遮挡啪啪摇乳动态图 | 久久人人爽人人爽人人片av高清 | 亚洲区欧美区综合区自拍区 | 久久99精品久久久久久 | 国产亲子乱弄免费视频 | 久久综合九色综合97网 | 国产在线一区二区三区四区五区 | 国产av无码专区亚洲a∨毛片 | www一区二区www免费 | 欧美xxxxx精品 | 男人扒开女人内裤强吻桶进去 | 东京热男人av天堂 | 欧美日韩一区二区免费视频 | 国产人妻精品午夜福利免费 | 少妇无码av无码专区在线观看 | 国产色精品久久人妻 | 疯狂三人交性欧美 | 成人三级无码视频在线观看 | 国产精品久久久久久久9999 | 国产精品香蕉在线观看 | 国产精品久久久久7777 | 大乳丰满人妻中文字幕日本 | 少妇人妻大乳在线视频 | 国模大胆一区二区三区 | 18无码粉嫩小泬无套在线观看 | av无码不卡在线观看免费 | 国产亚洲人成在线播放 | 真人与拘做受免费视频 | 免费看少妇作爱视频 | 又大又硬又黄的免费视频 | 亚洲综合伊人久久大杳蕉 | 国产综合在线观看 | 国产色在线 | 国产 | 亚洲成a人片在线观看无码 | 成人免费无码大片a毛片 | 欧美喷潮久久久xxxxx | 狠狠色噜噜狠狠狠7777奇米 | 国产精品-区区久久久狼 | 久在线观看福利视频 | 色综合久久久无码网中文 | 理论片87福利理论电影 | 日本丰满护士爆乳xxxx | 中文字幕乱码亚洲无线三区 | 激情综合激情五月俺也去 | 色偷偷人人澡人人爽人人模 | 一本久道久久综合婷婷五月 | 鲁鲁鲁爽爽爽在线视频观看 | 黑人大群体交免费视频 | 无码av中文字幕免费放 | 国产极品美女高潮无套在线观看 | 国产在热线精品视频 | 亚洲欧美日韩成人高清在线一区 | 小泽玛莉亚一区二区视频在线 | 亚洲乱码国产乱码精品精 | 国产欧美亚洲精品a | 国产精品无码一区二区三区不卡 | 成熟人妻av无码专区 | 国产卡一卡二卡三 | 亚洲综合无码一区二区三区 | 欧美丰满少妇xxxx性 | a片免费视频在线观看 | 久久综合九色综合97网 | 高清无码午夜福利视频 | 亚洲熟妇色xxxxx欧美老妇y | 一本一道久久综合久久 | 成人无码视频免费播放 | 成人aaa片一区国产精品 | 人人妻人人藻人人爽欧美一区 | 国产精品99久久精品爆乳 | 亚洲精品国偷拍自产在线观看蜜桃 | 久久久久av无码免费网 | 九月婷婷人人澡人人添人人爽 | 久久人人爽人人爽人人片ⅴ | 久久天天躁狠狠躁夜夜免费观看 | 人人爽人人爽人人片av亚洲 | 97夜夜澡人人爽人人喊中国片 | 亚洲中文字幕无码一久久区 | 国产午夜福利亚洲第一 | 一本久道高清无码视频 | 国产片av国语在线观看 | 久久久久免费精品国产 | 国产精品久久久久影院嫩草 | 动漫av网站免费观看 | 欧美性猛交xxxx富婆 | 国产午夜手机精彩视频 | 久久zyz资源站无码中文动漫 | 久久综合九色综合欧美狠狠 | 国产乱人无码伦av在线a | 黑人大群体交免费视频 | 欧美日韩在线亚洲综合国产人 | 国产综合色产在线精品 | 蜜桃臀无码内射一区二区三区 | 久久97精品久久久久久久不卡 | 亚洲综合久久一区二区 | 亚洲国产一区二区三区在线观看 | 亚洲综合无码一区二区三区 | 精品人人妻人人澡人人爽人人 | 亚洲成a人片在线观看无码 | 日本精品高清一区二区 | 综合人妻久久一区二区精品 | 中文字幕av伊人av无码av | 国产精品成人av在线观看 | 成人免费视频在线观看 | 久久综合久久自在自线精品自 | 在线天堂新版最新版在线8 | 2020久久香蕉国产线看观看 | 玩弄人妻少妇500系列视频 | 日韩精品久久久肉伦网站 | 久久亚洲日韩精品一区二区三区 | 久久人人爽人人爽人人片ⅴ | 国产精品久久久久影院嫩草 | 麻豆国产人妻欲求不满 | 窝窝午夜理论片影院 | 亚洲精品久久久久久一区二区 | 亚洲精品欧美二区三区中文字幕 | 日本又色又爽又黄的a片18禁 | 亚洲欧美国产精品专区久久 | 亚洲中文字幕无码中字 | 在线观看国产午夜福利片 | 精品熟女少妇av免费观看 | 欧美乱妇无乱码大黄a片 | 麻豆国产人妻欲求不满 | 国精产品一品二品国精品69xx | 少妇一晚三次一区二区三区 | 久久亚洲国产成人精品性色 | 性史性农村dvd毛片 | 久久人妻内射无码一区三区 | 国产激情无码一区二区app | 国产精品人人爽人人做我的可爱 | 女高中生第一次破苞av | 亚洲成av人在线观看网址 | 国产精品高潮呻吟av久久4虎 | 无遮挡啪啪摇乳动态图 | 成人一在线视频日韩国产 | 国产人妖乱国产精品人妖 | 久久久久成人精品免费播放动漫 | 国产av人人夜夜澡人人爽麻豆 | 久久成人a毛片免费观看网站 | 一本色道久久综合亚洲精品不卡 | 欧洲熟妇色 欧美 | 欧美 日韩 亚洲 在线 | 水蜜桃色314在线观看 | 丁香花在线影院观看在线播放 | 色婷婷香蕉在线一区二区 | 性生交片免费无码看人 | 亚洲国产欧美在线成人 | 久久精品视频在线看15 | 亚洲综合色区中文字幕 | 女高中生第一次破苞av | 久久精品成人欧美大片 | 亚洲熟女一区二区三区 | 蜜桃无码一区二区三区 | 中文字幕亚洲情99在线 | 国产精品99爱免费视频 | 中文字幕无码免费久久9一区9 | 色 综合 欧美 亚洲 国产 | 乱码av麻豆丝袜熟女系列 | 国产精品理论片在线观看 | 97久久精品无码一区二区 | 亚洲 a v无 码免 费 成 人 a v | 人人妻人人澡人人爽人人精品 | 亚洲一区av无码专区在线观看 | 狠狠色噜噜狠狠狠7777奇米 | 亚洲人亚洲人成电影网站色 | 亚洲精品国产品国语在线观看 | 99久久亚洲精品无码毛片 | 熟女少妇在线视频播放 | 久久久久成人片免费观看蜜芽 | 中文久久乱码一区二区 | 亚洲一区二区三区含羞草 | 精品一区二区三区波多野结衣 | 国产av无码专区亚洲awww | 国产精品毛片一区二区 | 亚洲a无码综合a国产av中文 | 亚洲日本va午夜在线电影 | 欧美高清在线精品一区 | 精品乱子伦一区二区三区 | 国产成人综合色在线观看网站 | 人人澡人摸人人添 | 青青青爽视频在线观看 | 亚洲欧洲日本综合aⅴ在线 | 亚洲色无码一区二区三区 | 亚洲午夜久久久影院 | 欧美丰满老熟妇xxxxx性 | 无码国模国产在线观看 | 日本一区二区三区免费高清 | 久久人人爽人人人人片 | 久久99久久99精品中文字幕 | 国产精品久久久午夜夜伦鲁鲁 | 少妇性l交大片 | 国产激情艳情在线看视频 | 亚洲国产欧美日韩精品一区二区三区 | 激情综合激情五月俺也去 | 久久国产36精品色熟妇 | 九九久久精品国产免费看小说 | 无码任你躁久久久久久久 | 精品无码一区二区三区爱欲 | 老子影院午夜伦不卡 | 国产suv精品一区二区五 | 熟女少妇人妻中文字幕 | 国产人成高清在线视频99最全资源 | 人人妻人人澡人人爽精品欧美 | 夜夜高潮次次欢爽av女 | 亚洲成熟女人毛毛耸耸多 | 一个人看的www免费视频在线观看 | 精品国产aⅴ无码一区二区 | 成人精品一区二区三区中文字幕 | 国产精品久久久久7777 | 国产欧美精品一区二区三区 | 十八禁视频网站在线观看 | 天天拍夜夜添久久精品 | 国产人妻精品一区二区三区不卡 | 波多野结衣乳巨码无在线观看 | 国产精品高潮呻吟av久久4虎 | 亚洲欧洲日本综合aⅴ在线 | 强伦人妻一区二区三区视频18 | 无码毛片视频一区二区本码 | 六十路熟妇乱子伦 | 国产办公室秘书无码精品99 | 欧美黑人性暴力猛交喷水 | 国产熟妇另类久久久久 | 男人和女人高潮免费网站 | 亚洲一区二区三区无码久久 | 色诱久久久久综合网ywww | 亚洲中文字幕在线无码一区二区 | 日本熟妇人妻xxxxx人hd | 久久午夜无码鲁丝片秋霞 | 国产精品人人爽人人做我的可爱 | 少妇无码av无码专区在线观看 | 2020久久超碰国产精品最新 | 国产特级毛片aaaaaa高潮流水 | 99久久精品国产一区二区蜜芽 | 欧美性猛交xxxx富婆 | 亚洲欧洲日本综合aⅴ在线 | 亚洲一区二区三区偷拍女厕 | 99视频精品全部免费免费观看 | 天天做天天爱天天爽综合网 | 99久久久无码国产精品免费 | 国产黄在线观看免费观看不卡 | 奇米影视7777久久精品人人爽 | 亚洲爆乳精品无码一区二区三区 | 麻花豆传媒剧国产免费mv在线 | 久久99久久99精品中文字幕 | 97久久精品无码一区二区 | 少妇久久久久久人妻无码 | 国产精品毛片一区二区 | 高中生自慰www网站 | 亚洲国产精品无码久久久久高潮 | 国产热a欧美热a在线视频 | 国产性生交xxxxx无码 | 99久久久无码国产精品免费 | 色爱情人网站 | 极品嫩模高潮叫床 | 国产卡一卡二卡三 | 成人一区二区免费视频 | 精品国产福利一区二区 | 亚洲狠狠色丁香婷婷综合 | 国产尤物精品视频 | 男女下面进入的视频免费午夜 | 亚洲国产欧美日韩精品一区二区三区 | 国产激情无码一区二区 | 日产精品99久久久久久 | 大肉大捧一进一出视频出来呀 | 国产亚洲精品久久久闺蜜 | 国产精品久久久久久亚洲影视内衣 | 最新国产麻豆aⅴ精品无码 | 国产成人无码av在线影院 | 精品少妇爆乳无码av无码专区 | 国产三级久久久精品麻豆三级 | 亚洲日韩av一区二区三区四区 | 国内老熟妇对白xxxxhd | 欧美日本免费一区二区三区 | 国产在线一区二区三区四区五区 | 久久久久免费精品国产 | 乱人伦中文视频在线观看 | 在线精品亚洲一区二区 | 久久99精品久久久久久 | 无码人妻精品一区二区三区不卡 | 久久97精品久久久久久久不卡 | 亚洲精品综合五月久久小说 | 一个人看的视频www在线 | 国产成人无码a区在线观看视频app | 丝袜 中出 制服 人妻 美腿 | 国内精品人妻无码久久久影院 | 欧美猛少妇色xxxxx | 亚洲精品一区国产 | 一本色道久久综合狠狠躁 | 性欧美疯狂xxxxbbbb | 亚洲日本一区二区三区在线 | 国产av人人夜夜澡人人爽麻豆 | 亚洲欧美国产精品专区久久 | 国产又粗又硬又大爽黄老大爷视 | 亚洲а∨天堂久久精品2021 | 两性色午夜免费视频 | av在线亚洲欧洲日产一区二区 | 久久精品一区二区三区四区 | 鲁鲁鲁爽爽爽在线视频观看 | 欧美大屁股xxxxhd黑色 | 久久久久成人精品免费播放动漫 | 久久综合久久自在自线精品自 | 亚洲熟妇色xxxxx亚洲 | 日韩人妻无码中文字幕视频 | 久久五月精品中文字幕 | 一本久久伊人热热精品中文字幕 | 中文字幕 人妻熟女 | 亚拍精品一区二区三区探花 | 99久久精品日本一区二区免费 | 亚洲色www成人永久网址 | 欧美freesex黑人又粗又大 | 7777奇米四色成人眼影 | 丝袜足控一区二区三区 | 成人免费视频在线观看 | 无码精品国产va在线观看dvd | 久久久久久亚洲精品a片成人 | 99久久精品无码一区二区毛片 | aⅴ亚洲 日韩 色 图网站 播放 | 久久视频在线观看精品 | 国产精品99爱免费视频 | √天堂资源地址中文在线 | 无码一区二区三区在线观看 | 两性色午夜视频免费播放 | 无码人妻av免费一区二区三区 | 无码帝国www无码专区色综合 | 精品国偷自产在线 | 少妇性l交大片欧洲热妇乱xxx | 国产精品亚洲五月天高清 | 日韩精品a片一区二区三区妖精 | 色偷偷av老熟女 久久精品人妻少妇一区二区三区 | 无码人妻丰满熟妇区五十路百度 | 色偷偷人人澡人人爽人人模 | 成人av无码一区二区三区 | 日本大乳高潮视频在线观看 | 国产精品怡红院永久免费 | 在线а√天堂中文官网 | 中文无码精品a∨在线观看不卡 | 精品一区二区三区波多野结衣 | 日韩精品久久久肉伦网站 | 精品国产麻豆免费人成网站 | www国产亚洲精品久久网站 | 在线天堂新版最新版在线8 | 人妻夜夜爽天天爽三区 | 又大又硬又爽免费视频 | 欧美日韩一区二区三区自拍 | 亚洲国产成人a精品不卡在线 | 国产成人综合在线女婷五月99播放 | 好屌草这里只有精品 | 国产卡一卡二卡三 | 亚洲精品国产a久久久久久 | 夜夜高潮次次欢爽av女 | 国产人妻精品一区二区三区 | 久久精品女人天堂av免费观看 | 999久久久国产精品消防器材 | 国产无套粉嫩白浆在线 | 日韩av无码中文无码电影 | 麻豆国产人妻欲求不满 | 97久久国产亚洲精品超碰热 | 中文字幕无码免费久久99 | 人妻aⅴ无码一区二区三区 | 国产性生交xxxxx无码 | 国产亚洲精品久久久久久 | 天天做天天爱天天爽综合网 | 嫩b人妻精品一区二区三区 | 久久久久国色av免费观看性色 | 丁香啪啪综合成人亚洲 | 中文字幕无码免费久久99 | 乱人伦人妻中文字幕无码久久网 | 99麻豆久久久国产精品免费 | 中文字幕无码免费久久9一区9 | 99久久精品无码一区二区毛片 | 亚洲综合在线一区二区三区 | 国产97色在线 | 免 | 亚洲成av人片在线观看无码不卡 | 亚洲狠狠婷婷综合久久 | 国产无遮挡又黄又爽免费视频 | 国产人妖乱国产精品人妖 | 欧美激情内射喷水高潮 | 国产成人一区二区三区在线观看 | 免费乱码人妻系列无码专区 | 欧美日韩视频无码一区二区三 | 一本色道久久综合狠狠躁 | 红桃av一区二区三区在线无码av | 日日天干夜夜狠狠爱 | 久久精品女人的天堂av | 理论片87福利理论电影 | 野狼第一精品社区 | 一二三四在线观看免费视频 | 亚洲爆乳精品无码一区二区三区 | 欧美精品一区二区精品久久 | 精品人妻av区 | 亚洲国产精品美女久久久久 | 亚洲精品国产a久久久久久 | 高潮毛片无遮挡高清免费视频 | 国精品人妻无码一区二区三区蜜柚 | 无码人妻丰满熟妇区毛片18 | 国产精品手机免费 | 欧美野外疯狂做受xxxx高潮 | 亚洲大尺度无码无码专区 | 欧美乱妇无乱码大黄a片 | 又大又黄又粗又爽的免费视频 | 伊人久久大香线蕉亚洲 | 无码av最新清无码专区吞精 | 亚洲欧洲日本无在线码 | 免费无码av一区二区 | 亚洲成在人网站无码天堂 | 永久黄网站色视频免费直播 | 97精品国产97久久久久久免费 | 亚洲熟妇色xxxxx欧美老妇 | 人妻天天爽夜夜爽一区二区 | 奇米影视7777久久精品人人爽 | 奇米影视888欧美在线观看 | 美女毛片一区二区三区四区 | 丰满肥臀大屁股熟妇激情视频 | 人妻中文无码久热丝袜 | 青青久在线视频免费观看 | 国产特级毛片aaaaaa高潮流水 | 亚洲日韩av一区二区三区中文 | 亚洲日韩av一区二区三区四区 | 亚洲精品综合五月久久小说 | 久久精品人妻少妇一区二区三区 | 国产农村乱对白刺激视频 | 色综合天天综合狠狠爱 | 色婷婷av一区二区三区之红樱桃 | 亚洲午夜福利在线观看 | 久久亚洲中文字幕无码 | 国产精品久久久久久亚洲影视内衣 | 国语精品一区二区三区 | 国产精品美女久久久久av爽李琼 | 性生交大片免费看女人按摩摩 | 一区二区三区高清视频一 | 午夜丰满少妇性开放视频 | 熟女少妇在线视频播放 | 国产免费观看黄av片 | 精品久久久久久亚洲精品 | 精品久久久久久人妻无码中文字幕 | 九九热爱视频精品 | 人妻互换免费中文字幕 | 丰满少妇人妻久久久久久 | 国产午夜手机精彩视频 | 狠狠色丁香久久婷婷综合五月 | 精品久久久无码人妻字幂 | 国产97在线 | 亚洲 | 亚洲性无码av中文字幕 | 天天做天天爱天天爽综合网 | 久久久国产一区二区三区 | 国产精品久久久久7777 | 亚洲熟女一区二区三区 | 婷婷色婷婷开心五月四房播播 | 国产乱人伦偷精品视频 | 成人三级无码视频在线观看 | 国产在线无码精品电影网 | 国产香蕉尹人视频在线 | 国产欧美亚洲精品a | 97精品国产97久久久久久免费 | 国产精品-区区久久久狼 | 久久国内精品自在自线 | 又大又硬又爽免费视频 | 亚洲欧美国产精品久久 | 日韩精品乱码av一区二区 | 精品午夜福利在线观看 | 自拍偷自拍亚洲精品10p | av人摸人人人澡人人超碰下载 | 午夜福利一区二区三区在线观看 | 国产性猛交╳xxx乱大交 国产精品久久久久久无码 欧洲欧美人成视频在线 | 欧美日韩人成综合在线播放 | 东京热男人av天堂 | 久久精品国产一区二区三区肥胖 | 在线а√天堂中文官网 | 久久精品人妻少妇一区二区三区 | 狠狠色欧美亚洲狠狠色www | 成人无码精品1区2区3区免费看 | 久久久久久久人妻无码中文字幕爆 | 女人被爽到呻吟gif动态图视看 | 美女极度色诱视频国产 | 国产亚洲精品久久久闺蜜 | 青青久在线视频免费观看 | 婷婷五月综合缴情在线视频 | 久久亚洲中文字幕精品一区 | 日韩av无码中文无码电影 | 人人妻人人澡人人爽人人精品 | 7777奇米四色成人眼影 | av无码久久久久不卡免费网站 | 精品午夜福利在线观看 | 极品嫩模高潮叫床 | 久久99精品久久久久婷婷 | 一区二区传媒有限公司 | 乱人伦人妻中文字幕无码久久网 | 小泽玛莉亚一区二区视频在线 | 内射欧美老妇wbb | 国产口爆吞精在线视频 | 爆乳一区二区三区无码 | 国产在线一区二区三区四区五区 | 国产成人精品视频ⅴa片软件竹菊 | 国产精品久久福利网站 | 装睡被陌生人摸出水好爽 | 日韩人妻系列无码专区 | 亚洲 日韩 欧美 成人 在线观看 | 国产三级精品三级男人的天堂 | 国产免费观看黄av片 | 成人精品视频一区二区 | 亚洲精品成a人在线观看 | 国产猛烈高潮尖叫视频免费 | 久久精品国产精品国产精品污 | 宝宝好涨水快流出来免费视频 | 欧美人与善在线com | 国产熟妇另类久久久久 | 99精品无人区乱码1区2区3区 | 少妇的肉体aa片免费 | 色一情一乱一伦一视频免费看 | 国内精品人妻无码久久久影院 | 少妇被黑人到高潮喷出白浆 | 国产亚av手机在线观看 | 日日摸夜夜摸狠狠摸婷婷 | 中文精品无码中文字幕无码专区 | 国产成人亚洲综合无码 | 国产一区二区三区日韩精品 | 国产乱人伦偷精品视频 | 中文亚洲成a人片在线观看 | 骚片av蜜桃精品一区 | 网友自拍区视频精品 | 九一九色国产 | 中文无码精品a∨在线观看不卡 | 日本一区二区三区免费高清 | 日本丰满熟妇videos | 无遮挡啪啪摇乳动态图 | 中文字幕中文有码在线 | 人人妻人人澡人人爽欧美一区 | 国产精品久久久 | 欧美人与善在线com | 天堂а√在线地址中文在线 | 极品尤物被啪到呻吟喷水 | 亚洲 另类 在线 欧美 制服 | 麻豆人妻少妇精品无码专区 | 国内少妇偷人精品视频免费 | 久久久久成人片免费观看蜜芽 | 久久精品99久久香蕉国产色戒 | 精品一区二区不卡无码av | 国产99久久精品一区二区 | 又大又硬又爽免费视频 | 午夜福利电影 | 国产精品久久久久无码av色戒 | 久久99国产综合精品 | 中文字幕日产无线码一区 | 欧美三级不卡在线观看 | 99国产精品白浆在线观看免费 | 国产亚洲欧美在线专区 | 精品偷拍一区二区三区在线看 | 久久熟妇人妻午夜寂寞影院 | 2019nv天堂香蕉在线观看 | 色欲久久久天天天综合网精品 | 人妻夜夜爽天天爽三区 | 国产又爽又猛又粗的视频a片 | 中国女人内谢69xxxx | 成人无码影片精品久久久 | 国内少妇偷人精品视频免费 | 欧美黑人乱大交 | 麻豆果冻传媒2021精品传媒一区下载 | 初尝人妻少妇中文字幕 | 疯狂三人交性欧美 | 久久久久免费看成人影片 | 久久97精品久久久久久久不卡 | 福利一区二区三区视频在线观看 | 成人影院yy111111在线观看 | 少妇人妻大乳在线视频 | 亚洲 激情 小说 另类 欧美 | 天天综合网天天综合色 | 丰满人妻精品国产99aⅴ | 亚洲综合久久一区二区 | 久久国产精品_国产精品 | 夜精品a片一区二区三区无码白浆 | 天堂无码人妻精品一区二区三区 | 高潮毛片无遮挡高清免费视频 | 青青青爽视频在线观看 | 国产网红无码精品视频 | 中文精品无码中文字幕无码专区 | 欧美午夜特黄aaaaaa片 | 亚洲成av人在线观看网址 | 亚洲精品www久久久 | 久久精品国产一区二区三区 | 无码国内精品人妻少妇 | av人摸人人人澡人人超碰下载 | 成年美女黄网站色大免费全看 | 好屌草这里只有精品 | 色欲久久久天天天综合网精品 | 欧美日韩综合一区二区三区 | 国产熟妇高潮叫床视频播放 | 色五月五月丁香亚洲综合网 | 日韩精品无码免费一区二区三区 | 久久国产36精品色熟妇 | 99精品国产综合久久久久五月天 | 久精品国产欧美亚洲色aⅴ大片 | 亚洲熟悉妇女xxx妇女av | 亚洲精品一区二区三区婷婷月 | 亚洲娇小与黑人巨大交 | 1000部啪啪未满十八勿入下载 | 国产精品久久久久久无码 | 日韩少妇白浆无码系列 | 午夜不卡av免费 一本久久a久久精品vr综合 | 免费乱码人妻系列无码专区 | 精品久久8x国产免费观看 | 精品偷自拍另类在线观看 | 亚洲精品久久久久中文第一幕 | 久久久久免费精品国产 | 国产午夜无码视频在线观看 | 精品国产一区av天美传媒 | 无码成人精品区在线观看 | 国产亚洲精品久久久ai换 | 中文字幕精品av一区二区五区 | 呦交小u女精品视频 | 最新国产乱人伦偷精品免费网站 | 国产莉萝无码av在线播放 | 国产 精品 自在自线 | 国产另类ts人妖一区二区 | 扒开双腿疯狂进出爽爽爽视频 | 国产麻豆精品一区二区三区v视界 | 精品国产国产综合精品 | 中文字幕av无码一区二区三区电影 | 激情人妻另类人妻伦 | 粗大的内捧猛烈进出视频 | 搡女人真爽免费视频大全 | 少妇久久久久久人妻无码 | 久久久久成人精品免费播放动漫 | 色欲综合久久中文字幕网 | 国产成人综合色在线观看网站 | 国产高清av在线播放 | 色诱久久久久综合网ywww | 激情人妻另类人妻伦 | 国产无遮挡又黄又爽免费视频 | 日韩成人一区二区三区在线观看 | 久久无码中文字幕免费影院蜜桃 | 亚洲色大成网站www | 国产又爽又猛又粗的视频a片 | 亚洲狠狠婷婷综合久久 | 欧洲欧美人成视频在线 | 成人无码影片精品久久久 | 性欧美大战久久久久久久 | 国产人成高清在线视频99最全资源 | 久久精品中文字幕大胸 | 少妇人妻偷人精品无码视频 | 亚洲乱码日产精品bd | 97无码免费人妻超级碰碰夜夜 | 国产精品理论片在线观看 | 欧美变态另类xxxx | 日韩人妻少妇一区二区三区 | 18禁黄网站男男禁片免费观看 | 国产激情一区二区三区 | 国产成人人人97超碰超爽8 | 性欧美大战久久久久久久 | 蜜臀av无码人妻精品 | 国产综合色产在线精品 | 特大黑人娇小亚洲女 | 国产偷国产偷精品高清尤物 | 国产精品多人p群无码 | 欧美性生交xxxxx久久久 | 中文字幕日产无线码一区 | 无套内射视频囯产 | 日日干夜夜干 | 男女作爱免费网站 | 亚洲日韩乱码中文无码蜜桃臀网站 | 亚洲色无码一区二区三区 | 国产午夜无码视频在线观看 | 黑人巨大精品欧美一区二区 | 欧美午夜特黄aaaaaa片 | 国产熟妇高潮叫床视频播放 | 未满小14洗澡无码视频网站 | 最近免费中文字幕中文高清百度 | 亚洲精品中文字幕久久久久 | 国产偷抇久久精品a片69 | 国产成人精品视频ⅴa片软件竹菊 | 131美女爱做视频 | 国产 精品 自在自线 | 欧美人与牲动交xxxx | 国产免费久久久久久无码 | 欧美人与善在线com | 国产深夜福利视频在线 | 国产精品人人妻人人爽 | 76少妇精品导航 | 久久人人爽人人爽人人片av高清 | 人人超人人超碰超国产 | 青青草原综合久久大伊人精品 | 亚洲国产精品毛片av不卡在线 | 亚洲精品www久久久 | 三级4级全黄60分钟 | 秋霞成人午夜鲁丝一区二区三区 | 国产成人亚洲综合无码 | 最近免费中文字幕中文高清百度 | 国产卡一卡二卡三 | 久久99精品国产.久久久久 | 国产午夜精品一区二区三区嫩草 | 99久久人妻精品免费一区 | 无码av中文字幕免费放 | 久久精品无码一区二区三区 | 在线观看国产一区二区三区 | 99久久婷婷国产综合精品青草免费 | 人妻少妇被猛烈进入中文字幕 | 亚洲中文字幕无码一久久区 | 国产麻豆精品一区二区三区v视界 | 国产成人av免费观看 | 婷婷丁香五月天综合东京热 | 天天综合网天天综合色 | 国产偷自视频区视频 | 夜夜影院未满十八勿进 | 好男人www社区 | 日韩精品a片一区二区三区妖精 | 欧美三级不卡在线观看 | www国产亚洲精品久久网站 | 色一情一乱一伦 | av无码不卡在线观看免费 | 超碰97人人做人人爱少妇 | 国产97人人超碰caoprom | 国产精品沙发午睡系列 | 麻豆蜜桃av蜜臀av色欲av | 国产黄在线观看免费观看不卡 | 一本加勒比波多野结衣 | 国产精品久免费的黄网站 | 国产肉丝袜在线观看 | 国产一区二区三区影院 | 无码人妻av免费一区二区三区 | 午夜福利一区二区三区在线观看 | 久在线观看福利视频 | 国产乡下妇女做爰 | 亚洲码国产精品高潮在线 | 精品欧洲av无码一区二区三区 | 亚洲国产一区二区三区在线观看 | 久久人人爽人人人人片 | 色妞www精品免费视频 | 扒开双腿疯狂进出爽爽爽视频 | 18禁黄网站男男禁片免费观看 | 日产国产精品亚洲系列 | 亚洲爆乳精品无码一区二区三区 | 久久精品一区二区三区四区 | 欧美人与禽猛交狂配 | 中文字幕无码日韩专区 | 国产一区二区三区影院 | aⅴ亚洲 日韩 色 图网站 播放 | 久久99精品久久久久婷婷 | 99久久久无码国产精品免费 | 六月丁香婷婷色狠狠久久 | 又黄又爽又色的视频 | 久久97精品久久久久久久不卡 | 久久精品国产精品国产精品污 | 国产精品久久国产三级国 | 久久99国产综合精品 | 国产办公室秘书无码精品99 | 国产精品二区一区二区aⅴ污介绍 | 97无码免费人妻超级碰碰夜夜 | 美女扒开屁股让男人桶 | 98国产精品综合一区二区三区 | 国产肉丝袜在线观看 | 亚洲国产精品一区二区美利坚 | 色一情一乱一伦 | 婷婷五月综合激情中文字幕 | 波多野42部无码喷潮在线 | 亚洲经典千人经典日产 | 国产另类ts人妖一区二区 | 日日摸日日碰夜夜爽av | 国産精品久久久久久久 | 亚洲成av人影院在线观看 | 国产精品二区一区二区aⅴ污介绍 | 日本大乳高潮视频在线观看 | 又大又黄又粗又爽的免费视频 | 亚洲热妇无码av在线播放 | 97久久国产亚洲精品超碰热 | 奇米影视888欧美在线观看 | 久久久久久久女国产乱让韩 | 极品尤物被啪到呻吟喷水 | 欧美午夜特黄aaaaaa片 | 国产电影无码午夜在线播放 | 88国产精品欧美一区二区三区 | 国产又爽又黄又刺激的视频 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 无码精品人妻一区二区三区av | 亚洲天堂2017无码中文 | 在线播放免费人成毛片乱码 | 亚洲大尺度无码无码专区 | 美女扒开屁股让男人桶 | 欧美老妇交乱视频在线观看 | 思思久久99热只有频精品66 | 美女毛片一区二区三区四区 | 熟妇人妻无乱码中文字幕 | 无遮挡国产高潮视频免费观看 | 福利一区二区三区视频在线观看 | 一本色道婷婷久久欧美 | 日本又色又爽又黄的a片18禁 | 久久久久免费精品国产 | 99国产精品白浆在线观看免费 | 午夜精品久久久内射近拍高清 | 久久zyz资源站无码中文动漫 | 国产精品内射视频免费 | 久久99精品国产麻豆蜜芽 | 国产一精品一av一免费 | 久久午夜无码鲁丝片秋霞 | 久久综合给合久久狠狠狠97色 | 日本大乳高潮视频在线观看 | 国产激情无码一区二区 | 装睡被陌生人摸出水好爽 | 野外少妇愉情中文字幕 | 久久久久久久久888 | 老熟女重囗味hdxx69 | 九月婷婷人人澡人人添人人爽 | 无套内谢的新婚少妇国语播放 | 久久国产精品_国产精品 | 欧美日韩人成综合在线播放 | 免费观看激色视频网站 | 国产成人精品视频ⅴa片软件竹菊 | 丰满少妇熟乱xxxxx视频 | 亚洲高清偷拍一区二区三区 | 精品一区二区不卡无码av | 成人无码影片精品久久久 | 国产麻豆精品精东影业av网站 | 国产真实夫妇视频 | 好男人www社区 | 国产又爽又猛又粗的视频a片 | 国产内射老熟女aaaa | 草草网站影院白丝内射 | 中文久久乱码一区二区 | 欧美日本免费一区二区三区 | 图片区 小说区 区 亚洲五月 | 国产精品久久久久影院嫩草 | 亚洲熟妇色xxxxx欧美老妇y | 人妻插b视频一区二区三区 | 国产人妻精品一区二区三区 | а√资源新版在线天堂 | 国模大胆一区二区三区 | 强辱丰满人妻hd中文字幕 | 日韩欧美中文字幕在线三区 | 一二三四在线观看免费视频 | 九月婷婷人人澡人人添人人爽 | 欧美 日韩 亚洲 在线 | 欧美一区二区三区视频在线观看 | 亚洲国产精品无码一区二区三区 | 欧美激情内射喷水高潮 | 丝袜 中出 制服 人妻 美腿 | 中文字幕乱码亚洲无线三区 | 国产亚洲精品久久久久久大师 | 天天躁夜夜躁狠狠是什么心态 | 国产三级久久久精品麻豆三级 | 黑森林福利视频导航 | 一本色道婷婷久久欧美 | 亚洲欧洲中文日韩av乱码 | 日本大乳高潮视频在线观看 | 一区二区三区高清视频一 | 亚洲s色大片在线观看 | 国产乱人伦偷精品视频 | 亚洲国精产品一二二线 | 国产精品久久久久久无码 | 免费乱码人妻系列无码专区 | 欧美日韩精品 | 成人av无码一区二区三区 | 伊人久久大香线蕉午夜 | 国产成人人人97超碰超爽8 | 99精品视频在线观看免费 | 无码精品国产va在线观看dvd | 中文字幕乱码亚洲无线三区 | 久久久久久久久蜜桃 | 一本大道伊人av久久综合 | 亚洲午夜久久久影院 | 全球成人中文在线 | 全球成人中文在线 | 波多野结衣av一区二区全免费观看 | 欧美日韩在线亚洲综合国产人 | 亚洲国产精品一区二区美利坚 | 又粗又大又硬毛片免费看 | 欧美人与禽zoz0性伦交 | √天堂中文官网8在线 | 精品无人国产偷自产在线 | 午夜精品久久久内射近拍高清 | 亚洲の无码国产の无码步美 | 成在人线av无码免费 | 亚洲阿v天堂在线 | 国产精品无码一区二区三区不卡 | 97无码免费人妻超级碰碰夜夜 | 精品久久久无码人妻字幂 | 亚洲日韩一区二区 | 久久精品国产大片免费观看 | 国产成人精品无码播放 | 亚洲成av人片天堂网无码】 | 成人精品一区二区三区中文字幕 | 欧美精品一区二区精品久久 | 狂野欧美性猛xxxx乱大交 | 樱花草在线社区www | 好屌草这里只有精品 | 99精品无人区乱码1区2区3区 | 精品夜夜澡人妻无码av蜜桃 | 久久久久99精品成人片 | 久久99精品久久久久婷婷 | 狂野欧美性猛xxxx乱大交 | 乱人伦中文视频在线观看 | 国产 精品 自在自线 | 久久久久久亚洲精品a片成人 | 国产一区二区不卡老阿姨 | 樱花草在线社区www | 无码帝国www无码专区色综合 | 色欲久久久天天天综合网精品 | 日韩欧美中文字幕公布 | 欧美日韩综合一区二区三区 | 久久综合给久久狠狠97色 | 亚洲成a人一区二区三区 | 免费人成在线观看网站 | 精品人人妻人人澡人人爽人人 | 亚洲人成影院在线无码按摩店 | 亚洲色欲色欲天天天www | 久久综合给合久久狠狠狠97色 | av在线亚洲欧洲日产一区二区 | 亚洲春色在线视频 | 国产偷抇久久精品a片69 | 特级做a爰片毛片免费69 | 久久www免费人成人片 | 思思久久99热只有频精品66 | 中文精品无码中文字幕无码专区 | 国产av人人夜夜澡人人爽麻豆 | 老熟女乱子伦 | 久久精品国产一区二区三区肥胖 | 老太婆性杂交欧美肥老太 | 日韩精品一区二区av在线 | 国产亚洲视频中文字幕97精品 | 亚洲午夜久久久影院 | 影音先锋中文字幕无码 | 东北女人啪啪对白 | 真人与拘做受免费视频一 | 国产综合在线观看 | 大屁股大乳丰满人妻 | 无码人妻精品一区二区三区下载 | 久久亚洲精品中文字幕无男同 | 全黄性性激高免费视频 | 中文精品无码中文字幕无码专区 | 麻豆国产人妻欲求不满谁演的 | 人人爽人人澡人人高潮 | 亚洲爆乳精品无码一区二区三区 | 亲嘴扒胸摸屁股激烈网站 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 成人无码精品1区2区3区免费看 | 欧美精品免费观看二区 | 久久久久久国产精品无码下载 | 丰满人妻精品国产99aⅴ | 成人免费视频在线观看 | 亚洲国产精品无码久久久久高潮 | 狠狠躁日日躁夜夜躁2020 | 无套内谢的新婚少妇国语播放 | 日本熟妇人妻xxxxx人hd | 波多野42部无码喷潮在线 | 色欲久久久天天天综合网精品 | 无码精品国产va在线观看dvd | 国产成人久久精品流白浆 | 精品欧美一区二区三区久久久 | 久久精品中文字幕一区 | 亚洲啪av永久无码精品放毛片 | 麻花豆传媒剧国产免费mv在线 | 日韩精品无码一区二区中文字幕 | 亚洲一区二区三区 | 色窝窝无码一区二区三区色欲 | 131美女爱做视频 | 亚洲精品一区国产 | 樱花草在线播放免费中文 | 丰满肥臀大屁股熟妇激情视频 | 亚洲理论电影在线观看 | 东京无码熟妇人妻av在线网址 | 天天综合网天天综合色 | 国产精品人人妻人人爽 | 亚洲日韩乱码中文无码蜜桃臀网站 | 亚洲中文字幕成人无码 | 国产尤物精品视频 | 无码一区二区三区在线观看 | 美女毛片一区二区三区四区 | 精品国产福利一区二区 | 免费网站看v片在线18禁无码 | 女人被爽到呻吟gif动态图视看 | 白嫩日本少妇做爰 | 国产性生大片免费观看性 | 色综合久久中文娱乐网 | 国产明星裸体无码xxxx视频 | a片免费视频在线观看 | 高潮毛片无遮挡高清免费视频 | 日本免费一区二区三区最新 | аⅴ资源天堂资源库在线 | 欧美日韩人成综合在线播放 | 亚洲七七久久桃花影院 | 伦伦影院午夜理论片 | 国产va免费精品观看 | 精品久久8x国产免费观看 | 欧美xxxxx精品 | 国产成人亚洲综合无码 | 波多野结衣aⅴ在线 | 免费视频欧美无人区码 | aⅴ亚洲 日韩 色 图网站 播放 | 久久国产精品偷任你爽任你 | 永久免费观看国产裸体美女 | 中文字幕人妻无码一夲道 | 欧美人与牲动交xxxx | 99久久精品午夜一区二区 | 大地资源网第二页免费观看 | 中文字幕无码免费久久9一区9 | 鲁大师影院在线观看 | 精品人人妻人人澡人人爽人人 | 国产成人无码区免费内射一片色欲 | 成熟妇人a片免费看网站 | 国产精品无码一区二区桃花视频 | 99久久婷婷国产综合精品青草免费 | 一二三四在线观看免费视频 | 中文字幕乱妇无码av在线 | 人人妻人人藻人人爽欧美一区 | 亚洲日韩精品欧美一区二区 | 六月丁香婷婷色狠狠久久 | 国产精品.xx视频.xxtv | 国产精品99久久精品爆乳 | 内射白嫩少妇超碰 | 亚洲精品一区二区三区四区五区 | 久久成人a毛片免费观看网站 | 国产精品亚洲一区二区三区喷水 | 国产激情艳情在线看视频 | 国产深夜福利视频在线 | 四虎影视成人永久免费观看视频 | 久久97精品久久久久久久不卡 | 欧美日韩一区二区综合 | 亚洲最大成人网站 | 久久久久国色av免费观看性色 | 少妇人妻av毛片在线看 | 久久午夜无码鲁丝片午夜精品 | 少妇无码吹潮 | 六十路熟妇乱子伦 | 牛和人交xxxx欧美 | 伊人久久大香线焦av综合影院 | 双乳奶水饱满少妇呻吟 | 亚洲gv猛男gv无码男同 | 又湿又紧又大又爽a视频国产 | 中文字幕无码热在线视频 | 偷窥日本少妇撒尿chinese | 人妻少妇精品久久 | 久久视频在线观看精品 | 亚洲精品一区二区三区四区五区 | 色婷婷久久一区二区三区麻豆 | 成人片黄网站色大片免费观看 | 国产精品香蕉在线观看 | 一本大道久久东京热无码av | 亚洲大尺度无码无码专区 | 日韩在线不卡免费视频一区 | 国产精品无码mv在线观看 | 大乳丰满人妻中文字幕日本 | 久久久婷婷五月亚洲97号色 | 亚洲综合在线一区二区三区 | 男女爱爱好爽视频免费看 | 久久久婷婷五月亚洲97号色 | 久久综合激激的五月天 | 免费人成在线观看网站 | 色一情一乱一伦一视频免费看 | 国产人妻久久精品二区三区老狼 | 久久久久久亚洲精品a片成人 | 国产精品国产三级国产专播 | 中文亚洲成a人片在线观看 | 青青草原综合久久大伊人精品 | 国产热a欧美热a在线视频 | 亚洲一区二区三区在线观看网站 | 国产在线一区二区三区四区五区 | 老子影院午夜伦不卡 | 久久亚洲精品中文字幕无男同 | 丰满岳乱妇在线观看中字无码 | 久久精品国产亚洲精品 | 成人aaa片一区国产精品 | 免费视频欧美无人区码 | 亚洲色偷偷男人的天堂 | 无遮挡啪啪摇乳动态图 | 亚洲国产av美女网站 | 免费观看的无遮挡av | 少妇高潮一区二区三区99 | 欧美 日韩 人妻 高清 中文 | 亚洲欧美日韩成人高清在线一区 | 国产精品无码久久av | 欧美精品国产综合久久 | 人人妻人人藻人人爽欧美一区 | 熟女俱乐部五十路六十路av | 欧美 日韩 亚洲 在线 | 少女韩国电视剧在线观看完整 | 色婷婷香蕉在线一区二区 | 久久亚洲国产成人精品性色 | 成人亚洲精品久久久久 | 少妇性俱乐部纵欲狂欢电影 | 精品人人妻人人澡人人爽人人 | 伊人久久大香线蕉亚洲 | 男女猛烈xx00免费视频试看 | 婷婷六月久久综合丁香 | 熟妇人妻无乱码中文字幕 | 国产精品亚洲а∨无码播放麻豆 | 亚洲成a人一区二区三区 | 亚洲啪av永久无码精品放毛片 | 精品国偷自产在线视频 | 国产精品久久精品三级 | 成人av无码一区二区三区 | 欧美freesex黑人又粗又大 | 精品人妻人人做人人爽夜夜爽 | 亚洲精品综合五月久久小说 | 欧美日韩一区二区免费视频 | 亚洲成熟女人毛毛耸耸多 | 老头边吃奶边弄进去呻吟 | av无码久久久久不卡免费网站 | 欧美丰满少妇xxxx性 | 中国大陆精品视频xxxx | 欧美喷潮久久久xxxxx | 娇妻被黑人粗大高潮白浆 | 99久久久国产精品无码免费 | 九九综合va免费看 | 午夜福利不卡在线视频 | 四虎4hu永久免费 | 国产亚洲美女精品久久久2020 | 欧美丰满老熟妇xxxxx性 | 国产做国产爱免费视频 | 欧美一区二区三区视频在线观看 | 亚洲精品国产第一综合99久久 | 人妻夜夜爽天天爽三区 | 国产网红无码精品视频 | 少妇性俱乐部纵欲狂欢电影 | 九一九色国产 | 久久久久免费看成人影片 | 又黄又爽又色的视频 | 精品国精品国产自在久国产87 | 全球成人中文在线 | 国产suv精品一区二区五 | 露脸叫床粗话东北少妇 | 亚洲国产成人a精品不卡在线 | 国产成人无码av一区二区 | 人人超人人超碰超国产 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 色妞www精品免费视频 | 少妇久久久久久人妻无码 | 久久国产精品二国产精品 | 老太婆性杂交欧美肥老太 | 欧美freesex黑人又粗又大 | 午夜丰满少妇性开放视频 | 99久久精品无码一区二区毛片 | 欧美日韩色另类综合 | 久久综合狠狠综合久久综合88 | 丰满诱人的人妻3 | 清纯唯美经典一区二区 | 亚洲精品一区二区三区在线观看 | 免费视频欧美无人区码 | 天天躁夜夜躁狠狠是什么心态 | 欧美日韩综合一区二区三区 | 国模大胆一区二区三区 | 377p欧洲日本亚洲大胆 | 又大又紧又粉嫩18p少妇 | 内射欧美老妇wbb | 一本精品99久久精品77 | 久久精品99久久香蕉国产色戒 | 无码av最新清无码专区吞精 | 成人三级无码视频在线观看 | 色综合久久久无码中文字幕 | 国产成人久久精品流白浆 | 亚洲精品国产a久久久久久 | 亚洲精品成人福利网站 | 久久精品一区二区三区四区 | 狠狠色噜噜狠狠狠狠7777米奇 | 精品无码av一区二区三区 | 欧美 日韩 亚洲 在线 | 99er热精品视频 | 久久精品国产日本波多野结衣 | 无码播放一区二区三区 | 精品久久久中文字幕人妻 | 激情内射日本一区二区三区 | 成人欧美一区二区三区 | 窝窝午夜理论片影院 | a国产一区二区免费入口 | 日韩少妇内射免费播放 | 大肉大捧一进一出视频出来呀 | 男人的天堂2018无码 | 欧美日韩一区二区免费视频 | 亚洲国产综合无码一区 | 东京无码熟妇人妻av在线网址 | 成人无码影片精品久久久 | 久久综合久久自在自线精品自 | 2019午夜福利不卡片在线 | 免费无码一区二区三区蜜桃大 | 精品夜夜澡人妻无码av蜜桃 | 高清国产亚洲精品自在久久 | 精品久久综合1区2区3区激情 | 国产国产精品人在线视 | 久久综合九色综合欧美狠狠 | 亚洲色在线无码国产精品不卡 | 成人影院yy111111在线观看 | 国产高清av在线播放 | 国产一区二区三区影院 | √天堂中文官网8在线 | 小sao货水好多真紧h无码视频 | 内射欧美老妇wbb | 成人aaa片一区国产精品 | 国产97人人超碰caoprom | 成 人影片 免费观看 | 国产精品18久久久久久麻辣 | 色老头在线一区二区三区 | 人人妻人人澡人人爽欧美一区九九 | 亚洲人成网站在线播放942 | 天堂亚洲2017在线观看 | 无码av中文字幕免费放 | 在线观看欧美一区二区三区 | 欧美人与物videos另类 | 男女猛烈xx00免费视频试看 | 亚洲国产午夜精品理论片 | 国产深夜福利视频在线 | 中文精品无码中文字幕无码专区 | 亚洲精品无码人妻无码 | 黑人大群体交免费视频 | 蜜桃无码一区二区三区 | 鲁大师影院在线观看 | 久久午夜无码鲁丝片秋霞 | 亚洲成色www久久网站 | 久久熟妇人妻午夜寂寞影院 | 无码av免费一区二区三区试看 | 粗大的内捧猛烈进出视频 | 免费网站看v片在线18禁无码 | 无码av中文字幕免费放 | 国内精品久久久久久中文字幕 | 久久熟妇人妻午夜寂寞影院 | 好屌草这里只有精品 | 国产 精品 自在自线 | 综合激情五月综合激情五月激情1 | av无码久久久久不卡免费网站 | 久久精品无码一区二区三区 | 东京热一精品无码av | 欧美丰满熟妇xxxx性ppx人交 | 亚洲国产精品美女久久久久 | 2020最新国产自产精品 | 宝宝好涨水快流出来免费视频 | 强开小婷嫩苞又嫩又紧视频 | 精品人妻中文字幕有码在线 | 强奷人妻日本中文字幕 | 一个人看的视频www在线 | 中文字幕久久久久人妻 | 国精产品一区二区三区 | 99麻豆久久久国产精品免费 | 亚洲欧美精品伊人久久 | 欧美日本免费一区二区三区 | 亚洲成熟女人毛毛耸耸多 | 乌克兰少妇性做爰 | 2020最新国产自产精品 | 中文字幕 人妻熟女 | 亚洲精品久久久久久久久久久 | 亚洲国产精品美女久久久久 | 永久黄网站色视频免费直播 | 老子影院午夜精品无码 | 久久国产精品二国产精品 | 国产97色在线 | 免 | 国产精品怡红院永久免费 | 亚洲色欲久久久综合网东京热 | 亚洲自偷自拍另类第1页 | 久久99精品久久久久婷婷 | 久久亚洲精品中文字幕无男同 | 高潮毛片无遮挡高清免费 | 一个人免费观看的www视频 | 老司机亚洲精品影院无码 | 亚洲精品鲁一鲁一区二区三区 | 激情亚洲一区国产精品 | 国产成人av免费观看 | 撕开奶罩揉吮奶头视频 | 99麻豆久久久国产精品免费 | 国产精品久久久久久久9999 | 日日摸日日碰夜夜爽av | 国产成人无码午夜视频在线观看 | 国产精品无码久久av | 国产三级精品三级男人的天堂 | 女人被男人躁得好爽免费视频 | 欧美猛少妇色xxxxx | 5858s亚洲色大成网站www | 亚洲精品一区三区三区在线观看 | 欧美 丝袜 自拍 制服 另类 | 日本爽爽爽爽爽爽在线观看免 | 亚洲va欧美va天堂v国产综合 | 色诱久久久久综合网ywww | 亚洲熟妇自偷自拍另类 | 中文亚洲成a人片在线观看 | 国产激情无码一区二区 | 国产乱人偷精品人妻a片 | 大肉大捧一进一出视频出来呀 | 99久久人妻精品免费二区 | √8天堂资源地址中文在线 | 精品国产成人一区二区三区 | 波多野结衣乳巨码无在线观看 | 疯狂三人交性欧美 | 少妇无套内谢久久久久 | 成人女人看片免费视频放人 | 久久婷婷五月综合色国产香蕉 | 少妇高潮喷潮久久久影院 | 成人一在线视频日韩国产 | 欧美国产亚洲日韩在线二区 | 欧美性黑人极品hd | 国产在线精品一区二区高清不卡 | 国产亚洲视频中文字幕97精品 | 亚洲一区二区三区四区 | 一个人免费观看的www视频 | 亚洲中文字幕av在天堂 | 色婷婷香蕉在线一区二区 | 中文字幕无码av波多野吉衣 | 国产黑色丝袜在线播放 | 思思久久99热只有频精品66 | 99视频精品全部免费免费观看 | 亚洲无人区午夜福利码高清完整版 | 澳门永久av免费网站 | 国产精品久久久久久亚洲毛片 | 午夜福利一区二区三区在线观看 | 99久久精品无码一区二区毛片 | 欧美亚洲国产一区二区三区 | 精品无人区无码乱码毛片国产 | 99久久婷婷国产综合精品青草免费 | 国产精品-区区久久久狼 | 亚洲综合色区中文字幕 | 国产莉萝无码av在线播放 | 中文字幕av伊人av无码av | 人妻插b视频一区二区三区 | 人妻插b视频一区二区三区 | 人妻中文无码久热丝袜 | 亚洲成av人片天堂网无码】 | 成熟妇人a片免费看网站 | 狠狠亚洲超碰狼人久久 | 中文字幕人妻无码一区二区三区 | 少妇无码av无码专区在线观看 | 少妇的肉体aa片免费 | 日本一本二本三区免费 | 中文字幕乱码中文乱码51精品 | 国产精品无码mv在线观看 | 欧美午夜特黄aaaaaa片 | 三上悠亚人妻中文字幕在线 | 无码人妻久久一区二区三区不卡 | 狠狠色欧美亚洲狠狠色www | 国产国产精品人在线视 | 亚洲欧美国产精品久久 | 在线天堂新版最新版在线8 | 人人妻人人澡人人爽精品欧美 | 色老头在线一区二区三区 | 久久久婷婷五月亚洲97号色 | 无遮挡啪啪摇乳动态图 | 日韩精品无码一区二区中文字幕 | 夫妻免费无码v看片 | 日欧一片内射va在线影院 | 久久综合狠狠综合久久综合88 | 亚洲日韩av一区二区三区四区 | 日本一卡二卡不卡视频查询 | 日本www一道久久久免费榴莲 | 日韩精品a片一区二区三区妖精 | 动漫av网站免费观看 | 亚洲 高清 成人 动漫 | 久久人人爽人人爽人人片av高清 | 99久久亚洲精品无码毛片 | 精品国产青草久久久久福利 | 老司机亚洲精品影院无码 | 无码人妻出轨黑人中文字幕 | 人人超人人超碰超国产 | 成人无码影片精品久久久 | 18禁止看的免费污网站 | 中文字幕精品av一区二区五区 | 高清国产亚洲精品自在久久 | 亚洲一区二区三区国产精华液 | 亚洲国产综合无码一区 | 国产人妻精品午夜福利免费 | 亚洲欧美国产精品专区久久 | 亚洲呦女专区 | 久久久婷婷五月亚洲97号色 | 人人妻人人澡人人爽欧美一区九九 | 日韩精品久久久肉伦网站 | 亚洲无人区一区二区三区 | 精品人妻人人做人人爽夜夜爽 | 成人欧美一区二区三区黑人免费 | 国产精品久久久久9999小说 | 中文字幕无码av激情不卡 | 欧美国产亚洲日韩在线二区 | 欧美老妇与禽交 | 精品厕所偷拍各类美女tp嘘嘘 | 牲交欧美兽交欧美 | 无码av最新清无码专区吞精 | 免费观看又污又黄的网站 | 国产成人精品久久亚洲高清不卡 | 少妇性荡欲午夜性开放视频剧场 | 国产三级久久久精品麻豆三级 | 天干天干啦夜天干天2017 | 亚洲欧洲无卡二区视頻 | 国产精品手机免费 | www一区二区www免费 | 内射老妇bbwx0c0ck | 久在线观看福利视频 | 亚洲精品久久久久久久久久久 | 国产在线无码精品电影网 | 曰韩无码二三区中文字幕 | 一本久久a久久精品亚洲 | 国产内射爽爽大片视频社区在线 | 欧美成人免费全部网站 | 久久无码中文字幕免费影院蜜桃 | 国产人妻人伦精品1国产丝袜 | 99久久精品午夜一区二区 | 一本精品99久久精品77 | 九月婷婷人人澡人人添人人爽 | 4hu四虎永久在线观看 | 水蜜桃色314在线观看 | 无码帝国www无码专区色综合 | 成人精品视频一区二区三区尤物 | 清纯唯美经典一区二区 | 伊人久久婷婷五月综合97色 | 中文字幕无码日韩欧毛 | 午夜精品一区二区三区在线观看 | 亚洲熟妇色xxxxx欧美老妇 | 少妇太爽了在线观看 | 在线а√天堂中文官网 | 日韩无码专区 | 国产精品丝袜黑色高跟鞋 | 亚洲国产综合无码一区 | 亚洲一区二区三区在线观看网站 | 高清国产亚洲精品自在久久 | 特级做a爰片毛片免费69 | 亚无码乱人伦一区二区 | 精品无码一区二区三区的天堂 | 欧美亚洲日韩国产人成在线播放 | 色一情一乱一伦一区二区三欧美 | 亚洲欧美精品aaaaaa片 | av人摸人人人澡人人超碰下载 | 精品亚洲成av人在线观看 | 日欧一片内射va在线影院 | 国産精品久久久久久久 | 在线成人www免费观看视频 | 成人亚洲精品久久久久 | 国产精品多人p群无码 | 国产精品久久久久久亚洲毛片 | 俺去俺来也www色官网 | 又湿又紧又大又爽a视频国产 | 色综合久久中文娱乐网 | 精品人妻人人做人人爽夜夜爽 | 亚洲 日韩 欧美 成人 在线观看 | 人人妻人人澡人人爽欧美精品 | 精品国产成人一区二区三区 | 天天做天天爱天天爽综合网 | 在线播放免费人成毛片乱码 | 无套内射视频囯产 | 亚洲精品中文字幕久久久久 | 狂野欧美性猛xxxx乱大交 | 99久久久无码国产aaa精品 | 亚洲一区av无码专区在线观看 | 亚洲色偷偷偷综合网 | 俺去俺来也在线www色官网 | 国产麻豆精品精东影业av网站 | 久久综合给合久久狠狠狠97色 | 亚洲va中文字幕无码久久不卡 | 久久成人a毛片免费观看网站 | 久久精品99久久香蕉国产色戒 | 国产人妻精品一区二区三区不卡 | 亚洲 另类 在线 欧美 制服 | 色偷偷人人澡人人爽人人模 | 国产亚洲日韩欧美另类第八页 | 国产成人无码a区在线观看视频app | 少妇高潮一区二区三区99 | 精品国偷自产在线 | 国产成人一区二区三区别 | 国产精品毛片一区二区 | 少妇被粗大的猛进出69影院 | 亚洲精品国偷拍自产在线观看蜜桃 | 日韩av无码一区二区三区 | 在线观看免费人成视频 | 久久zyz资源站无码中文动漫 | 精品久久综合1区2区3区激情 | 成人片黄网站色大片免费观看 | 国产两女互慰高潮视频在线观看 | 国产真人无遮挡作爱免费视频 | 成人欧美一区二区三区黑人 | 国产在热线精品视频 | 无遮无挡爽爽免费视频 | 中文字幕 人妻熟女 | 日本大香伊一区二区三区 | 久久五月精品中文字幕 | 网友自拍区视频精品 | 欧美xxxx黑人又粗又长 | 欧美怡红院免费全部视频 | 欧美自拍另类欧美综合图片区 | 精品成人av一区二区三区 | 狠狠色色综合网站 | 骚片av蜜桃精品一区 | 亚洲一区二区三区含羞草 | 久激情内射婷内射蜜桃人妖 | 99精品国产综合久久久久五月天 | 无码人妻精品一区二区三区下载 | 在线看片无码永久免费视频 | 亚洲欧美精品aaaaaa片 | 久久久中文字幕日本无吗 | 国产69精品久久久久app下载 | 国产性生交xxxxx无码 | 全球成人中文在线 | 熟女少妇在线视频播放 | 亚洲无人区一区二区三区 | 国产成人综合在线女婷五月99播放 | 特级做a爰片毛片免费69 | 六十路熟妇乱子伦 | 午夜肉伦伦影院 | 好男人www社区 | 疯狂三人交性欧美 | 无码av中文字幕免费放 | 国产极品视觉盛宴 | 国产香蕉97碰碰久久人人 | 亚洲欧美日韩成人高清在线一区 | 麻豆成人精品国产免费 | 日本熟妇乱子伦xxxx | 日本免费一区二区三区最新 | 亚洲精品一区三区三区在线观看 | 国产精品毛片一区二区 | 永久黄网站色视频免费直播 | 宝宝好涨水快流出来免费视频 | 97夜夜澡人人双人人人喊 | 国产人妻精品一区二区三区 | 水蜜桃亚洲一二三四在线 | 欧美老人巨大xxxx做受 | 亚洲高清偷拍一区二区三区 | 成人亚洲精品久久久久软件 | 亚洲综合精品香蕉久久网 | 亚洲精品一区二区三区在线观看 | 色婷婷久久一区二区三区麻豆 | 九月婷婷人人澡人人添人人爽 | 国产成人一区二区三区在线观看 | 欧美日本精品一区二区三区 | 久久人人97超碰a片精品 | 波多野结衣一区二区三区av免费 | 日韩精品一区二区av在线 | 人妻无码αv中文字幕久久琪琪布 | 亚洲精品久久久久avwww潮水 | 国产婷婷色一区二区三区在线 | 欧美野外疯狂做受xxxx高潮 | 国产精品毛多多水多 | 婷婷综合久久中文字幕蜜桃三电影 | 色一情一乱一伦一区二区三欧美 | 中文字幕无码热在线视频 | 欧美 日韩 亚洲 在线 | 亚洲一区二区三区偷拍女厕 | 久久99久久99精品中文字幕 | 麻花豆传媒剧国产免费mv在线 | 亚洲春色在线视频 | 成人亚洲精品久久久久 | 国产性猛交╳xxx乱大交 国产精品久久久久久无码 欧洲欧美人成视频在线 | 国产成人综合美国十次 | 国产香蕉97碰碰久久人人 | 在线成人www免费观看视频 | 日本熟妇乱子伦xxxx | 男女作爱免费网站 | 麻花豆传媒剧国产免费mv在线 | 精品午夜福利在线观看 | 亚洲日本一区二区三区在线 | 亚洲乱码国产乱码精品精 | 国产高潮视频在线观看 | 日本又色又爽又黄的a片18禁 | 任你躁国产自任一区二区三区 | 久久国内精品自在自线 | 国产精品亚洲专区无码不卡 | 欧美怡红院免费全部视频 | av无码不卡在线观看免费 | 中文字幕无码av波多野吉衣 | 亚洲精品无码国产 | 亚洲中文字幕av在天堂 | 国产精品久久久久9999小说 | 久久国产精品偷任你爽任你 | 久久99久久99精品中文字幕 | 精品国产青草久久久久福利 | 狂野欧美性猛交免费视频 | 亚洲gv猛男gv无码男同 | 亚洲欧洲日本无在线码 | 少妇无码av无码专区在线观看 | 欧美放荡的少妇 | 免费看男女做好爽好硬视频 | 黑人大群体交免费视频 | 中文无码成人免费视频在线观看 | 九九久久精品国产免费看小说 | 精品久久综合1区2区3区激情 | 在线观看国产一区二区三区 | 国产精品自产拍在线观看 | 欧美老人巨大xxxx做受 | 日本精品久久久久中文字幕 | 国产香蕉尹人视频在线 | 日产国产精品亚洲系列 | 久久国产劲爆∧v内射 | 性生交片免费无码看人 | 无码任你躁久久久久久久 | 熟妇女人妻丰满少妇中文字幕 | 2019nv天堂香蕉在线观看 | 国产成人无码a区在线观看视频app | 人妻无码αv中文字幕久久琪琪布 | 亚洲日韩一区二区三区 | 亚洲爆乳精品无码一区二区三区 | 精品国产一区二区三区av 性色 | 亚洲中文字幕无码一久久区 | 成人片黄网站色大片免费观看 | 中文字幕无码免费久久9一区9 | 亚洲国产综合无码一区 | 青草视频在线播放 |