欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

GO的鎖和原子操作的示例詳解

 更新時(shí)間:2023年02月24日 10:58:51   作者:阿兵云原生  
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中鎖和原子操作的相關(guān)資料,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語(yǔ)言有一定的幫助,需要的可以參考一下

GO的鎖和原子操作分享

上次我們說(shuō)到協(xié)程,我們?cè)賮?lái)回顧一下:

  • 協(xié)程類似線程,是一種更為輕量級(jí)的調(diào)度單位
  • 線程是系統(tǒng)級(jí)實(shí)現(xiàn)的,常見的調(diào)度方法是時(shí)間片輪轉(zhuǎn)法
  • 協(xié)程是應(yīng)用軟件級(jí)實(shí)現(xiàn),原理與線程類似
  • 協(xié)程的調(diào)度基于 GPM 模型實(shí)現(xiàn)

要是對(duì)協(xié)程的使用感興趣的話,可以看看這篇文章簡(jiǎn)單了解一下瞅一眼就會(huì)使用GO的并發(fā)編程分享

今天我們來(lái)聊聊GO里面的鎖

鎖是什么

鎖 是用于解決隔離性的一種機(jī)制

某個(gè)協(xié)程(線程)在訪問(wèn)某個(gè)資源時(shí)先鎖住,防止其它協(xié)程的訪問(wèn),等訪問(wèn)完畢解鎖后其他協(xié)程再來(lái)加鎖進(jìn)行訪問(wèn)

在我們生活中,我們應(yīng)該不會(huì)陌生,鎖是這樣的

本意是指置于可啟閉的器物上,以鑰匙或暗碼開啟,引申義是用鎖鎖住、封閉

生活中用到的鎖

上鎖基本是為了防止外人進(jìn)來(lái)、防止自己財(cái)物被盜

編程語(yǔ)言中的鎖

鎖的種類更是多種多樣,每種鎖的加鎖開銷以及應(yīng)用場(chǎng)景也不盡相同

鎖是用來(lái)做什么的

用來(lái)控制各個(gè)協(xié)程的同步,防止資源競(jìng)爭(zhēng)導(dǎo)致錯(cuò)亂問(wèn)題

在高并發(fā)的場(chǎng)景下,如果選對(duì)了合適的鎖,則會(huì)大大提高系統(tǒng)的性能,否則性能會(huì)降低。

那么知道各種鎖的開銷,以及應(yīng)用場(chǎng)景很有必要

GO中的鎖有哪些?

  • 互斥鎖
  • 讀寫鎖

我們?cè)诰幋a中會(huì)存在多個(gè) goroutine 協(xié)程同時(shí)操作一個(gè)資源(臨界區(qū)),這種情況會(huì)發(fā)生競(jìng)態(tài)問(wèn)題(數(shù)據(jù)競(jìng)態(tài)

舉一個(gè)生活中的例子

生活中最明顯的例子就是,大家搶著上廁所,資源有限,只能一個(gè)一個(gè)的用

舉一個(gè)編碼中的例子

package main

import (
	"fmt"
	"sync"
)

// 全局變量
var num int64
var wg sync.WaitGroup

func add() {
	for i := 0; i < 10000000; i++ {
		num = num + 1
	}
	// 協(xié)程退出, 記錄 -1
	wg.Done()
}
func main() {
	// 啟動(dòng)2個(gè)協(xié)程,記錄 2
	wg.Add(2)

	go add()
	go add()

	// 等待子協(xié)程退出
	wg.Wait()
	fmt.Println(num)
}

按照上述代碼,我們的輸出結(jié)果應(yīng)該是 20000000,每一個(gè)協(xié)程計(jì)算 10000000 次,可是實(shí)際結(jié)果卻是

10378923

每一次計(jì)算的結(jié)果還不一樣,出現(xiàn)這個(gè)問(wèn)題的原因就是上述提到的資源競(jìng)爭(zhēng)

兩個(gè) goroutine 協(xié)程在訪問(wèn)和修改num變量,會(huì)存在2個(gè)協(xié)程同時(shí)對(duì)num+1 , 最終num 總共只加了 1 ,而不是 2

這就導(dǎo)致最后的結(jié)果與期待的不符,那么我們?nèi)绾谓鉀Q呢?

