国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Go語言核心36講(Go語言實戰與應用八)--學習筆記

niuxiaowei111 / 2190人閱讀

摘要:用于展示一種簡易的且更加寬松的互斥鎖的模擬。由于這里的原子操作函數只支持非常有限的數據類型,所以在很多應用場景下,互斥鎖往往是更加適合的。這主要是因為原子操作函數的執行速度要比互斥鎖快得多。

30 | 原子操作(下)

我們接著上一篇文章的內容繼續聊,上一篇我們提到了,sync/atomic包中的函數可以做的原子操作有:加法(add)、比較并交換(compare and swap,簡稱 CAS)、加載(load)、存儲(store)和交換(swap)。并且以此衍生出了兩個問題。

今天我們繼續來看第三個衍生問題: 比較并交換操作與交換操作相比有什么不同?優勢在哪里?

回答是:比較并交換操作即 CAS 操作,是有條件的交換操作,只有在條件滿足的情況下才會進行值的交換。

所謂的交換指的是,把新值賦給變量,并返回變量的舊值。

在進行 CAS 操作的時候,函數會先判斷被操作變量的當前值,是否與我們預期的舊值相等。如果相等,它就把新值賦給該變量,并返回true以表明交換操作已進行;否則就忽略交換操作,并返回false。

可以看到,CAS 操作并不是單一的操作,而是一種操作組合。這與其他的原子操作都不同。正因為如此,它的用途要更廣泛一些。例如,我們將它與for語句聯用就可以實現一種簡易的自旋鎖(spinlock)。

for { if atomic.CompareAndSwapInt32(&num2, 10, 0) {  fmt.Println("The second number has gone to zero.")  break } time.Sleep(time.Millisecond * 500)}

在for語句中的 CAS 操作可以不停地檢查某個需要滿足的條件,一旦條件滿足就退出for循環。這就相當于,只要條件未被滿足,當前的流程就會被一直“阻塞”在這里。

這在效果上與互斥鎖有些類似。不過,它們的適用場景是不同的。我們在使用互斥鎖的時候,總是假設共享資源的狀態會被其他的 goroutine 頻繁地改變。

而for語句加 CAS 操作的假設往往是:共享資源狀態的改變并不頻繁,或者,它的狀態總會變成期望的那樣。這是一種更加樂觀,或者說更加寬松的做法。

