0
点赞
收藏
分享

微信扫一扫

go 并发编程

就是耍帅 2022-02-28 阅读 94

并发编程就是可以让你的程序不是顺序执行的,而是可以多个分支同时进行,在go中,这个分支也被称为协程goroutine(轻量级线程)。

Go语言中使用goroutine非常简单,只需要在调用函数的时候在前面加上go关键字,就可以为一个函数创建一个goroutine。

一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数。

在程序启动时,Go程序就会为main()函数创建一个默认的goroutine。main函数结束,所有的goroutine都会结束。

package main

import (
	"fmt"
	"sync"
)

var total struct{
	sync.Mutex
	value int
}

func worker(wg *sync.WaitGroup) {
	defer wg.Done() //计数器值减一

	for i := 0; i < 100; i++ {
		total.Lock() //加锁,其他线程想加锁会发生阻塞,保证加锁范围内的语句同一时刻只会有一个线程访问
		total.value += i
		total.Unlock() //解锁
	}
}

func main()  {
	var wg sync.WaitGroup //计数器,初始值0
	wg.Add(2) //对计数器进行加2
	wg.Add(1)
	go worker(&wg) //开启go routine
	go worker(&wg) //计数器要传引用
	go worker(&wg)
	wg.Wait() //阻塞,知道计数器值变为0

	fmt.Println(total.value)
}

上例中可以看到有加锁语句,对多线程模型的程序而言,原子性操作进行加锁和解锁都是有必要的。所谓的原子性操作,就是并发编程中“最小的且不可并行化的”操作。假如上文中的total.value += i同时有两个线程执行这句,假如此时value=2,i都为5,则执行这两句后,得到的结果为7,而不是12,所以可能会导致结果不正确。我们也可用sync/atomic包来进行原子性操作(推荐)

func worker(wg *sync.WaitGroup) {
	defer wg.Done() //计数器值减一

	for i := 0; i < 100; i++ {
		atomic.AddInt64(&total.value, int64(i))
	}
}

顺序一致性内存模型

顺序一致性内存模型有两大特性。

  • 一个线程中所有操作必须按照程序的顺序来执行。
  • (不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。

在Go语言中,同一个Goroutine线程内部,顺序一致性的模型是得到保证的。但是不同的Goroutine之间,并不满足顺序一致性的内存模型。需要通过明确定义的同步事件来作为同步的参考。

goroutine奉行通过通信来共享内存,其中,通道是在goroutine之间进行同步的主要方法。用法如下:

package main

import "fmt"

var chan1 = make(chan bool) //建立一个无缓存通道(通道的缓存大小是0)
var chan2 = make(chan bool)

func work(job string)  {
	fmt.Println("I want to work: ", job)
	chan1 <- true // 无缓存通道的发送操作总在对应的接收操作前发生
}

func study(book string)  {
	fmt.Println("I want to study: ", book)
	close(chan1) //关闭通道后,接收者可以从通道中接收到零值
}

func play(game string)  {
	fmt.Println("I want to play: ", game)
	<- chan2 // 无缓存通道的接收总在对应该通道的发生完成前
}

func main()  {
	go work("程序员")
	go study("go语言")
	go play("王者荣耀")
	<- chan1
	<- chan1
	chan2 <- false
}

对于带缓存的通道,对于通道中的第K个接收完成操作发生在第K+C个发送操作完成之前,其中C是通道的缓存大小。我们可以通过控制通道的缓存大小来控制并发执行的goroutine的最大数目。

例如:(此处仅当示例)

package main

import "fmt"

var chan1 = make(chan bool) //建立一个无缓存通道(通道的缓存大小是0)
var chan2 = make(chan bool)
var chan3 = make(chan bool, 3)

func work(job string)  {
	fmt.Println("I want to work: ", job)
	//chan1 <- true // 无缓存通道的发送操作总在对应的接收操作前发生
	chan3 <- true
}

func study(book string)  {
	fmt.Println("I want to study: ", book)
	//close(chan1) //关闭通道后,接收者可以从通道中接收到零值
	chan3 <- true
}

func play(game string)  {
	fmt.Println("I want to play: ", game)
	//<- chan2 // 无缓存通道的接收总在对应该通道的发生完成前
	chan3 <- true
}

func main()  {
	go work("程序员")
	go study("go语言")
	go play("王者荣耀")
	<- chan3
	<- chan3
	<- chan3
	//<- chan1
	//chan2 <- false
}
举报

相关推荐

0 条评论