sync.Map是1.9才推薦的并發(fā)安全的map,除了互斥量以外,還運(yùn)用了原子操作,所以在這之前,有必要了解下 Go語(yǔ)言——原子操作
創(chuàng)新互聯(lián)建站秉承實(shí)現(xiàn)全網(wǎng)價(jià)值營(yíng)銷的理念,以專業(yè)定制企業(yè)官網(wǎng),網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè),重慶小程序開發(fā),網(wǎng)頁(yè)設(shè)計(jì)制作,移動(dòng)網(wǎng)站建設(shè),營(yíng)銷型網(wǎng)站建設(shè)幫助傳統(tǒng)企業(yè)實(shí)現(xiàn)“互聯(lián)網(wǎng)+”轉(zhuǎn)型升級(jí)專業(yè)定制企業(yè)官網(wǎng),公司注重人才、技術(shù)和管理,匯聚了一批優(yōu)秀的互聯(lián)網(wǎng)技術(shù)人才,對(duì)客戶都以感恩的心態(tài)奉獻(xiàn)自己的專業(yè)和所長(zhǎng)。
go1.10\src\sync\map.go
entry分為三種情況:
從read中讀取key,如果key存在就tryStore。
注意這里開始需要加鎖,因?yàn)樾枰僮鱠irty。
條目在read中,首先取消標(biāo)記,然后將條目保存到dirty里。(因?yàn)闃?biāo)記的數(shù)據(jù)不在dirty里)
最后原子保存value到條目里面,這里注意read和dirty都有條目。
總結(jié)一下Store:
這里可以看到dirty保存了數(shù)據(jù)的修改,除非可以直接原子更新read,繼續(xù)保持read clean。
有了之前的經(jīng)驗(yàn),可以猜測(cè)下load流程:
與猜測(cè)的 區(qū)別 :
由于數(shù)據(jù)保存兩份,所以刪除考慮:
先看第二種情況。加鎖直接刪除dirty數(shù)據(jù)。思考下貌似沒(méi)什么問(wèn)題,本身就是臟數(shù)據(jù)。
第一種和第三種情況唯一的區(qū)別就是條目是否被標(biāo)記。標(biāo)記代表刪除,所以直接返回。否則CAS操作置為nil。這里總感覺少點(diǎn)什么,因?yàn)闂l目其實(shí)還是存在的,雖然指針nil。
看了一圈貌似沒(méi)找到標(biāo)記的邏輯,因?yàn)閯h除只是將他變成nil。
之前以為這個(gè)邏輯就是簡(jiǎn)單的將為標(biāo)記的條目拷貝給dirty,現(xiàn)在看來(lái)大有文章。
p == nil,說(shuō)明條目已經(jīng)被delete了,CAS將他置為標(biāo)記刪除。然后這個(gè)條目就不會(huì)保存在dirty里面。
這里其實(shí)就跟miss邏輯串起來(lái)了,因?yàn)閙iss達(dá)到閾值之后,dirty會(huì)全量變成read,也就是說(shuō)標(biāo)記刪除在這一步最終刪除。這個(gè)還是很巧妙的。
真正的刪除邏輯:
很繞。。。。
Go 中的分片數(shù)組,實(shí)際上有點(diǎn)類似于Java中的ArrayList,是一個(gè)可以擴(kuò)展的數(shù)組,但是Go中的切片由比較靈活,它和數(shù)組很像,也是基于數(shù)組,所以在了解Go切片前我們先了解下數(shù)組。
數(shù)組簡(jiǎn)單描述就由相同類型元素組成的數(shù)據(jù)結(jié)構(gòu), 在創(chuàng)建初期就確定了長(zhǎng)度,是不可變的。
但是Go的數(shù)組類型又和C與Java的數(shù)組類型不一樣, NewArray 用于創(chuàng)建一個(gè)數(shù)組,從源碼中可以看出最后返回的是 Array{}的指針,并不是第一個(gè)元素的指針,在Go中數(shù)組屬于值類型,在進(jìn)行傳遞時(shí),采取的是值傳遞,通過(guò)拷貝整個(gè)數(shù)組。Go語(yǔ)言的數(shù)組是一種有序的struct。
Go 語(yǔ)言的數(shù)組有兩種不同的創(chuàng)建方式,一種是顯示的初始化,一種是隱式的初始化。
注意一定是使用 [...]T 進(jìn)行創(chuàng)建,使用三個(gè)點(diǎn)的隱式創(chuàng)建,編譯器會(huì)對(duì)數(shù)組的大小進(jìn)行推導(dǎo),只是Go提供的一種語(yǔ)法糖。
其次,Go中數(shù)組的類型,是由數(shù)值類型和長(zhǎng)度兩個(gè)一起確定的。[2]int 和 [3]int 不是同一個(gè)類型,不能進(jìn)行傳參和比較,把數(shù)組理解為類型和長(zhǎng)度兩個(gè)屬性的結(jié)構(gòu)體,其實(shí)就一目了然了。
Go中的數(shù)組屬于值類型,通常應(yīng)該存儲(chǔ)于棧中,局部變量依然會(huì)根據(jù)逃逸分析確定存儲(chǔ)棧還是堆中。
編譯器對(duì)數(shù)組函數(shù)中做兩種不同的優(yōu)化:
在靜態(tài)區(qū)完成賦值后復(fù)制到棧中。
總結(jié)起來(lái),在不考慮逃逸分析的情況下,如果數(shù)組中元素的個(gè)數(shù)小于或者等于 4 個(gè),那么所有的變量會(huì)直接在棧上初始化,如果數(shù)組元素大于 4 個(gè),變量就會(huì)在靜態(tài)存儲(chǔ)區(qū)初始化然后拷貝到棧上。
由于數(shù)組是值類型,那么賦值和函數(shù)傳參操作都會(huì)復(fù)制整個(gè)數(shù)組數(shù)據(jù)。
不管是賦值或函數(shù)傳參,地址都不一致,發(fā)生了拷貝。如果數(shù)組的數(shù)據(jù)較大,則會(huì)消耗掉大量?jī)?nèi)存。那么為了減少拷貝我們可以主動(dòng)的傳遞指針呀。
地址是一樣的,不過(guò)傳指針會(huì)有一個(gè)弊端,從打印結(jié)果可以看到,指針地址都是同一個(gè),萬(wàn)一原數(shù)組的指針指向更改了,那么函數(shù)里面的指針指向都會(huì)跟著更改。
同樣的我們將數(shù)組轉(zhuǎn)換為切片,通過(guò)傳遞切片,地址是不一樣的,數(shù)組值相同。
切片是引用傳遞,所以它們不需要使用額外的內(nèi)存并且比使用數(shù)組更有效率。
所以,切片屬于引用類型。
通過(guò)這種方式可以將數(shù)組轉(zhuǎn)換為切片。
中間不加三個(gè)點(diǎn)就是切片,使用這種方式創(chuàng)建切片,實(shí)際上是先創(chuàng)建數(shù)組,然后再通過(guò)第一種方式創(chuàng)建。
使用make創(chuàng)建切片,就不光編譯期了,make創(chuàng)建切片會(huì)涉及到運(yùn)行期。1. 切片的大小和容量是否足夠?。?/p>
切片是否發(fā)生了逃逸,最終在堆上初始化。如果切片小的話會(huì)先在?;蜢o態(tài)區(qū)進(jìn)行創(chuàng)建。
切片有一個(gè)數(shù)組的指針,len是指切片的長(zhǎng)度, cap指的是切片的容量。
cap是在初始化切片是生成的容量。
發(fā)現(xiàn)切片的結(jié)構(gòu)體是數(shù)組的地址指針array unsafe.Pointer,而Go中數(shù)組的地址代表數(shù)組結(jié)構(gòu)體的地址。
slice 中得到一塊內(nèi)存地址,array[0]或者unsafe.Pointer(array[0])。
也可以通過(guò)地址構(gòu)造切片
nil切片:指的unsafe.Pointer 為nil
空切片:
創(chuàng)建的指針不為空,len和cap為空
當(dāng)一個(gè)切片的容量滿了,就需要擴(kuò)容了。怎么擴(kuò),策略是什么?
如果原來(lái)數(shù)組切片的容量已經(jīng)達(dá)到了最大值,再想擴(kuò)容, Go 默認(rèn)會(huì)先開一片內(nèi)存區(qū)域,把原來(lái)的值拷貝過(guò)來(lái),然后再執(zhí)行 append() 操作。這種情況對(duì)現(xiàn)數(shù)組的地址和原數(shù)組地址不相同。
從上面結(jié)果我們可以看到,如果用 range 的方式去遍歷一個(gè)切片,拿到的 Value 其實(shí)是切片里面的值拷貝,即淺拷貝。所以每次打印 Value 的地址都不變。
由于 Value 是值拷貝的,并非引用傳遞,所以直接改 Value 是達(dá)不到更改原切片值的目的的,需要通過(guò) slice[index] 獲取真實(shí)的地址。
按值傳遞函數(shù)參數(shù),是拷貝參數(shù)的實(shí)際值到函數(shù)的形式參數(shù)的方法調(diào)用。在這種情況下,參數(shù)在函數(shù)內(nèi)變化對(duì)參數(shù)不會(huì)有影響。
默認(rèn)情況下,Go編程語(yǔ)言使用調(diào)用通過(guò)值的方法來(lái)傳遞參數(shù)。在一般情況下,這意味著,在函數(shù)內(nèi)碼不能改變用來(lái)調(diào)用所述函數(shù)的參數(shù)??紤]函數(shù)swap()的定義如下。
代碼如下:
/* function definition to swap the values */
func swap(int x, int y) int {
var temp int
temp = x /* save the value of x */
x = y /* put y into x */
y = temp /* put temp into y */
return temp;
}
現(xiàn)在,讓我們通過(guò)使實(shí)際值作為在以下示例調(diào)用函數(shù)swap():
代碼如下:
package main
import "fmt"
func main() {
/* local variable definition */
var a int = 100
var b int = 200
fmt.Printf("Before swap, value of a : %d\n", a )
fmt.Printf("Before swap, value of b : %d\n", b )
/* calling a function to swap the values */
swap(a, b)
fmt.Printf("After swap, value of a : %d\n", a )
fmt.Printf("After swap, value of b : %d\n", b )
}
func swap(x, y int) int {
var temp int
temp = x /* save the value of x */
x = y /* put y into x */
y = temp /* put temp into y */
return temp;
}
讓我們把上面的代碼放在一個(gè)C文件,編譯并執(zhí)行它,它會(huì)產(chǎn)生以下結(jié)果:
Before swap, value of a :100
Before swap, value of b :200
After swap, value of a :100
After swap, value of b :200
這表明,參數(shù)值沒(méi)有被改變,雖然它們已經(jīng)在函數(shù)內(nèi)部改變。
通過(guò)傳遞函數(shù)參數(shù),即是拷貝參數(shù)的地址到形式參數(shù)的參考方法調(diào)用。在函數(shù)內(nèi)部,地址是訪問(wèn)調(diào)用中使用的實(shí)際參數(shù)。這意味著,對(duì)參數(shù)的更改會(huì)影響傳遞的參數(shù)。
要通過(guò)引用傳遞的值,參數(shù)的指針被傳遞給函數(shù)就像任何其他的值。所以,相應(yīng)的,需要聲明函數(shù)的參數(shù)為指針類型如下面的函數(shù)swap(),它的交換兩個(gè)整型變量的值指向它的參數(shù)。
代碼如下:
/* function definition to swap the values */
func swap(x *int, y *int) {
var temp int
temp = *x /* save the value at address x */
*x = *y /* put y into x */
*y = temp /* put temp into y */
}
現(xiàn)在,讓我們調(diào)用函數(shù)swap()通過(guò)引用作為在下面的示例中傳遞數(shù)值:
代碼如下:
package main
import "fmt"
func main() {
/* local variable definition */
var a int = 100
var b int= 200
fmt.Printf("Before swap, value of a : %d\n", a )
fmt.Printf("Before swap, value of b : %d\n", b )
/* calling a function to swap the values.
* a indicates pointer to a ie. address of variable a and
* b indicates pointer to b ie. address of variable b.
*/
swap(a, b)
fmt.Printf("After swap, value of a : %d\n", a )
fmt.Printf("After swap, value of b : %d\n", b )
}
func swap(x *int, y *int) {
var temp int
temp = *x /* save the value at address x */
*x = *y /* put y into x */
*y = temp /* put temp into y */
}
讓我們把上面的代碼放在一個(gè)C文件,編譯并執(zhí)行它,它會(huì)產(chǎn)生以下結(jié)果:
Before swap, value of a :100
Before swap, value of b :200
After swap, value of a :200
After swap, value of b :100
這表明變化的功能以及不同于通過(guò)值調(diào)用的外部體現(xiàn)的改變不能反映函數(shù)之外。
Kotlin是一個(gè)跨編譯平臺(tái)的語(yǔ)言。 現(xiàn)在我們比較熟悉的是JVM平臺(tái)的Kotlin/JVM,但實(shí)際上還有Kotlin/javascript和正在開發(fā)的Kotlin/Native。 對(duì)不同編譯平臺(tái)的Kotlin來(lái)說(shuō),各方面差異比較大,所以在此分開討論。 先講講大家最熟悉的Kotlin/JVM。