Golang并发基础

Golang并发


1.runtime包


1.1 runtime.Gosched()

让出CPU时间片,重新等待安排任务。

调用前:

package main

import (
	"fmt"
)

func main()  {
	go func() {
		for i := 0; i < 2; i++ {
			fmt.Println("子协程在运行。")
		}
	}()
	for i := 0; i < 2; i++ {
		//fmt.Println("主协程让出时间片。")
		//runtime.Gosched()
		fmt.Println("主协程在运行。")
	}
}
主协程在运行。
主协程在运行。

调用后:

package main

import (
	"fmt"
	"runtime"
)

func main()  {
	fmt.Println("主协程让出时间片。")
	go func() {
		for i := 0; i < 2; i++ {
			fmt.Println("子协程在运行。")
		}
	}()
	for i := 0; i < 2; i++ {
		runtime.Gosched()
		fmt.Println("主协程在运行。")
	}
}
主协程让出时间片。
子协程在运行。
子协程在运行。
主协程在运行。
主协程在运行。

1.2 runtime.Goexit()

退出当前协程。

使用前:

package main

import (
	"fmt"
)

func main()  {
	go func() {
		defer fmt.Println("A.defer")
		func() {
			defer fmt.Println("B.defer")
			//结束协程
			//runtime.Goexit()
			defer fmt.Println("C.defer")
			fmt.Println("B")
		} ()
		fmt.Println("B")
	}()
	for {
	}
}
B
C.defer
B.defer
B
A.defer

使用后:

package main

import (
	"fmt"
	"runtime"
)

func main()  {
	go func() {
		defer fmt.Println("A.defer")
		func() {
			defer fmt.Println("B.defer")
			//结束协程
			runtime.Goexit()
			defer fmt.Println("C.defer")
			fmt.Println("B")
		} ()
		fmt.Println("B")
	}()
	for {
	}
}
B.defer
A.defer

1.3 runtime.GOMAXPROCS

Go运行时的调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码。默认值是机器上的CPU核心数。例如在一个8核心的机器上,调度器会把Go代码同时调度到8个OS线程上(GOMAXPROCS是m:n调度中的n)。

Go语言中可以通过runtime.GOMAXPROCS()函数设置当前程序并发时占用的CPU逻辑核心数。

Go1.5版本之前,默认使用的是单核心执行。Go1.5版本之后,默认使用全部的CPU逻辑核心数。

将核心数编辑为1:

package main

import (
	"fmt"
	"runtime"
	"time"
)

func a() {
	for i := 0; i < 10; i++ {
		fmt.Println("A", i)
	}
}

func b() {
	for i := 0; i < 10; i++ {
		fmt.Println("B", i)
	}
}

func main()  {
	runtime.GOMAXPROCS(1)
	go a()
	go b()
	time.Sleep(time.Second)
}
B 0
B 1
B 2
B 3
B 4
B 5
B 6
B 7
B 8
B 9
A 0
A 1
A 2
A 3
A 4
A 5
A 6
A 7
A 8
A 9

将核心数编辑为2:

package main

import (
	"fmt"
	"runtime"
	"time"
)

func a() {
	for i := 0; i < 10; i++ {
		fmt.Println("A", i)
	}
}

func b() {
	for i := 0; i < 10; i++ {
		fmt.Println("B", i)
	}
}

func main()  {
	//runtime.GOMAXPROCS(1)
	runtime.GOMAXPROCS(2)
	go a()
	go b()
	time.Sleep(time.Second)
}
A 0
B 0
B 1
B 2
B 3
B 4
B 5
B 6
B 7
B 8
B 9
A 1
A 2
A 3
A 4
A 5
A 6
A 7
A 8
A 9

2. channel类型

2.1 channel类型

channel是一种引用类型。

声明通道的类型格式如下:

var 变量 chan 元素类型

例如:

var ch1 chan int   // 声明一个传递整型的通道
var ch2 chan bool  // 声明一个传递布尔型的通道
var ch3 chan []int // 声明一个传递int切片的通道

2.2 创建channel

通道是引用类型,所以通道类型的空值是nil。

var ch chan int
fmt.Println(ch) // <nil>

声明的通道后需要使用make函数初始化之后才能使用。

创建channel的格式如下:

//无缓冲
make(chan 元素类型)
//有缓冲
make(chan 元素类型, [缓冲大小])