我們當(dāng)然是用鎖控制同步了,保證各自協(xié)程在操作臨界區(qū)資源的時(shí)候,先確實(shí)是否拿到鎖,只有拿到鎖了才能進(jìn)行對(duì)臨界區(qū)資源的修改

先來(lái)看看互斥鎖

互斥鎖

互斥鎖的簡(jiǎn)單理解就像上述我們講到上廁所的案例一樣,同一時(shí)間點(diǎn),只能有一個(gè)人在使用其他人只能排隊(duì)等待

在編程中,引入了對(duì)象互斥鎖的概念,來(lái)保證共享數(shù)據(jù)操作的完整性

每個(gè)對(duì)象都對(duì)應(yīng)于一個(gè)可稱為互斥鎖的標(biāo)記,這個(gè)標(biāo)記用來(lái)保證在任一時(shí)刻,只能有一個(gè)協(xié)程訪問(wèn)該對(duì)象。

應(yīng)用場(chǎng)景

寫大于讀操作的

它代表的資源就是一個(gè),不管是讀者還是寫者,只要誰(shuí)擁有了它,那么其他人就只有等待解鎖后

我們來(lái)使用互斥鎖解決上述的問(wèn)題

互斥鎖 - 解決問(wèn)題

互斥鎖是一種常用的控制共享資源訪問(wèn)的方法,它能夠保證同時(shí)只有一個(gè) goroutine 協(xié)程可以訪問(wèn)共享資源

Go 中使用到如下 1個(gè)知識(shí)點(diǎn)來(lái)解決

sync包Mutex類型 來(lái)實(shí)現(xiàn)互斥鎖

package main

import (
   "fmt"
   "sync"
)

// 全局變量
var num int64
var wg sync.WaitGroup
var lock sync.Mutex

func add() {
   for i := 0; i < 10000000; i++ {
      // 訪問(wèn)資源前  加鎖
      lock.Lock()
      num = num + 1
      // 訪問(wèn)資源后  解鎖
      lock.Unlock()
   }
   // 協(xié)程退出, 記錄 -1
   wg.Done()
}
func main() {
   // 啟動(dòng)2個(gè)協(xié)程,記錄 2
   wg.Add(2)

   go add()
   go add()

   // 等待子協(xié)程退出
   wg.Wait()
   fmt.Println(num)
}

執(zhí)行上述代碼,我們能看到,輸出的結(jié)果與我們預(yù)期的一致

20000000

使用互斥鎖能夠保證同一時(shí)間有且只有一個(gè)goroutine 協(xié)程進(jìn)入臨界區(qū),其他的goroutine則在等待鎖

當(dāng)互斥鎖釋放后,等待的 goroutine 協(xié)程才可以獲取鎖進(jìn)入臨界區(qū)

如何知道哪一個(gè)協(xié)程是先被喚醒呢?

可是,多個(gè)goroutine 協(xié)程同時(shí)等待一個(gè)鎖時(shí),如何知道哪一個(gè)協(xié)程是先被喚醒呢?

互斥鎖這里的喚醒的策略是隨機(jī)的,并不知道到底是先喚醒誰(shuí)

讀寫鎖

為什么有了互斥鎖 ,還要讀寫鎖呢?

很明顯就是互斥鎖不能滿足所有的應(yīng)用場(chǎng)景,就催生出了讀寫鎖,我們細(xì)細(xì)道來(lái)

互斥鎖是完全互斥的,不管協(xié)程是讀臨界區(qū)資源還是寫臨界區(qū)資源,都必須要拿到鎖,否則就無(wú)法操作(這個(gè)限制太死了對(duì)嗎)

可是在我們實(shí)際的應(yīng)用場(chǎng)景下是讀多寫少

若我們并發(fā)的去讀取一個(gè)資源,且不對(duì)資源做任何修改的時(shí)候如果也要加鎖才能讀取數(shù)據(jù),是不是就很沒有必要呢

這種場(chǎng)景下讀寫鎖就發(fā)揮作用了,他就相對(duì)靈活了,也很好的解決了讀多寫少的場(chǎng)景問(wèn)題

讀寫鎖的種類

  • 讀鎖
  • 寫鎖

