GO编程(打卡)-Task13: 并发编程

并发编程

并发在图中的解释是两队人排队接咖啡,两队切换。

并行是两个咖啡机,两队人同时接咖啡

goroutine

启动goroutine只要在前面调用函数前加go关键字即可

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

通过runtime.GOMAXPROCS(n)函数设置当前程序并发时占用的CPU逻辑核心数

并发安全和锁

package main

import (
	"fmt"
	"sync"
	"time"
)
func worker(id int, wg *sync.WaitGroup){
	defer wg.Done()
	fmt.Printf("Worker %d starting
",id)
	time.Sleep(time.Second)
	fmt.Printf("Worker %d done
",id)
}
func DoSomething(doonce *sync.Once){
	doonce.Do(func(){
		fmt.Println("Run once - first time, loading...")
	})
	fmt.Println("Run this every time")
}
// SafeCounter 的并发使用是安全的。
type SafeCounter struct {
	v   map[string]int
	mux sync.Mutex
}

// Inc 增加给定 key 的计数器的值。
func (c *SafeCounter) Inc(key string) {
  c.mux.Lock()
  defer c.mux.Unlock()
	// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
  c.v[key]++
}

// Value 返回给定 key 的计数器的当前值。
func (c *SafeCounter) Value(key string) int {
	c.mux.Lock()
	// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
	defer c.mux.Unlock()
	return c.v[key]
}
func main() {
	// 计数器不能为负值
	// WaitGroup对象不是引用类型
	var wg sync.WaitGroup
	for i := 1; i <= 3; i++{
		wg.Add(1)
		go worker(i, &wg)
	}
	wg.Wait()
	// sync.Once控制函数只能被调用一次,不能多次重复调用
	var doOnce sync.Once
	DoSomething(&doOnce)
	DoSomething(&doOnce)
	// 互斥锁 Mutex
	// 读写锁 RWMutex 读锁会阻止写但不会阻止读 RLock() RUnlock()释放
	// 写锁就等同于Mutex
	c := SafeCounter{v: make(map[string]int)}
	for i := 0; i < 1000; i++ {
		go c.Inc("somekey")
	}

	time.Sleep(time.Second)
	fmt.Println(c.Value("somekey"))
	// 条件变量 Cond
	// 可以让一系列的 Goroutine 都在满足特定条件时被唤醒
}

原子操作

原子操作由内置的标准库sync/atomic提供

package main

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

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

// 普通版加函数
func add() {
	// x = x + 1
	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() // 普通版add函数 不是并发安全的
		// go mutexAdd() // 加锁版add函数 是并发安全的,但是加锁性能开销大
		go atomicAdd() // 原子操作版add函数 是并发安全,性能优于加锁版
	}
	wg.Wait()
	end := time.Now()
	fmt.Println("原子操作版add函数 x=", x)
	fmt.Println("原子操作版add函数", end.Sub(start))
}

Channel通道

Channel 是一种引用类型

var 变量 chan 元素类型
分类

无缓冲的Channel
发送与接受同时进行。如果没有Goroutine读取Channel(<-Channel),发送者(Channel<-x)会一直阻塞。

有缓冲的Channel
发送与接受并非同时进行。当队列为空,接受者阻塞;队列满,发送者阻塞。

worker pool(goroutine池)

指定启动的goroutine数量–worker pool模式,控制goroutine的数量,防止goroutine泄漏和暴涨。

select多路复用

使用select提高代码可读性

  • 可处理一个或多个channel的发送/接收操作。
    如果多个case同时满足,select会随机选择一个。
    对于没有case的select{}会一直等待,可用于阻塞main函数。
package main

import (
	"fmt"
)

func main() {
	ch := make(chan int, 1)
	for i := 0; i < 10; i++ {
		select {
		case x := <-ch:
			fmt.Println(x)
		case ch <- i:
		}
	}
}

参考

https://github.com/datawhalechina/go-talent/blob/master/12.并发编程.md

https://www.cnblogs.com/nickchen121/p/11517440.html

原文地址:https://www.cnblogs.com/rn-05181226-rw/p/14182092.html