2.3 channel操作

通道有发送(send)、接收(receive)和关闭(close)三种操作。

发送和接收都使用<-符号。

发送:

ch <- 10 // 把10发送到ch中

接收:

x := <- ch // 从ch中接收值并赋值给变量x
<-ch       // 从ch中接收值,忽略结果

关闭:

close(ch)

注意:
关闭后的通道有以下特点

1.对一个关闭的通道再发送值就会导致panic。
2.对一个关闭的通道进行接收会一直获取值直到通道为空。
3.对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
4.关闭一个已经关闭的通道会导致panic。
5.对一个没有关闭但没有值的通道取值,会导致panic。
6.关闭一个已经关闭的channel会导致panic。

2.4 无缓冲的通道

无缓冲通道在goroutine之间同步:
在这里插入图片描述
无缓冲的通道又称为阻塞的通道。

例如:

package main

import "fmt"

func main()  {
	ch := make(chan int)
	ch <- 10
	fmt.Println("发送成功")
}
fatal error: all goroutines are asleep - deadlock!

以上代码为什么会出错呢?

因为我们使用ch := make(chan int)创建的是无缓冲的通道,无缓冲的通道只有在有人接收值的时候才能发送值。而上面的代码会阻塞在ch <- 10这一行代码形成死锁

一种方法是启用一个goroutine去接收值,比如:

package main

import "fmt"

func recv(c chan int)  {
	ret := <-c
	fmt.Println("接收成功", ret)
}
func main()  {
	ch := make(chan int)
	go recv(ch)
	ch <- 10
	fmt.Println("发送成功")
}
发送成功
接收成功 10

无缓冲通道上的发送操作会阻塞,直到另一个goroutine在该通道上执行接收操作,这时值才能发送成功,两个goroutine将继续执行。相反,如果接收操作先执行,接收方的goroutine将阻塞,直到另一个goroutine在该通道上发送一个值。

使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此,无缓冲通道也被称为同步通道。


2.5 有缓冲的通道

使用有缓冲的通道在goruntine之间同步数据:
在这里插入图片描述
我们可以在使用make函数初始化通道的时候为其指定通道的容量:

package main

import "fmt"

func main()  {
	ch := make(chan int, 1)
	ch <- 10
	fmt.Println("发送成功")
}
发送成功

2.6 close()

可以通过内置的close()函数关闭channel(如果你的管道不往里存值或者取值的时候一定记得关闭管道)

package main

import "fmt"

func main()  {
	c := make(chan int)

	go func() {
		for i := 0; i < 10; i++ {
			c <- i
		}
		close(c)
	}()

	for {
		if data, ok := <- c; ok {
			fmt.Println(data)
		} else {
			break
		}
	}
	fmt.Println("main结束")
}
0
1
2
3
4
5
6
7
8
9
main结束

以上代码如果不close channel,就会引发阻塞,if语句那一行会报错,data已经不能从通道中取到值。


2.7优雅的从通道循环取值

当通过通道发送有限的数据时,我们可以通过close函数关闭通道来告知从该通道接收值的goroutine停止等待。当通道被关闭时,往该通道发送值会引发panic,从该通道里接收的值一直都是类型零值。那如何判断一个通道是否被关闭了呢?

来看看下面这个例子:

package main

import "fmt"

func main()  {
	ch1 := make(chan int)
	ch2 := make(chan int)

	// 开启goroutine将0~100的数发送到ch1中
	go func() {
		for i := 0; i < 100; i++ {
			ch1 <- i
		}
		close(ch1)
	}()

	// 开启goroutine从ch1中接收值,并将该值的平方发送到ch2中
	go func() {
		for {
			i, ok := <-ch1  //通道关闭以后再取值
			if !ok {
				break
			}
			ch2 <- i * i
		}
		close(ch2)
	}()
	//在主goroutine中从ch2中接收值打印
	for i := range ch2 {  //通道关闭以后会退出for range循环
		fmt.Println(i)
	}
}

2.8 单向通道

有的时候我们会将通道作为参数在多个任务函数间传递,很多时候我们在不同的任务函数中使用通道都会对其进行限制,比如限制通道在函数中只能发送或只能接收。Go语言中提供了单向通道来处理这种情况。

func counter(out chan<- int) {
    for i := 0; i < 100; i++ {
        out <- i
    }
    close(out)
}