當(dāng)一個(gè)goroutine 協(xié)程獲取讀鎖之后,其他的 goroutine 協(xié)程如果是獲取讀鎖會(huì)繼續(xù)獲得鎖

可如果是獲取寫鎖就必須等待

當(dāng)一個(gè) goroutine 協(xié)程獲取寫鎖之后,其他的goroutine 協(xié)程無(wú)論是獲取讀鎖還是寫鎖都會(huì)等待

我們先來(lái)寫一個(gè)讀寫鎖的DEMO

Go 中使用到如下 1個(gè)知識(shí)點(diǎn)來(lái)解決

sync包RWMutex類型 來(lái)實(shí)現(xiàn)讀寫鎖

package main

import (
   "fmt"
   "sync"
   "time"
)

var (
   num    int64
   wg     sync.WaitGroup
   //lock   sync.Mutex
   rwlock sync.RWMutex
)

func write() {
   // 加互斥鎖
   // lock.Lock()

   // 加寫鎖
   rwlock.Lock()

   num = num + 1
   // 模擬真實(shí)寫數(shù)據(jù)消耗的時(shí)間
   time.Sleep(10 * time.Millisecond)

   // 解寫鎖
   rwlock.Unlock()

   // 解互斥鎖
   // lock.Unlock()

   // 退出協(xié)程前 記錄 -1
   wg.Done()
}

func read() {
   // 加互斥鎖
   // lock.Lock()

   // 加讀鎖
   rwlock.RLock()

   // 模擬真實(shí)讀取數(shù)據(jù)消耗的時(shí)間
   time.Sleep(time.Millisecond)

   // 解讀鎖
   rwlock.RUnlock()

   // 解互斥鎖
   // lock.Unlock()

   // 退出協(xié)程前 記錄 -1
   wg.Done()
}

func main() {
   // 用于計(jì)算時(shí)間 消耗
   start := time.Now()

   // 開5個(gè)協(xié)程用作 寫
   for i := 0; i < 5; i++ {
      wg.Add(1)
      go write()
   }

   // 開500 個(gè)協(xié)程,用作讀
   for i := 0; i < 1000; i++ {
      wg.Add(1)
      go read()
   }

   // 等待子協(xié)程退出
   wg.Wait()
   end := time.Now()

   // 打印程序消耗的時(shí)間
   fmt.Println(end.Sub(start))
}

我們開5個(gè)協(xié)程用于寫,開1000個(gè)協(xié)程用于讀,使用讀寫鎖加鎖,結(jié)果耗時(shí) 54.4871ms 如下

54.4871ms

如果我們將上述代碼修改成加 互斥鎖,運(yùn)行之后的結(jié)果是 1.7750029s 如下

1.7750029s

是不是結(jié)果相差很大呢,對(duì)于不同的場(chǎng)景應(yīng)用不同的鎖,對(duì)于我們的程序性能影響也是很大,當(dāng)然上述結(jié)果,若讀協(xié)程,和寫協(xié)程的個(gè)數(shù)差距越大,結(jié)果就會(huì)越懸殊

我們總結(jié)一下這一小塊的邏輯:

  • 寫者是排他性的,一個(gè)讀寫鎖同時(shí)只能有一個(gè)寫者或多個(gè)讀者
  • 不能同時(shí)既有讀者又有寫者
  • 如果讀寫鎖當(dāng)前沒有讀者,也沒有寫者,那么寫者可以立刻獲得讀寫鎖,否則它必須自旋在那里,直到?jīng)]有任何寫者或讀者。
  • 如果讀寫鎖沒有寫者,那么讀者可以立即獲得該讀寫鎖,否則讀者必須自旋在那里,直到寫者釋放該讀寫鎖。

上述提了自旋鎖,我們來(lái)簡(jiǎn)單解釋一下,什么是自旋鎖

自旋鎖是專為防止多處理器并發(fā)而引入的一種鎖,它在內(nèi)核中大量應(yīng)用于中斷處理等部分(對(duì)于單處理器來(lái)說(shuō),防止中斷處理中的并發(fā)可簡(jiǎn)單采用關(guān)閉中斷的方式,即在標(biāo)志寄存器中關(guān)閉/打開中斷標(biāo)志位,不需要自旋鎖)。