package mainimport (	"fmt"	"sync/atomic"	"time")func main() {	// 第三個衍生問題的示例。	forAndCAS1()	fmt.Println()	forAndCAS2()}// forAndCAS1 用于展示簡易的自旋鎖。func forAndCAS1() {	sign := make(chan struct{}, 2)	num := int32(0)	fmt.Printf("The number: %d/n", num)	go func() { // 定時增加num的值。		defer func() {			sign <- struct{}{}		}()		for {			time.Sleep(time.Millisecond * 500)			newNum := atomic.AddInt32(&num, 2)			fmt.Printf("The number: %d/n", newNum)			if newNum == 10 {				break			}		}	}()	go func() { // 定時檢查num的值,如果等于10就將其歸零。		defer func() {			sign <- struct{}{}		}()		for {			if atomic.CompareAndSwapInt32(&num, 10, 0) {				fmt.Println("The number has gone to zero.")				break			}			time.Sleep(time.Millisecond * 500)		}	}()	<-sign	<-sign}// forAndCAS2 用于展示一種簡易的(且更加寬松的)互斥鎖的模擬。func forAndCAS2() {	sign := make(chan struct{}, 2)	num := int32(0)	fmt.Printf("The number: %d/n", num)	max := int32(20)	go func(id int, max int32) { // 定時增加num的值。		defer func() {			sign <- struct{}{}		}()		for i := 0; ; i++ {			currNum := atomic.LoadInt32(&num)			if currNum >= max {				break			}			newNum := currNum + 2			time.Sleep(time.Millisecond * 200)			if atomic.CompareAndSwapInt32(&num, currNum, newNum) {				fmt.Printf("The number: %d [%d-%d]/n", newNum, id, i)			} else {				fmt.Printf("The CAS operation failed. [%d-%d]/n", id, i)			}		}	}(1, max)	go func(id int, max int32) { // 定時增加num的值。		defer func() {			sign <- struct{}{}		}()		for j := 0; ; j++ {			currNum := atomic.LoadInt32(&num)			if currNum >= max {				break			}			newNum := currNum + 2			time.Sleep(time.Millisecond * 200)			if atomic.CompareAndSwapInt32(&num, currNum, newNum) {				fmt.Printf("The number: %d [%d-%d]/n", newNum, id, j)			} else {				fmt.Printf("The CAS operation failed. [%d-%d]/n", id, j)			}		}	}(2, max)	<-sign	<-sign}

第四個衍生問題:假設我已經保證了對一個變量的寫操作都是原子操作,比如:加或減、存儲、交換等等,那我對它進行讀操作的時候,還有必要使用原子操作嗎?

回答是:很有必要。其中的道理你可以對照一下讀寫鎖。為什么在讀寫鎖保護下的寫操作和讀操作之間是互斥的?這是為了防止讀操作讀到沒有被修改完的值,對嗎?

如果寫操作還沒有進行完,讀操作就來讀了,那么就只能讀到僅修改了一部分的值。這顯然破壞了值的完整性,讀出來的值也是完全錯誤的。

所以,一旦你決定了要對一個共享資源進行保護,那就要做到完全的保護。不完全的保護基本上與不保護沒有什么區別。

好了,上面的主問題以及相關的衍生問題涉及了原子操作函數的用法、原理、對比和一些最佳實踐,希望你已經理解了。

由于這里的原子操作函數只支持非常有限的數據類型,所以在很多應用場景下,互斥鎖往往是更加適合的。

不過,一旦我們確定了在某個場景下可以使用原子操作函數,比如:只涉及并發地讀寫單一的整數類型值,或者多個互不相關的整數類型值,那就不要再考慮互斥鎖了。

這主要是因為原子操作函數的執行速度要比互斥鎖快得多。而且,它們使用起來更加簡單,不會涉及臨界區的選擇,以及死鎖等問題。當然了,在使用 CAS 操作的時候,我們還是要多加注意的,因為它可以被用來模仿鎖,并有可能“阻塞”流程。

知識擴展

問題:怎樣用好sync/atomic.Value?

為了擴大原子操作的適用范圍,Go 語言在 1.4 版本發布的時候向sync/atomic包中添加了一個新的類型Value。此類型的值相當于一個容器,可以被用來“原子地”存儲和加載任意的值。

atomic.Value類型是開箱即用的,我們聲明一個該類型的變量(以下簡稱原子變量)之后就可以直接使用了。這個類型使用起來很簡單,它只有兩個指針方法:Store和Load。不過,雖然簡單,但還是有一些值得注意的地方的。

首先一點,一旦atomic.Value類型的值(以下簡稱原子值)被真正使用,它就不應該再被復制了。什么叫做“真正使用”呢?

我們只要用它來存儲值了,就相當于開始真正使用了。atomic.Value類型屬于結構體類型,而結構體類型屬于值類型。

所以,復制該類型的值會產生一個完全分離的新值。這個新值相當于被復制的那個值的一個快照。之后,不論后者存儲的值怎樣改變,都不會影響到前者,反之亦然。

另外,關于用原子值來存儲值,有兩條強制性的使用規則。第一條規則,不能用原子值存儲nil。

也就是說,我們不能把nil作為參數值傳入原子值的Store方法,否則就會引發一個 panic。

這里要注意,如果有一個接口類型的變量,它的動態值是nil,但動態類型卻不是nil,那么它的值就不等于nil。我在前面講接口的時候和你說明過這個問題。正因為如此,這樣一個變量的值是可以被存入原子值的。

第二條規則,我們向原子值存儲的第一個值,決定了它今后能且只能存儲哪一個類型的值。

例如,我第一次向一個原子值存儲了一個string類型的值,那我在后面就只能用該原子值來存儲字符串了。如果我又想用它存儲結構體,那么在調用它的Store方法的時候就會引發一個 panic。這個 panic 會告訴我,這次存儲的值的類型與之前的不一致。

你可能會想:我先存儲一個接口類型的值,然后再存儲這個接口的某個實現類型的值,這樣是不是可以呢?

很可惜,這樣是不可以的,同樣會引發一個 panic。因為原子值內部是依據被存儲值的實際類型來做判斷的。所以,即使是實現了同一個接口的不同類型,它們的值也不能被先后存儲到同一個原子值中。

遺憾的是,我們無法通過某個方法獲知一個原子值是否已經被真正使用,并且,也沒有辦法通過常規的途徑得到一個原子值可以存儲值的實際類型。這使得我們誤用原子值的可能性大大增加,尤其是在多個地方使用同一個原子值的時候。

下面,我給你幾條具體的使用建議。

1、不要把內部使用的原子值暴露給外界。比如,聲明一個全局的原子變量并不是一個正確的做法。這個變量的訪問權限最起碼也應該是包級私有的。

2、如果不得不讓包外,或模塊外的代碼使用你的原子值,那么可以聲明一個包級私有的原子變量,然后再通過一個或多個公開的函數,讓外界間接地使用到它。注意,這種情況下不要把原子值傳遞到外界,不論是傳遞原子值本身還是它的指針值。

3、如果通過某個函數可以向內部的原子值存儲值的話,那么就應該在這個函數中先判斷被存儲值類型的合法性。若不合法,則應該直接返回對應的錯誤值,從而避免 panic 的發生。

4、如果可能的話,我們可以把原子值封裝到一個數據類型中,比如一個結構體類型。這樣,我們既可以通過該類型的方法更加安全地存儲值,又可以在該類型中包含可存儲值的合法類型信息。

除了上述使用建議之外,我還要再特別強調一點:盡量不要向原子值中存儲引用類型的值。因為這很容易造成安全漏洞。請看下面的代碼:

var box6 atomic.Valuev6 := []int{1, 2, 3}box6.Store(v6)v6[1] = 4 // 注意,此處的操作不是并發安全的!

我把一個[]int類型的切片值v6, 存入了原子值box6。注意,切片類型屬于引用類型。所以,我在外面改動這個切片值,就等于修改了box6中存儲的那個值。這相當于繞過了原子值而進行了非并發安全的操作。那么,應該怎樣修補這個漏洞呢?可以這樣做:

store := func(v []int) { replica := make([]int, len(v)) copy(replica, v) box6.Store(replica)}store(v6)v6[2] = 5 // 此處的操作是安全的。

我先為切片值v6創建了一個完全的副本。這個副本涉及的數據已經與原值毫不相干了。然后,我再把這個副本存入box6。如此一來,無論我再對v6的值做怎樣的修改,都不會破壞box6提供的安全保護。

以上,就是我要告訴你的關于atomic.Value的注意事項和使用建議。你可以在 demo64.go 文件中看到相應的示例。

package mainimport (	"errors"	"fmt"	"io"	"os"	"reflect"	"sync/atomic")func main() {	// 示例1。	var box atomic.Value	fmt.Println("Copy box to box2.")	box2 := box // 原子值在真正使用前可以被復制。	v1 := [...]int{1, 2, 3}	fmt.Printf("Store %v to box./n", v1)	box.Store(v1)	fmt.Printf("The value load from box is %v./n", box.Load())	fmt.Printf("The value load from box2 is %v./n", box2.Load())	fmt.Println()	// 示例2。	v2 := "123"	fmt.Printf("Store %q to box2./n", v2)	box2.Store(v2) // 這里并不會引發panic。	fmt.Printf("The value load from box is %v./n", box.Load())	fmt.Printf("The value load from box2 is %q./n", box2.Load())	fmt.Println()	// 示例3。	fmt.Println("Copy box to box3.")	box3 := box // 原子值在真正使用后不應該被復制!	fmt.Printf("The value load from box3 is %v./n", box3.Load())	v3 := 123	fmt.Printf("Store %d to box3./n", v3)	//box3.Store(v3) // 這里會引發一個panic,報告存儲值的類型不一致。	_ = box3	fmt.Println()	// 示例4。	var box4 atomic.Value	v4 := errors.New("something wrong")	fmt.Printf("Store an error with message %q to box4./n", v4)	box4.Store(v4)	v41 := io.EOF	fmt.Println("Store a value of the same type to box4.")	box4.Store(v41)	v42, ok := interface{}(&os.PathError{}).(error)	if ok {		fmt.Printf("Store a value of type %T that implements error interface to box4./n", v42)		//box4.Store(v42) // 這里會引發一個panic,報告存儲值的類型不一致。	}	fmt.Println()	// 示例5。	box5, err := NewAtomicValue(v4)	if err != nil {		fmt.Printf("error: %s/n", err)	}	fmt.Printf("The legal type in box5 is %s./n", box5.TypeOfValue())	fmt.Println("Store a value of the same type to box5.")	err = box5.Store(v41)	if err != nil {		fmt.Printf("error: %s/n", err)	}	fmt.Printf("Store a value of type %T that implements error interface to box5./n", v42)	err = box5.Store(v42)	if err != nil {		fmt.Printf("error: %s/n", err)	}	fmt.Println()	// 示例6。	var box6 atomic.Value	v6 := []int{1, 2, 3}	fmt.Printf("Store %v to box6./n", v6)	box6.Store(v6)	v6[1] = 4 // 注意,此處的操作不是并發安全的!	fmt.Printf("The value load from box6 is %v./n", box6.Load())	// 正確的做法如下。	v6 = []int{1, 2, 3}	store := func(v []int) {		replica := make([]int, len(v))		copy(replica, v)		box6.Store(replica)	}	fmt.Printf("Store %v to box6./n", v6)	store(v6)	v6[2] = 5 // 此處的操作是安全的。	fmt.Printf("The value load from box6 is %v./n", box6.Load())}type atomicValue struct {	v atomic.Value	t reflect.Type}func NewAtomicValue(example interface{}) (*atomicValue, error) {	if example == nil {		return nil, errors.New("atomic value: nil example")	}	return &atomicValue{		t: reflect.TypeOf(example),	}, nil}func (av *atomicValue) Store(v interface{}) error {	if v == nil {		return errors.New("atomic value: nil value")	}	t := reflect.TypeOf(v)	if t != av.t {		return fmt.Errorf("atomic value: wrong type: %s", t)	}	av.v.Store(v)	return nil}func (av *atomicValue) Load() interface{} {	return av.v.Load()}func (av *atomicValue) TypeOfValue() reflect.Type {	return av.t}

總結

我們把這兩篇文章一起總結一下。相對于原子操作函數,原子值類型的優勢很明顯,但它的使用規則也更多一些。首先,在首次真正使用后,原子值就不應該再被復制了。

其次,原子值的Store方法對其參數值(也就是被存儲值)有兩個強制的約束。一個約束是,參數值不能為nil。另一個約束是,參數值的類型不能與首個被存儲值的類型不同。也就是說,一旦一個原子值存儲了某個類型的值,那它以后就只能存儲這個類型的值了。

基于上面這幾個注意事項,我提出了幾條使用建議,包括:不要對外暴露原子變量、不要傳遞原子值及其指針值、盡量不要在原子值中存儲引用類型的值,等等。與之相關的一些解決方案我也一并提出了。希望你能夠受用。

原子操作明顯比互斥鎖要更加輕便,但是限制也同樣明顯。所以,我們在進行二選一的時候通常不會太困難。但是原子值與互斥鎖之間的選擇有時候就需要仔細的考量了。不過,如果你能牢記我今天講的這些內容的話,應該會有很大的助力。

思考題

今天的思考題只有一個,那就是:如果要對原子值和互斥鎖進行二選一,你認為最重要的三個決策條件應該是什么?

筆記源碼

https://github.com/MingsonZheng/go-core-demo

本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。

歡迎轉載、使用、重新發布,但務必保留文章署名 鄭子銘 (包含鏈接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商業目的,基于本文修改后的作品務必以相同的許可發布。

?

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/123978.html

相關文章

  • Go語言核心36Go語言實戰應用十二)--學習筆記

    摘要:除此之外,把并發安全字典封裝在一個結構體類型中,往往是一個很好的選擇。請看下面的代碼如上所示,我編寫了一個名為的結構體類型,它代表了鍵類型為值類型為的并發安全字典。在這個結構體類型中,只有一個類型的字段。34 | 并發安全字典sync.Map (上)我們今天再來講一個并發安全的高級數據結構:sync.Map。眾所周知,Go 語言自帶的字典類型map并不是并發安全的。前導知識:并發安全字典誕生...

    不知名網友 評論0 收藏0
  • Go語言核心36Go語言實戰應用十三)--學習筆記

    摘要:在第二種方案中,我們封裝的結構體類型的所有方法,都可以與類型的方法完全一致包括方法名稱和方法簽名。所以在設計這樣的結構體類型的時候,只包含類型的字段就不夠了。當參數或的實際類型不符合要求時,方法會立即引發。35 | 并發安全字典sync.Map (下)我們在上一篇文章中談到了,由于并發安全字典提供的方法涉及的鍵和值的類型都是interface{},所以我們在調用這些方法的時候,往往還需要對鍵...

    不知名網友 評論0 收藏0
  • PHPer書單

    摘要:想提升自己,還得多看書多看書多看書下面是我收集到的一些程序員應該看得書單及在線教程,自己也沒有全部看完。共勉吧當然,如果你有好的書想分享給大家的或者覺得書單不合理,可以去通過進行提交。講師溫銘,軟件基金會主席,最佳實踐作者。 想提升自己,還得多看書!多看書!多看書!下面是我收集到的一些PHP程序員應該看得書單及在線教程,自己也沒有全部看完。共勉吧!當然,如果你有好的書想分享給大家的或者...

    jimhs 評論0 收藏0
  • SegmentFault 技術周刊 Vol.40 - 2018,來學習一門新的編程語言吧!

    摘要:入門,第一個這是一門很新的語言,年前后正式公布,算起來是比較年輕的編程語言了,更重要的是它是面向程序員的函數式編程語言,它的代碼運行在之上。它通過編輯類工具,帶來了先進的編輯體驗,增強了語言服務。 showImg(https://segmentfault.com/img/bV1xdq?w=900&h=385); 新的一年不知不覺已經到來了,總結過去的 2017,相信小伙們一定有很多收獲...

    caspar 評論0 收藏0
  • SegmentFault 技術周刊 Vol.40 - 2018,來學習一門新的編程語言吧!

    摘要:入門,第一個這是一門很新的語言,年前后正式公布,算起來是比較年輕的編程語言了,更重要的是它是面向程序員的函數式編程語言,它的代碼運行在之上。它通過編輯類工具,帶來了先進的編輯體驗,增強了語言服務。 showImg(https://segmentfault.com/img/bV1xdq?w=900&h=385); 新的一年不知不覺已經到來了,總結過去的 2017,相信小伙們一定有很多收獲...

    nihao 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<