func squarer(out chan<- int, in <-chan int) {
    for i := range in {
        out <- i * i
    }
    close(out)
}
func printer(in <-chan int) {
    for i := range in {
        fmt.Println(i)
    }
}

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    go counter(ch1)
    go squarer(ch2, ch1)
    printer(ch2)
}

2.9 通道总结

在这里插入图片描述

3.goroutine池

本质上是生产者和消费者模型,优点是可以有效控制goroutine数量,防止暴涨。

work.go

package main

import "sync"

// Worker 任务类型接口
type Worker interface {
	Task(goid int)
}

// Pool 任务池
type Pool struct {
	//创建work类型的管道
	work chan Worker
	wg sync.WaitGroup
}

// New 新建
func New(maxGoroutines int) *Pool {
	//任务池
	p := Pool{
		work: make(chan Worker),
	}
	p.wg.Add(maxGoroutines)
	//创建maxGoroutines个go协程
	for i := 0; i < maxGoroutines; i++ {
		go func(goid int) {
			//保证goroutine不停止执行通道中的任务
			for w := range p.work {
				w.Task(goid)
			}
			//每个goroutine不再执行work通道中任务时停止
			p.wg.Done()
		}(i)
	}
	return &p
}

// Run 运行
func (p *Pool) Run(r Worker)  {
	p.work <- r
}

// Shutdown 停止
func (p *Pool) Shutdown()  {
	close(p.work)
	p.wg.Wait()
}

main.go

package main

import (
	"log"
	"sync"
	"time"
)

//
var names = []string{
	"lili",
	"yingying",
}

//Worker实现类型
type namePrinter struct {
	name string
}

func (n *namePrinter) Task(goid int) {
	log.Printf("goroutineID:%d,打印名字为:%s\n", goid, n.name)
	time.Sleep(time.Second)
}

func main() {
	p := New(3)
	var wg sync.WaitGroup
	wg.Add(10 * len(names))

	for i := 0; i < 10; i++ {
		for _, name := range names {
			//任务实例
			np := namePrinter{
				name: name,
			}

			go func() {
				p.Run(&np)
				wg.Done()
			}()
		}
	}
	wg.Wait()
	p.Shutdown()
}
2021/10/27 16:12:25 goroutineID:0,打印名字为:yingying
2021/10/27 16:12:25 goroutineID:1,打印名字为:lili
2021/10/27 16:12:25 goroutineID:2,打印名字为:yingying
2021/10/27 16:12:26 goroutineID:0,打印名字为:lili
2021/10/27 16:12:26 goroutineID:2,打印名字为:yingying
2021/10/27 16:12:26 goroutineID:1,打印名字为:yingying
2021/10/27 16:12:27 goroutineID:0,打印名字为:lili
2021/10/27 16:12:27 goroutineID:2,打印名字为:lili
2021/10/27 16:12:27 goroutineID:1,打印名字为:yingying
2021/10/27 16:12:28 goroutineID:1,打印名字为:yingying
2021/10/27 16:12:28 goroutineID:2,打印名字为:lili
2021/10/27 16:12:28 goroutineID:0,打印名字为:yingying
2021/10/27 16:12:29 goroutineID:1,打印名字为:lili
2021/10/27 16:12:29 goroutineID:0,打印名字为:yingying
2021/10/27 16:12:29 goroutineID:2,打印名字为:lili
2021/10/27 16:12:30 goroutineID:1,打印名字为:yingying
2021/10/27 16:12:30 goroutineID:0,打印名字为:lili
2021/10/27 16:12:30 goroutineID:2,打印名字为:yingying
2021/10/27 16:12:31 goroutineID:1,打印名字为:lili
2021/10/27 16:12:31 goroutineID:2,打印名字为:lili

参考自:https://www.cnblogs.com/limaosheng/p/11070819.html


4.select


4.1 select多路复用

在某些场景下我们需要同时从多个通道接收数据。通道在接收数据时,如果没有数据可以接收将会发生阻塞。为了应对这种场景,Go内置了select关键字,可以同时响应多个通道的操作。
select的使用类似于switch语句,它有一系列case分支和一个默认的分支。每个case会对应一个通道的通信(接收或发送)过程。select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句。


具体格式如下:

select {
    case <-chan1:
       // 如果chan1成功读到数据,则进行该case处理语句
    case chan2 <- 1:
       // 如果成功向chan2写入数据,则进行该case处理语句
    default:
       // 如果上面都没有成功,则进入default处理流程
    }

select可以同时监听一个或多个channel,直到其中一个channel ready

package main

import (
	"fmt"
	"time"
)

func test1(ch chan string)  {
	time.Sleep(time.Second * 5)
	ch <- "test1"
}

func test2(ch chan string)  {
	time.Sleep(time.Second * 2)
	ch <- "test2"
}

func main()  {
	//开两个管道
	output1 := make(chan string)
	output2 := make(chan string)
	//跑两个子协程
	go test1(output1)
	go test2(output2)
	//用select监控
	select {
		case s1 := <- output1 :
			fmt.Println("s1 = ", s1)
		case s2 := <-output2:
			fmt.Println("s2 = ", s2)
	}
}
s2 =  test2

如果多个channel同时ready,则随机选择一个执行

package main

import "fmt"

func receString(ch chan string)  {
	ch <- "hello"
}

func receInt(ch chan int)  {
	ch <- 123
}

func main()  {
	//创建两个通道
	ch_string := make(chan string, 1)
	ch_int := make(chan int, 1)

	//创建两个协程
	go receString(ch_string)
	go receInt(ch_int)

	//用select监控
	select {
		case s1 := <-ch_string :
			fmt.Println(s1)
		case s2 := <- ch_int :
			fmt.Println(s2)
	}
}
hello

可以用于判断管道是否存满

package main

import (
	"fmt"
	"time"
)

func main()  {
	//创建管道
	output1 := make(chan string, 5)

	//子协程写数据
	go write(output1)

	//取数据
	for s := range output1 {
		fmt.Println("res", s)
		time.Sleep(time.Second)
	}
}

func write(ch chan string) {
	//defer wg.Done()
	for {
		select {
			//写数据
			case ch <- "hello":
				fmt.Println("write hello")
			default:
				fmt.Println("channel full")
		}
		time.Sleep(time.Millisecond * 500)
	}
}
write hello
res hello
write hello
res hello
write hello
write hello
res hello
write hello
write hello
write hello
res hello
write hello
res hello
write hello
write hello
res hello
write hello
channel full
res hello
write hello
channel full
...

5.并发安全和锁


5.1 未加锁

package main

import (
	"fmt"
	"sync"
)

var x int64
var wg sync.WaitGroup

func add()  {
	for i := 0; i < 5000; i++ {
		x = x + 1
	}
	wg.Done()
}

func main()  {
	wg.Add(2)

	go add()
	go add()

	wg.Wait()
	fmt.Println(x)
}
5561

5.2 互斥锁


互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个goroutine可以访问共享资源。Go语言中使用sync包的Mutex类型来实现互斥锁。

package main

import (
	"fmt"
	"sync"
)

var x int64
var wg sync.WaitGroup
var lock sync.Mutex

func add()  {
	for i := 0; i < 5000; i++ {
		lock.Lock()
		x = x + 1
		lock.Unlock()
	}
	wg.Done()
}

func main()  {
	wg.Add(2)

	go add()
	go add()

	wg.Wait()
	fmt.Println(x)
}

5.3 读写互斥锁


读写锁非常适合读多写少的场景,如果读和写的操作差别不大,读写锁的优势就发挥不出来。

package main

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

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

func write()  {
	//lock.Lock()  //加互斥锁
	rwlock.Lock()  //加写锁
	x = x + 1
	time.Sleep(10 * time.Millisecond)  //假设操作耗时10毫秒
	//lock.Unlock()  //解互斥锁
	rwlock.Unlock()   //解写锁
	wg.Done()
}

func read()  {
	//lock.Lock()  //加互斥锁
	rwlock.RLock()
	x = x + 1
	time.Sleep(10 * time.Millisecond)
	//lock.Unlock()
	rwlock.RUnlock()
	wg.Done()
}

func main()  {
	start := time.Now()
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go write()
	}

	for i := 0; i < 10; i++ {
		wg.Add(1)
		go read()
	}

	wg.Wait()
	end := time.Now()
	fmt.Println(end.Sub(start))
}

加读写锁:

114.6093ms

加互斥锁

208.023ms

6.Sync


6.1 sync.WaitGroup

Go语言中可以使用sync.WaitGroup来实现并发任务的同步。

