網站首頁 編程語言 正文
原子操作就是不可中斷的操作,外界是看不到原子操作的中間狀態,要么看到原子操作已經完成,要么看到原子操作已經結束。在某個值的原子操作執行的過程中,CPU絕對不會再去執行其他針對該值的操作,那么其他操作也是原子操作。
Go語言中提供的原子操作都是非侵入式的,在標準庫代碼包sync/atomic中提供了相關的原子函數。
增或減
用于增或減的原子操作的函數名稱都是以"Add"開頭的,后面跟具體的類型名,比如下面這個示例就是int64類型的原子減操作
func main() { var counter int64 = 23 atomic.AddInt64(&counter,-3) fmt.Println(counter) } ---output--- 20
原子函數的第一個參數都是指向變量類型的指針,是因為原子操作需要知道該變量在內存中的存放位置,然后加以特殊的CPU指令,也就是說對于不能取得內存存放地址的變量是無法進行原子操作的。第二個參數的類型會自動轉換為與第一個參數相同的類型。此外,原子操作會自動將操作后的值賦值給變量,無需我們自己手動賦值了。
對于 atomic.AddUint32() 和 atomic.AddUint64() 的第二個參數為 uint32 與 uint64,因此無法直接傳遞一個負的數值進行減法操作,Go語言提供了另一種方法來迂回實現:使用二進制補碼的特性
注意:unsafe.Pointer 類型的值無法被加減。
比較并交換(Compare And Swap)
簡稱CAS,在標準庫代碼包sync/atomic中以”Compare And Swap“為前綴的若干函數就是CAS操作函數,比如下面這個
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
第一個參數的值是這個變量的指針,第二個參數是這個變量的舊值,第三個參數指的是這個變量的新值。
運行過程:調用CompareAndSwapInt32 后,會先判斷這個指針上的值是否跟舊值相等,若相等,就用新值覆蓋掉這個值,若相等,那么后面的操作就會被忽略掉。返回一個 swapped 布爾值,表示是否已經進行了值替換操作。
與鎖有不同之處:鎖總是假設會有并發操作修改被操作的值,而CAS總是假設值沒有被修改,因此CAS比起鎖要更低的性能損耗,鎖被稱為悲觀鎖,而CAS被稱為樂觀鎖。
CAS的使用示例
var value int32 func AddValue(delta int32) { for { v:= value if atomic.CompareAndSwapInt32(&value,v,(v+delta)) { break } } }
由示例可以看出,我們需要多次使用for循環來判斷該值是否已被更改,為了保證CAS操作成功,僅在 CompareAndSwapInt32 返回為 true時才退出循環,這跟自旋鎖的自旋行為相似。
載入與存儲
對一個值進行讀或寫時,并不代表這個值是最新的值,也有可能是在在讀或寫的過程中進行了并發的寫操作導致原值改變。為了解決這問題,Go語言的標準庫代碼包sync/atomic提供了原子的讀?。↙oad為前綴的函數)或寫入(Store為前綴的函數)某個值
將上面的示例改為原子讀取
var value int32 func AddValue(delta int32) { for { v:= atomic.LoadInt32(&value) if atomic.CompareAndSwapInt32(&value,v,(v+delta)) { break } } }
原子寫入總會成功,因為它不需要關心原值是什么,而CAS中必須關注舊值,因此原子寫入并不能代替CAS,原子寫入包含兩個參數,以下面的StroeInt32為例:
//第一個參數是被操作值的指針,第二個是被操作值的新值 func StoreInt32(addr *int32, val int32)
交換
這類操作都以”Swap“開頭的函數,稱為”原子交換操作“,功能與之前說的CAS操作與原子寫入操作有相似之處。
func SwapInt32(addr *int32, new int32) (old int32)
以 SwapInt32 為例,第一個參數是int32類型的指針,第二個是新值。原子交換操作不需要關心原值,而是直接設置新值,但是會返回被操作值的舊值。
原子值
Go語言的標準庫代碼包sync/atomic中有一個叫做Value的原子值,它是一個結構體類型,用于存儲需要原子讀寫的值,結構體如下
// Value提供原子加載并存儲一致類型的值。 // Value的零值從Load返回nil。 //調用Store后,不得復制值。 //首次使用后不得復制值。 type Value struct { v interface{} }
可以看出結構體內是一個 v interface{},也就是說 該Value原子值可以保存任何類型的需要原子讀寫的值。
使用方式如下:
var Atomicvalue atomic.Value
該類型有兩個公開的指針方法
//原子的讀取原子值實例中存儲的值,返回一個 interface{} 類型的值,且不接受任何參數。 //若未曾通過store方法存儲值之前,會返回nil func (v *Value) Load() (x interface{}) //原子的在原子實例中存儲一個值,接收一個 interface{} 類型(不能為nil)的參數,且不會返回任何值 func (v *Value) Store(x interface{})
一旦原子值實例存儲了某個類型的值,那么之后Store存儲的值就必須是與該類型一致,否則就會引發panic。
嚴格來講,atomic.Value類型的變量一旦被聲明,就不應該被復制到其他地方。比如:作為源值賦值給其他變量,作為參數傳遞給函數,作為結果值從函數返回,作為元素值通過通道傳遞,這些都會造成值的復制。
但是atomic.Value類型的指針類型變量就不會存在這個問題,原因是對結構體的復制不但會生成該值的副本,還會生成其中字段的副本,這樣那么并發引發的值變化都與原值沒關系了。
看下面這個小示例
func main() { var Atomicvalue atomic.Value Atomicvalue.Store([]int{1,2,3,4,5}) anotherStore(Atomicvalue) fmt.Println("main: ",Atomicvalue) } func anotherStore(Atomicvalue atomic.Value) { Atomicvalue.Store([]int{6,7,8,9,10}) fmt.Println("anotherStore: ",Atomicvalue) } ---output--- anotherStore: {[6 7 8 9 10]} main: {[1 2 3 4 5]}
原子操作與互斥鎖的區別
互斥鎖是一種數據結構,使你可以執行一系列互斥操作。而原子操作是互斥的單個操作,這意味著沒有其他線程可以打斷它。那么就Go語言里atomic包里的原子操作和sync包提供的同步鎖有什么不同呢?
首先atomic操作的優勢是更輕量,比如CAS可以在不形成臨界區和創建互斥量的情況下完成并發安全的值替換操作。這可以大大的減少同步對程序性能的損耗。
原子操作也有劣勢。還是以CAS操作為例,使用CAS操作的做法趨于樂觀,總是假設被操作值未曾被改變(即與舊值相等),并一旦確認這個假設的真實性就立即進行值替換,那么在被操作值被頻繁變更的情況下,CAS操作并不那么容易成功。而使用互斥鎖的做法則趨于悲觀,我們總假設會有并發的操作要修改被操作的值,并使用鎖將相關操作放入臨界區中加以保護。
所以總結下來原子操作與互斥鎖的區別有:
互斥鎖是一種數據結構,用來讓一個線程執行程序的關鍵部分,完成互斥的多個操作。
原子操作是針對某個值的單個互斥操作。
可以把互斥鎖理解為悲觀鎖,共享資源每次只給一個線程使用,其它線程阻塞,用完后再把資源轉讓給其它線程。
atomic包提供了底層的原子性內存原語,這對于同步算法的實現很有用。這些函數一定要非常小心地使用,使用不當反而會增加系統資源的開銷,對于應用層來說,最好使用通道或sync包中提供的功能來完成同步操作。
針對atomic包的觀點在Google的郵件組里也有很多討論,其中一個結論解釋是:
應避免使用該包裝。或者,閱讀C ++ 11標準的“原子操作”一章;如果您了解如何在C ++中安全地使用這些操作,那么你才能有安全地使用Go的 sync/atomic包的能力。
原文鏈接:https://www.jianshu.com/p/a0be632df99b
相關推薦
- 2022-04-11 .NET垃圾回收器原理及使用_實用技巧
- 2024-04-05 docker部署mongodb
- 2022-03-18 處理Oracle監聽程序當前無法識別連接描述符中請求的服務異常(ORA-12514)_oracle
- 2023-07-30 element中對el-input 自定義驗證規則
- 2023-12-21 npm ERR! code EPERM npm ERR! syscall unlink npm ER
- 2023-03-27 python去除空格,tab制表符和\n換行符的小技巧分享_python
- 2022-12-04 React條件渲染實例講解使用_React
- 2022-01-18 在使用npm install時遇到的問題 npm ERR! code ERESOLVE
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支