簡(jiǎn)單來(lái)說(shuō),在并發(fā)過(guò)程中,若其中一個(gè)協(xié)程拿不到鎖,他會(huì)不停的去嘗試拿鎖,不停的去看能不能拿,而不是阻塞睡眠

自旋鎖和互斥鎖的區(qū)別

互斥鎖

當(dāng)拿不到鎖的時(shí)候,會(huì)阻塞等待,會(huì)睡眠,等待鎖釋放后被喚醒

自旋鎖

當(dāng)拿不到鎖的時(shí)候,會(huì)在原地不停的看能不能拿到鎖,所以叫做自旋,他不會(huì)阻塞,不會(huì)睡眠

如何選擇鎖

對(duì)于 C/C++ 而言

  • 若加鎖后的業(yè)務(wù)操作消耗,大于互斥鎖阻塞后切換上下文的消耗 ,那么就選擇互斥鎖
  • 若加鎖后的業(yè)務(wù)操作消耗,小于互斥鎖阻塞后切換上下文的消耗,那么選擇自旋鎖

對(duì)于 GO 而言

  • 若寫的頻次大大的多余讀的頻次,那么選擇互斥鎖
  • 若讀的頻次大大的多余寫的頻次,那么選擇讀寫鎖

我們都是對(duì)自身要求比較高的同學(xué),那么有沒有比鎖還好用的東西呢

自然是有的,我們來(lái)看看原子操作

啥是原子操作

"原子操作(atomic operation)是不需要synchronized",這是多線程編程的老生常談了。所謂原子操作是指不會(huì)被線程調(diào)度機(jī)制打斷的操作

這種操作一旦開始,就一直運(yùn)行到結(jié)束,中間不會(huì)有任何 context switch (切換到另一個(gè)線程)。

原子操作的特性:

原子操作是不可分割的,在執(zhí)行完畢之前不會(huì)被任何其它任務(wù)或事件中斷

上述我們的加鎖案例,咱們編碼中的加鎖操作會(huì)涉及內(nèi)核態(tài)的上下文切換會(huì)比較耗時(shí)、代價(jià)比較高

針對(duì)基本的數(shù)據(jù)類型我們還可以使用原子操作來(lái)保證并發(fā)安全

因?yàn)樵硬僮魇荊o語(yǔ)言提供的方法它在用戶態(tài)就可以完成,因此性能比加鎖操作更好

不用我們自己寫匯編,這里 GO 也提供了原子操作的包,供我們一起來(lái)使用 sync/atomic

我們對(duì)上述的案例做一個(gè)延伸

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

var num int64
var l sync.Mutex
var wg sync.WaitGroup

// 普通版加函數(shù)
func add() {
	num = num + 1
	wg.Done()
}

// 互斥鎖版加函數(shù)
func mutexAdd() {
	l.Lock()
	num = num + 1
	l.Unlock()
	wg.Done()
}

// 原子操作版加函數(shù)
func atomicAdd() {
	atomic.AddInt64(&num, 1)
	wg.Done()
}

func main() {
	// 目的是 記錄程序消耗時(shí)間
	start := time.Now()
	for i := 0; i < 20000; i++ {

		wg.Add(1)

		// go add()       // 無(wú)鎖的  add函數(shù) 不是并發(fā)安全的
		// go mutexAdd()  // 互斥鎖的 add函數(shù) 是并發(fā)安全的,因?yàn)槟貌坏交コ怄i會(huì)阻塞,所以加鎖性能開銷大

		go atomicAdd()    // 原子操作的 add函數(shù) 是并發(fā)安全,性能優(yōu)于加鎖的
	}

	// 等待子協(xié)程 退出
	wg.Wait()

	end := time.Now()
	fmt.Println(num)
	// 打印程序消耗時(shí)間
	fmt.Println(end.Sub(start))
}

我們使用上述 demo 代碼,模擬了3種情況下,程序的耗時(shí)以及計(jì)算結(jié)果對(duì)比

不加鎖

無(wú)鎖的 add函數(shù) 不是并發(fā)安全的

19495
11.9474ms

加互斥鎖

互斥鎖的 add函數(shù) 是并發(fā)安全的,因?yàn)槟貌坏交コ怄i會(huì)阻塞,所以加鎖性能開銷大

20000
14.9586ms

使用原子操作