sync.WaitGroup有以下几个方法:

在这里插入图片描述
sync.WaitGroup内部维护着一个计数器,计数器的值可以增加和减少。例如当我们启动了N 个并发任务时,就将计数器值增加N。每个任务完成时通过调用Done()方法将计数器减1。通过调用Wait()来等待并发任务执行完,当计数器值为0时,表示所有并发任务已经完成。

var wg sync.WaitGroup

func hello() {
    defer wg.Done()
    fmt.Println("Hello Goroutine!")
}
func main() {
    wg.Add(1)
    go hello() // 启动另外一个goroutine去执行hello函数
    fmt.Println("main goroutine done!")
    wg.Wait()
}

需要注意sync.WaitGroup是一个结构体,传递的时候要传递指针。


6.2 sync.Once


在编程的很多场景下我们需要确保某些操作在高并发的场景下只执行一次,例如只加载一次配置文件、只关闭一次通道等。

Go语言中的sync包中提供了一个针对只执行一次场景的解决方案–sync.Once。

sync.Once只有一个Do方法,其签名如下:

func (o *Once) Do(f func()) {}

例如:

package main

import (
	"fmt"
	"sync"
)

func main()  {
	var once sync.Once
	onceBody := func() {
		fmt.Println("Only once")
	}
	done := make(chan bool)
	for i := 0; i < 10; i++ {
		go func() {
			once.Do(onceBody)   //传进一个函数对象
			done <- true
		}()
	}
	for i := 0; i < 10; i++ {
		<- done
	}
}
Only once

sync.Once 使用变量 done 来记录函数的执行状态,使用 sync.Mutex 和 sync.atomic 来保证线程安全的读取 done 。


源码

package sync

import (
	"sync/atomic"
)

// Once is an object that will perform exactly one action.
type Once struct {
	m    Mutex
	done uint32
}

// 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()) {
	if atomic.LoadUint32(&o.done) == 1 {
		return
	}
	// Slow-path.
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}

6.3 sync.Map


Go语言中内置的map不是并发安全的。

例如:

package main

import (
	"fmt"
	"strconv"
	"sync"
)

var m = make(map[string]int)

func get(key string) int {
	return m[key]
}

func set(key string, value int)  {
	m[key] = value
}

func main()  {
	wg := sync.WaitGroup{}
	for i := 0; i < 20; i++ {
		wg.Add(1)
		go func(n int) {
			key := strconv.Itoa(n)
			set(key, n)
			fmt.Printf("k=%v, v=%v\n", key, get(key))
			wg.Done()
		}(i)
	}
	wg.Wait()
}
fatal error: concurrent map writes

像这种场景下就需要为map加锁来保证并发的安全性了,Go语言的sync包中提供了一个开箱即用的并发安全版map–sync.Map。开箱即用表示不用像内置的map一样使用make函数初始化就能直接使用。同时sync.Map内置了诸如Store、Load、LoadOrStore、Delete、Range等操作方法。

package main

import (
	"fmt"
	"strconv"
	"sync"
)

var m = sync.Map{}

func main()  {
	wg := sync.WaitGroup{}
	for i := 0; i < 20; i++ {
		wg.Add(1)
		go func(n int) {
			key := strconv.Itoa(n)
			m.Store(key, n)
			value, _ := m.Load(key)
			fmt.Printf("k=%v,v=%v\n\n", key, value)
			wg.Done()
		}(i)
	}
	wg.Wait()
}
k=0,v=0
k=17,v=17
k=5,v=5
k=14,v=14
k=16,v=16
k=13,v=13
k=8,v=8
k=9,v=9
k=6,v=6
k=11,v=11
k=2,v=2
k=19,v=19
k=10,v=10
k=4,v=4
k=15,v=15
k=12,v=12
k=3,v=3
k=1,v=1
k=18,v=18
k=7,v=7

底层原理:https://blog.csdn.net/u010853261/article/details/103848666


7. (atomic)原子操作

代码中的加锁操作因为涉及内核态的上下文切换会比较耗时、代价比较高。针对基本数据类型我们还可以使用原子操作来保证并发安全,因为原子操作是Go语言提供的方法它在用户态就可以完成,因此性能比加锁操作更好。Go语言中原子操作由内置的标准库sync/atomic提供。

7.1 atomic包

