并发编程
goroutinegoroutinechannel

1. 协程(goroutine)

goroutinegogoroutinegoroutine

  OS线程(操作系统线程)一般都有固定的栈内存(通常为2MB),一个goroutine的栈在其生命周期开始时只有很小的栈(典型情况下2KB),goroutine的栈不是固定的,他可以按需增大和缩小,goroutine的栈大小限制可以达到1GB,虽然极少会用到这么大。所以在Go语言中一次创建十万左右的goroutine也是可以的。
  参考自https://www.liwenzhou.com/posts/Go/14_concurrence/

1.1. 创建一个goroutine

goroutinehellogoroutine
	package main
	
	import (
		"fmt"
	)
	
	func hello() {
		fmt.Println("hello")
	}
	func main() {
		go hello()
		//time.Sleep(time.Second)
		fmt.Println("main")
	}

time.Sleep()goroutinegoroutinesync.WaitGroup

1.2. sync.WaitGroup

wg.Done()
goroutinehello()goroutinegoroutine
	package main

	import (
		"fmt"
		"time"
	)
	
	func hello(i int) {
		fmt.Println("hello goroutine", i)
	}
	
	func main() {
		for i := 0; i < 100; i++ {
			go hello(i)
			time.Sleep(time.Millisecond)
		}
	}
goroutinegoroutinegoroutinegoroutinegoroutinesleep
sync.WaitGroup
sync.WaitGroupgoroutine
	package main
	
	import (
		"fmt"
		"sync"
	)
	
	func hello(i int) {
		defer wg.Done() // goroutine将hello中任务执行完,计数器减一
		fmt.Println("hello goroutine", i)
	}
	
	var wg sync.WaitGroup
	func main() {
		for i := 0; i < 100; i++ {
			wg.Add(1) // 每开启一个goroutine,计数器加一
			go hello(i)
			//time.Sleep(time.Millisecond)
			wg.Wait() // 等待计数器降为0,再继续执行
		}
		// 更多的业务场景是,多个goroutine和main之间的同步,上述代码只是为了模拟sleep同样的效果。
	}

2. channel

goroutinechannelgoroutinegoroutine

2.1. channel类型

	// 1. 声明
	var ch1 chan int
	var ch2 chan bool
	
	// 2. 创建channel
	make(chan int, 16)
	
	// 3. 将一个值发送到通道
	ch <- 10
	// 4. 从一个通道中接收值
	x := <- ch	// 赋值给x
	<- ch		// 忽略结果
	
	// 5. 关闭通道
	close(ch)
	

2.2. 优雅的操作channel

  1. 循环读取数据
	package main
	
	import "fmt"
	
	func main() {
		ch1 := make(chan int)
		ch2 := make(chan int)
		
		// 开启一个goroutine,把0-24之间的数发送到ch1
		go func() {
			for i := 0; i < 25; i++ {
				ch1 <- i
			}
			close(ch1)
		}()
		
		// 从ch1中取出数据,计算平方,放到ch2
		go func() {
			for {
				x, ok := <- ch1
				if ok {
					ch2 <- x * x
				}
			}
			close(ch2)
		}()
	
		for i := range ch2 {
			fmt.Println(i)
		}
	}

2.3. 单向通道

  有些通道可能只用于发送,或者只用于接收。单向通道多用于函数的参数中。

	func f1(ch1 chan<- int) {
	}
	
	func f2(ch1 <-chan int){}

2.4. 通道注意事项

关闭: 只有在通知接收方goroutine所有的数据都发送完毕的时候才需要关闭通道。通道是可以被GC回收的,它和关闭文件是不一样的,在结束操作之后关闭文件是必须要作的,但关闭通道不是必须的。

2.5. 生产者消费者模型案例

  开启两个协程,一个协程生产数据,另一个协程对数据进行处理,处理完再把数据发回去。

	package main
	
	import (
		"fmt"
		"sync"
	)
	
	var wg sync.WaitGroup
	
	func producer(in chan<- int) {
		defer wg.Done()
		for i := 0; i < 10; i++ {
			in <- i * i
		}
		close(in)
	}
	
	func consumer(out <-chan int) {
		defer wg.Done()
		for {
			x, ok := <-out
			if !ok {
				break
			}
			fmt.Println("num: ", x)
		}
	}
	func main() {
		ch := make(chan int, 16)
		wg.Add(2)
		go producer(ch)
		go consumer(ch)
		wg.Wait()
		fmt.Println("主协程结束...")
	}

3. 锁

3.1. 竞态

  竞态是指在多个goroutine按某些交错顺序执行时,程序无法给出正确的结果。它对于程序是致命的,因为它们潜伏在程序中,出现的频率也很低,有可能仅在高负载环境或者在特定的编译器、平台和架构时才会出现。发生竞态现象的必要条件是:两个goroutine并发读写同一个变量,并且至少其中一个goroutine是写入操作。

  在存款操作和取款操作并发执行的时候,就会发生静态现象。有三种方法可以避免数据静态。

  1. 不要修改变量。
  2. 避免从多个goroutine访问同一变量。
  3. 允许多个goroutine访问同一变量,但同一时间只有一个goroutine可以访问。这种方法称为互斥机制。

  Golang中代码中加锁,一般体现在是对临界区加锁。

   临界区:程序片段访问临界资源的代码,临界区同一时刻只能有一个线程运行,其他线程必须等待访问,所以需要用到锁;

3.2. 互斥锁:sync.Mutex

goroutinesyncMutexgoroutinegoroutinegoroutinegoroutine

3.3. 读写互斥锁:sync.RWMutex

syncRWMutexgoroutinegoroutinegoroutine

4. sync包其它应用

4.1. sync.Once

sync.Once
	// 如果要执行函数f,就需要搭配闭包来使用
	func (o *Once) Do(f func()) {}

4.2. sync.Map

sync.Map

5. atomic原子性操作

  1. 我开10万个goroutine,每个goroutine对全局变量x加一,每次执行结果都不一样。
	package main
	
	import (
		"fmt"
		"sync"
	)
	
	var x int
	var wg sync.WaitGroup
	
	func add() {
		x++
		wg.Done()
	}
	
	func main() {
		for i := 0; i < 100000; i++ {
			wg.Add(1)
			go add()
		}
		wg.Wait()
		fmt.Println(x)
	}

在这里插入图片描述
2. 加锁(sync.Mutex)

package main

import (
	"fmt"
	"sync"
)

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

func add() {
	defer wg.Done()
	// 对临界区加锁
	lock.Lock()
	x++
	lock.Unlock()
}

func main() {
	for i := 0; i < 100000; i++ {
		wg.Add(1)
		go add()
	}
	wg.Wait()
	fmt.Println(x)
}

在这里插入图片描述
3. 使用原子操作

	package main
	
	import (
		"fmt"
		"sync"
		"sync/atomic"
	)
	
	var x int64
	var wg sync.WaitGroup
	var lock sync.Mutex
	
	func add() {
		defer wg.Done()
		//lock.Lock()
		//x++
		//lock.Unlock()
		atomic.AddInt64(&x, 1)
	}
	
	func main() {
		for i := 0; i < 100000; i++ {
			wg.Add(1)
			go add()
		}
		wg.Wait()
		fmt.Println(x)
	}

在这里插入图片描述