原子操作的 add函數(shù) 是并發(fā)安全,性能優(yōu)于加鎖的

20000
9.9726ms

總結(jié)

  • 分享了鎖是什么,用來(lái)做什么
  • 分享了互斥鎖,讀寫鎖,以及其區(qū)別和應(yīng)用場(chǎng)景
  • 分享了原子操作
  • 大家感興趣可以去看看鎖的實(shí)現(xiàn),里面也是有使用原子操作

以上就是GO的鎖和原子操作的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于GO鎖 原子操作的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go?Fiber快速搭建一個(gè)HTTP服務(wù)器

    Go?Fiber快速搭建一個(gè)HTTP服務(wù)器

    Fiber?是一個(gè)?Express?啟發(fā)?web?框架基于?fasthttp?,最快?Go?的?http?引擎,這篇文章主要介紹了Go?Fiber快速搭建一個(gè)HTTP服務(wù)器,需要的朋友可以參考下
    2023-06-06
  • Go簡(jiǎn)單實(shí)現(xiàn)協(xié)程池的實(shí)現(xiàn)示例

    Go簡(jiǎn)單實(shí)現(xiàn)協(xié)程池的實(shí)現(xiàn)示例

    本文主要介紹了Go簡(jiǎn)單實(shí)現(xiàn)協(xié)程池的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • Go語(yǔ)言使用HTTP包創(chuàng)建WEB服務(wù)器的方法

    Go語(yǔ)言使用HTTP包創(chuàng)建WEB服務(wù)器的方法

    這篇文章主要介紹了Go語(yǔ)言使用HTTP包創(chuàng)建WEB服務(wù)器的方法,結(jié)合實(shí)例形式分析了Go語(yǔ)言基于HTTP包創(chuàng)建WEB服務(wù)器客戶端與服務(wù)器端的實(shí)現(xiàn)方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2016-07-07
  • 自動(dòng)生成代碼controller?tool的簡(jiǎn)單使用

    自動(dòng)生成代碼controller?tool的簡(jiǎn)單使用

    這篇文章主要為大家介紹了自動(dòng)生成代碼controller?tool的簡(jiǎn)單使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • Go語(yǔ)言學(xué)習(xí)教程之反射的示例詳解

    Go語(yǔ)言學(xué)習(xí)教程之反射的示例詳解

    這篇文章主要通過(guò)記錄對(duì)reflect包的簡(jiǎn)單使用,來(lái)對(duì)反射有一定的了解。文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語(yǔ)言有一定幫助,需要的可以參考一下
    2022-09-09
  • Go語(yǔ)言中嵌入C語(yǔ)言的方法

    Go語(yǔ)言中嵌入C語(yǔ)言的方法

    這篇文章主要介紹了Go語(yǔ)言中嵌入C語(yǔ)言的方法,實(shí)例分析了Go語(yǔ)言中cgo工具的使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-02-02
  • Golang三個(gè)編譯基本命令的使用小結(jié)

    Golang三個(gè)編譯基本命令的使用小結(jié)

    本文主要介紹了Golang三個(gè)編譯基本命令的使用小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • Go中Channel發(fā)送和接收操作指南

    Go中Channel發(fā)送和接收操作指南

    在golang中channel屬于較為核心的一個(gè)功能,尤其在go協(xié)程中,channel功能尤為重要,下面這篇文章主要給大家介紹了關(guān)于Go中Channel發(fā)送和接收操作的相關(guān)資料,需要的朋友可以參考下
    2021-08-08
  • Go語(yǔ)言sync包與鎖實(shí)現(xiàn)限制線程對(duì)變量的訪問(wèn)

    Go語(yǔ)言sync包與鎖實(shí)現(xiàn)限制線程對(duì)變量的訪問(wèn)

    本文主要介紹了Go語(yǔ)言sync包與鎖實(shí)現(xiàn)限制線程對(duì)變量的訪問(wèn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • Golang回調(diào)函數(shù)與閉包和接口函數(shù)的定義及使用介紹

    Golang回調(diào)函數(shù)與閉包和接口函數(shù)的定義及使用介紹

    這篇文章主要介紹了Golang回調(diào)函數(shù)與閉包和接口函數(shù)的定義及使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧
    2023-05-05

最新評(píng)論