// SwapInt32以原子方式将new存储到*addr中,并返回之前的*addr值。
func SwapInt32(addr *int32, new int32) (old int32)

// SwapInt64以原子方式将new存储到*addr中,并返回之前的*addr值。
func SwapInt64(addr *int64, new int64) (old int64)

// SwapUint32原子存储new到*addr中并返回前一个*addr值。
func SwapUint32(addr *uint32, new uint32) (old uint32)

// SwapUint64原子存储new到*addr,返回前*addr值。
func SwapUint64(addr *uint64, new uint64) (old uint64)

// SwapUintptr原子地将new存储到*addr中,并返回之前的*addr值。
func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)

// SwapPointer自动地将new存储到*addr中,并返回先前的*addr值。
func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)

// CompareAndSwapInt32对一个int32值执行比较和交换操作。
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)

// CompareAndSwapInt64 executes the compare-and-swap operation for an int64 value.
func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)

// CompareAndSwapUint32 executes the compare-and-swap operation for a uint32 value.
func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)

// CompareAndSwapUint64 executes the compare-and-swap operation for a uint64 value.
func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)

// CompareAndSwapUintptr executes the compare-and-swap operation for a uintptr value.
func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)

// CompareAndSwapPointer executes the compare-and-swap operation for a unsafe.Pointer value.
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)

// AddInt32 atomically adds delta to *addr and returns the new value.
func AddInt32(addr *int32, delta int32) (new int32)

// AddUint32 atomically adds delta to *addr and returns the new value.
// To subtract a signed positive constant value c from x, do AddUint32(&x, ^uint32(c-1)).
// In particular, to decrement x, do AddUint32(&x, ^uint32(0)).
func AddUint32(addr *uint32, delta uint32) (new uint32)

// AddInt64 atomically adds delta to *addr and returns the new value.
func AddInt64(addr *int64, delta int64) (new int64)

// AddUint64 atomically adds delta to *addr and returns the new value.
// To subtract a signed positive constant value c from x, do AddUint64(&x, ^uint64(c-1)).
// In particular, to decrement x, do AddUint64(&x, ^uint64(0)).
func AddUint64(addr *uint64, delta uint64) (new uint64)

// AddUintptr atomically adds delta to *addr and returns the new value.
func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)

// LoadInt32 atomically loads *addr.
func LoadInt32(addr *int32) (val int32)

// LoadInt64 atomically loads *addr.
func LoadInt64(addr *int64) (val int64)

// LoadUint32 atomically loads *addr.
func LoadUint32(addr *uint32) (val uint32)

// LoadUint64 atomically loads *addr.
func LoadUint64(addr *uint64) (val uint64)

// LoadUintptr atomically loads *addr.
func LoadUintptr(addr *uintptr) (val uintptr)

// LoadPointer atomically loads *addr.
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)

// StoreInt32 atomically stores val into *addr.
func StoreInt32(addr *int32, val int32)

// StoreInt64 atomically stores val into *addr.
func StoreInt64(addr *int64, val int64)

// StoreUint32 atomically stores val into *addr.
func StoreUint32(addr *uint32, val uint32)

// StoreUint64 atomically stores val into *addr.
func StoreUint64(addr *uint64, val uint64)

// StoreUintptr atomically stores val into *addr.
func StoreUintptr(addr *uintptr, val uintptr)

// StorePointer atomically stores val into *addr.
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)

7.2 性能对比

通过一个示例比较互斥锁和原子操作的性能。

package main

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

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

//普通版加函数
func add()  {
	x++
	wg.Done()
}

//互斥锁版加函数
func mutexAdd()  {
	l.Lock()
	x++
	l.Unlock()
	wg.Done()
}

//原子操作版加函数
func atomicAdd()  {
	atomic.AddInt64(&x, 1)
	wg.Done()
}

func main()  {
	start := time.Now()

	for i := 0; i < 10000; i++ {
		wg.Add(1)
		go add()
		//go mutexAdd()
		//go atomicAdd()
	}
	wg.Wait()
	end := time.Now()
	fmt.Println(x)
	fmt.Println(end.Sub(start))
}

普通版:

9754
2.978ms

互斥锁版:

10000
4.4646ms

原子操作版:

10000
3.472ms
posted @ 2021-10-27 12:12  Dawnlight-_-  阅读(48)  评论(0)    收藏  举报