go 并发编程

go 并发编程_第1张图片
go 并发编程_第2张图片

1goroutine

go 并发编程_第3张图片

1.1 统计当前goroutine数量go 并发编程_第4张图片

go 并发编程_第5张图片


//控制并发数量
func GoroutineAnts() {
	//1.统计当前goroutine数量
	go func() {
		for {
			fmt.Println("goroutinenum:", runtime.NumGoroutine())
			time.Sleep(250 * time.Millisecond)
		}
	}()
	//2.初始化协程池 goroutine pool
	size := 1024
	pool, err := ants.NewPool(size)
	if err != nil {
		log.Fatalln(err)
	}
	//保证pool被关闭
	defer pool.Release()
	//3.利用pool,调度需要并发的大量goroutine
	for {
		//向pool中提交一个执行的goroutine
		err := pool.Submit(func() {
			V := make([]int, 1024)
			_ = V
			time.Sleep(time.Second * 100)
		})
		if err != nil {
			log.Fatalln(err)
		}
	}
}

1.2 控制并发数量

//控制并发数量
func GoroutineAnts() {
	//1.统计当前goroutine数量
	go func() {
		for {
			fmt.Println("goroutinenum:", runtime.NumGoroutine())
			time.Sleep(250 * time.Millisecond)
		}
	}()
	//2.初始化协程池 goroutine pool
	size := 1024
	pool, err := ants.NewPool(size)
	if err != nil {
		log.Fatalln(err)
	}
	//保证pool被关闭
	defer pool.Release()
	//3.利用pool,调度需要并发的大量goroutine
	for {
		//向pool中提交一个执行的goroutine
		err := pool.Submit(func() {
			V := make([]int, 1024)
			_ = V
			time.Sleep(time.Second * 100)
		})
		if err != nil {
			log.Fatalln(err)
		}
	}
}

请添加图片描述

1.3 多对多协程调度模式

go 并发编程_第6张图片
go 并发编程_第7张图片
go 并发编程_第8张图片

1.4 GMP模型结构

所有G由P管理,一旦P与M绑定,就将G交由M执行,注意:P只能管理G,不能执行Ggo 并发编程_第9张图片
go 并发编程_第10张图片
go 并发编程_第11张图片
go 并发编程_第12张图片
go 并发编程_第13张图片
go 并发编程_第14张图片
go 并发编程_第15张图片
go 并发编程_第16张图片
go 并发编程_第17张图片
go 并发编程_第18张图片
go 并发编程_第19张图片
go 并发编程_第20张图片
go 并发编程_第21张图片
go 并发编程_第22张图片
go 并发编程_第23张图片
go 并发编程_第24张图片

1.5 抢占调度和协作调度

go 并发编程_第25张图片

//主动让出执行权
func GoroutineSched() {
	wg := sync.WaitGroup{}
	max := 100
	wg.Add(2)
	//设置为 1个P 在调度 G
	runtime.GOMAXPROCS(1) //单线程模式
	//输出奇数
	go func() {
		defer wg.Done()
		for i := 1; i <= max; i += 2 {
			fmt.Println(" ", i)
			//time.Sleep(time.Millisecond * 3)
			//主动让出
			runtime.Gosched()
		}
	}()
	//输出偶数
	go func() {
		defer wg.Done()
		for i := 2; i <= max; i += 2 {
			fmt.Println(" ", i)
			//time.Sleep(3 * time.Millisecond) //延长时间,使得处理机进行调度  单处理机运行一般10ms
			//主动让出
			runtime.Gosched()
		}
	}()
	wg.Wait()
}

1.6小节

  • GMP
  • G存在于P的本地队列或全局队列中
  • M要与P绑定,P中的G才会执行
  • M执行G中的系统调用时,会解绑M和P。P会找到新的M执行

2 综合应用

  • channel

1.需要先关闭,然后再循环遍历取值

//空接口管道断言
dog1:=<-allChan
a:=dog1.(dog) //需要类型断言
a:=(<-allChan).(dog) //与上面等价

2.1

go 并发编程_第26张图片

var intChan chan int
func ReadAndWrite() {
	intChan = make(chan int, 50)
	exitChan := make(chan bool, 1)
	go WriteData(intChan)
	go ReadData(intChan, exitChan)
	if <-exitChan {
		fmt.Println("end!")
	}
}
//封装两个方法
func WriteData(intChan chan int) {
	rand.Seed(time.Now().UnixNano())
	for i := 0; i < 50; i++ {
		var tempInt int
		tempInt = rand.Intn(4) + 10
		fmt.Printf("写入数据为%v,第%v次写入\n", tempInt, i+1)
		intChan <- tempInt
	}
	close(intChan)
}
func ReadData(intChan chan int, exitChan chan bool) {
	var count int
	for {
		val, ok := <-intChan
		count++
		if !ok {
			break
		}
		fmt.Printf("读取到%v,是第%v次读取\n", val, count)
	}
	exitChan <- true //读取完毕
	close(exitChan)
}

2.2统计素数需求的传统实现与协程和管道的实现

go 并发编程_第27张图片

  • 通常方法检查素数
// 检查是否为素数 通常用法
func IsPrime(n int) {
	for i := 1; i <= n; i++ {
		var flag bool = true
		for j := 2; j < i; j++ {
			if i%j == 0 {
				flag = false
				continue
			}
		}
		if flag {
			fmt.Println(i, "为素数")
		}
	}
}
  • 管道协程检查素数
// 检查是否为素数 管道协程
func initChan(n int) {
	for i := 1; i <= n; i++ {
		intChan <- i
	}
	close(intChan)
}
func isPrimeChannel(intChan chan int, primeChan chan int, exitChan chan bool) {
	var flag bool
	for {
		intchan, ok := <-intChan
		flag = true
		if !ok {
			break
		}
		for j := 2; j < intchan; j++ {
			if intchan%j == 0 {
				flag = false
				continue
			}
		}
		if flag {
			primeChan <- intchan
		}
	}
	exitChan <- true
}
func IsPrimeChannel() {
	var primeChan chan int = make(chan int, 100)
	var exitChan chan bool = make(chan bool, 8)
	go initChan(100)
	for i := 1; i <= 8; i++ {
		go isPrimeChannel(intChan, primeChan, exitChan)
	}
	go func() {
		for i := 0; i < 8; i++ {
			<-exitChan
		}
		close(primeChan)
		close(exitChan) //取完才能关闭,因为开启了八个协程,所以该管道会有八个值
	}()
	for {
		res, ok := <-primeChan
		if !ok {
			break
		}
		fmt.Println("素数有", res)
	}
}

2.3 只读只写

go 并发编程_第28张图片

2.4 如果不close(管道),怎么避免锁错误 使用 break label

break label 跳出循环不再执行for循环里的代码,即不再第二次执行for循环代码。
// 检查是否为素数 管道协程
func initChan(n int) {
	for i := 1; i <= n; i++ {
		intChan <- i
	}
	//close(intChan)
}
func isPrimeChannel(intChan chan int, primeChan chan int, exitChan chan bool) {
	var flag bool
label:
	for {
		select {
		case intchan := <-intChan:
			flag = true
			for j := 2; j < intchan; j++ {
				if intchan%j == 0 {
					flag = false
					continue
				}
			}
			if flag {
				primeChan <- intchan
			}
		default:
			break label
		}
	}
	exitChan <- true
}
func IsPrimeChannel() {
	var primeChan chan int = make(chan int, 100)
	var exitChan chan bool = make(chan bool, 8)
	go initChan(100)
	for i := 1; i <= 8; i++ {
		go isPrimeChannel(intChan, primeChan, exitChan)
	}
	go func() {
		for i := 0; i < 8; i++ {
			<-exitChan
		}
		//close(primeChan)
		//close(exitChan) //取完才能关闭,因为开启了八个协程,所以该管道会有八个值
	}()
label:
	for {
		select {
		case res := <-primeChan:
			fmt.Println("素数有", res)
		default:
			break label
		}

	}
}

2.5 defer 匿名函数捕获panic

go 并发编程_第29张图片

//defer recover panic
func PanicMain() {
	go onlyOut(2)
	fmt.Println("exit main")
	go onlyIn()
	time.Sleep(time.Second * 2)
}
func onlyOut(num int) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("onlyOut报错", err)
		}
	}()
	var testMap map[int]int //这地方报错
	testMap[0] = 1
}
func onlyIn() {
	for i := 0; i < 10; i++ {
		fmt.Println(i)
	}
}

2.6 生产者 消费者

go 并发编程_第30张图片
go 并发编程_第31张图片

//生产者消费者
func ProCouMain() {
	num := 10
	storageChan := make(chan Product, num)
	shopChan := make(chan Product, num)
	exitChan := make(chan bool, 1)
	for i := 0; i < num; i++ {
		go Produce(storageChan, num)
	}
	go Logistics(storageChan, shopChan)
	go Consumer(shopChan, num, exitChan)
	if <-exitChan {
		return
	}
}
//定义商品
type Product struct {
	Name string
}
//生产
func Produce(storage chan<- Product, count int) {
	for {
		producer := Product{Name: "商品:" + strconv.Itoa(count)}
		storage <- producer
		count--
		fmt.Println("生产了:", producer)
		time.Sleep(time.Second)
		if count < 1 {
			return
		}
	}
}
//运输
func Logistics(storageChan <-chan Product, shopCHan chan<- Product) {
	for {
		product := <-storageChan
		shopCHan <- product
		fmt.Println("运输了", product)
	}
}
//消费者消费
func Consumer(shopChan <-chan Product, count int, exitChan chan<- bool) {
	for {
		product := <-shopChan
		fmt.Println("消费者消费了", product)
		count--
		if count < 1 {
			exitChan <- true
			return
		}
	}
}

2.7 协程管道定时任务的应用

go 并发编程_第32张图片
go 并发编程_第33张图片

//协程管道定时任务
func TimingMain() {
	//方式一:
	fmt.Println("当前时间", time.Now())
	timer := time.NewTimer(time.Second * 3)
	t1 := <-timer.C //timer.c是一个只读的管道
	fmt.Println("现在时间", t1)
	//方式二:
	fmt.Println("当前时间", time.Now())
	t2 := <-time.After(time.Second * 3) //源码可见,实际返回的是 return NewTimer(d).C
	fmt.Println("现在时间", t2)
}

请添加图片描述

2.7 定时器的终止与重置

//协程管道定时任务
func TimingMain() {
	//方式一:
	fmt.Println("当前时间", time.Now())
	timer := time.NewTimer(time.Second * 3)
	if flag {
		timer.Stop()
	} else {
		t1 := <-timer.C //timer.c是一个只读的管道
		fmt.Println("现在时间", t1)
	}
	//方式二:
	//fmt.Println("当前时间", time.Now())
	//t2 := <-time.After(time.Second * 3) //源码可见,实际返回的是 return NewTimer(d).C
	//fmt.Println("现在时间", t2)
}
func IsStopTimer() bool {
	rand.Seed(time.Now().UnixNano())
	tempInt := rand.Intn(2) + 16
	if tempInt >= 18 {
		fmt.Println("已经找到大于18的,结束timer")
		return true
	} else {
		return false
	}
}

2.8 每隔固定时间触发任务

//循环时钟
func CircMain() {
	var count int = 0
	var exitChan chan bool
	newTicker := time.NewTicker(time.Second)
	go func() {
		for {
			t := <-newTicker.C
			fmt.Println(t.Format("2006-01-02 03:04:05PM"))
			count++
			if count > 1 {
				newTicker.Stop()
				exitChan <- true
			}
		}
	}()
	<-exitChan
	fmt.Println("结束!")
}

2.8 改造任务队列

go 并发编程_第34张图片

  • timer ticker

//循环时钟
func CircMain() {
	var wg sync.WaitGroup
	wg.Add(2)
	var count int = 0
	var countb int = 0
	newTicker := time.NewTicker(time.Second)
	go func() {
		defer wg.Done()
		defer newTicker.Stop()
		for {
			t := <-newTicker.C
			fmt.Println(t.Format("ticker" + "2006-01-02 03:04:05PM"))
			count++
			if count > 2 {
				return
			}
		}
	}()
	newTimer := time.NewTimer(time.Second)
	go func() {
		defer wg.Done()
		defer newTimer.Stop()
		for {
			t := <-newTimer.C
			fmt.Println(t.Format("timer:" + "2006-01-02 03:04:05PM"))
			newTimer.Reset(time.Second)
			countb++
			if countb > 3 {
				return
			}
		}
	}()
	wg.Wait()
	fmt.Println("结束!")
}

3 context上下文

go 并发编程_第35张图片

3.1 默认context使用

go 并发编程_第36张图片

3.2 主动传递取消信号

go 并发编程_第37张图片go 并发编程_第38张图片

//context主动取消
func ContextCancel() {
	//1.创建cancelContext
	ctx, cancel := context.WithCancel(context.Background())
	//2.启动goroutine 携带cancelCtx
	for i := 0; i < 4; i++ {
		//启动goroutine 携带ctx参数
		go func(c context.Context, n int) {
			//监听context的取消完成channel,来确定是否执行了主动的cancel操作
			for {
				select {
				//等待接收c.Done()这个channel
				case <-c.Done():
					fmt.Println("1 context cancel!") //这里不输出原因是 37行 cancel()执行后 主goroutine已经结束,因此后续goroutine自动结束,所以还没来得及执行就结束了。
					return                           //不是<-c.Done()导致协程结束 ,是因为return导致结束。 <-c.Done()只是一个开关
				default:
				}
				fmt.Println(strings.Repeat("  ", n), n)
				time.Sleep(time.Millisecond * 300)
			}
		}(ctx, i)
	}
	//3.主动取消 cacel()
	//3秒后取消
	select {
	//case <-time.NewTimer(3 * time.Second).C:
	case <-time.After(2 * time.Second):
		cancel() // ctx.Done() <- struct{}
	}
	select {
	case <-ctx.Done():
		fmt.Println("2 context cancel!")
	}
}

3.3 定时取消

go 并发编程_第39张图片

//定时取消信号
func ContextCancelTime() {
	fmt.Println(time.Now())
	//1.创建 带有时间的 cancelContext  一段时间后取消
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	//ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second*2)) //以上两个等价 这个是具体时间点
	//或具体到年月日时分秒纳秒,
	//curr := time.Now()
	//ctx, cancel := context.WithDeadline(context.Background(), time.Date(curr.Year(), curr.Month(), curr.Day(), 20, 30, 0, 0, time.Local))
	//2.启动goroutine 携带cancelCtx
	for i := 0; i < 4; i++ {
		//启动goroutine 携带ctx参数
		go func(c context.Context, n int) {
			//监听context的取消完成channel,来确定是否执行了主动的cancel操作
			for {
				select {
				//等待接收c.Done()这个channel
				case <-c.Done():
					fmt.Println("1 context cancel!") //这里不输出原因是 37行 cancel()执行后 主goroutine已经结束,因此后续goroutine自动结束,所以还没来得及执行就结束了。
					return                           //不是<-c.Done()导致协程结束 ,是因为return导致结束。 <-c.Done()只是一个开关
				default:
				}
				fmt.Println(strings.Repeat("  ", n), n)
				time.Sleep(time.Millisecond * 300)
			}
		}(ctx, i)
	}
	//3.主动取消 cancel() 到时取消
	select {
	//4s后主动取消
	case <-time.After(4 * time.Second):
		cancel() // ctx.Done() <- struct{}
		fmt.Println("主动", time.Now())
		fmt.Println("call cancel() Cancel")
	case <-ctx.Done(): //2s后到时取消
		fmt.Println("被动", time.Now())
		fmt.Println("main  cancel!")
	}
}

3.4 cancel操作的向下取消

go 并发编程_第40张图片

//cancel取消向下传递
func ContextCancelDeep() {
	//1.创建层级关系的cancelCtx  1-(2-(4),3)
	//ctxOne, _ := context.WithCancel(context.Background())
	//ctxTwo, cancel := context.WithCancel(ctxOne)
	//ctxThree, _ := context.WithCancel(ctxOne)
	//ctxFour, _ := context.WithCancel(ctxTwo)
	//1.定时器方式保证取消 一方面也解决了枷锁固定问题
	ctxOne, _ := context.WithTimeout(context.Background(), time.Second*2)
	ctxTwo, cancel := context.WithTimeout(ctxOne, time.Second*2)
	ctxThree, _ := context.WithTimeout(ctxOne, time.Second*2)
	ctxFour, _ := context.WithTimeout(ctxTwo, time.Second*2)
	wg := sync.WaitGroup{}

	//2.使用goroutine来接收ctx.Done()
	go func(c context.Context) {
		wg.Add(1)
		defer wg.Done()
		select {
		case <-c.Done(): //ctxOne
			fmt.Println("one cancel")
		}
	}(ctxOne)
	go func(c context.Context) {
		wg.Add(1)
		defer wg.Done()
		select {
		case <-c.Done(): //ctxOne
			fmt.Println("two cancel")
		}
	}(ctxTwo)
	go func(c context.Context) {
		wg.Add(1)
		defer wg.Done()
		select {
		case <-c.Done(): //ctxOne
			fmt.Println("three cancel")
		}
	}(ctxThree)
	go func(c context.Context) {
		wg.Add(1)
		defer wg.Done()
		select {
		case <-c.Done(): //ctxOne
			fmt.Println("four cancel")
		}
	}(ctxFour)
	//主动取消
	cancel()
	wg.Wait()
}

3.5 取消操作的流程

go 并发编程_第41张图片
go 并发编程_第42张图片

3.6 context传值

go 并发编程_第43张图片

go 并发编程_第44张图片

//context传值
func ContextValue() {
	wg := sync.WaitGroup{}
	wg.Add(1)
	//1.创建带有value的Context
	//传递 有哪个用哪个 没有的话就用上层的(如果key一致),key不一致的话,按从低到高找  1--2--3
	//key的值不建议直接使用string或其他内置类型 例如 两个包的 key "one" ,但值不一样
	ctxOne := context.WithValue(context.Background(), MyContextString("one"), "go of one")
	ctxTwo := context.WithValue(ctxOne, "title", "go of two")
	ctxThree := context.WithValue(ctxTwo, "title", "go of three")
	//2.将ctx传到goroutine中使用
	go func(c context.Context) {
		defer wg.Done()
		//获取key对应的value
		if v := c.Value(MyContextString("one")); v != nil {
			fmt.Println("title 对应的值为", v)
			return
		}
		fmt.Println("NOT FOUND VALUE ACCORDING TO title")
	}(ctxThree)
	wg.Wait()
}

3.7 小节

go 并发编程_第45张图片

4 同步与锁

go 并发编程_第46张图片

4.1互斥锁的使用go 并发编程_第47张图片

请添加图片描述

4.2 读写互斥锁rwmutex

go 并发编程_第48张图片
go 并发编程_第49张图片

func SyncRLock() {
	//RLOCK可能先于LOCK
	wg := sync.WaitGroup{}
	//模拟多个goroutine
	var rwlck sync.RWMutex
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			//输出一段内容
			//rwlck.Lock()
			rwlck.RLock()
			fmt.Println(time.Now())
			time.Sleep(1 * time.Second)
			//rwlck.Unlock()
			rwlck.RUnlock()
		}()
	}
	wg.Add(1)
	go func() {
		defer wg.Done()
		defer wg.Done()
		//输出一段内容
		rwlck.Lock()
		fmt.Println(time.Now(), "LOCK")
		time.Sleep(1 * time.Second)
		rwlck.Unlock()
	}()
	wg.Wait()
}

4.3 sync.map同步map的使用

请添加图片描述

go 并发编程_第50张图片

go 并发编程_第51张图片

go 并发编程_第52张图片
go 并发编程_第53张图片

请添加图片描述

//syncmap
func SyncSyncMapMethod() {
	wg := sync.WaitGroup{}
	//直接初始化即可使用,不需要指定key和value的类型
	var m sync.Map
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(n int) {
			defer wg.Done()
			//存储
			m.Store(n, fmt.Sprintf("value:(%d)", n))
		}(i)
	}
	//并发goroutine完成load操作
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(n int) {
			defer wg.Done()
			//读取
			fmt.Println(m.Load(n))
		}(i)
	}
	wg.Wait()
	//遍历
	m.Range(func(key, value interface{}) bool {
		fmt.Println(key, value)
		//返回true,继续遍历,直到map结尾
		//返回false 提前终止遍历
		return true
	})
	m.Delete(4)
}

4.4原子操作go 并发编程_第54张图片

go 并发编程_第55张图片

// 原子操作
func SyncAtomicAdd() {
	//并发的过程 没有加锁
	//atomic 原子的int32 counter:=0
	var counter atomic.Int32
	wg := sync.WaitGroup{}
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for i := 0; i < 100; i++ {
				//原子累加 == counter++
				counter.Add(1)
			}
		}()
	}
	wg.Wait()
	fmt.Println("counter", counter.Load())
}

func SyncAtomicValue() {
	//一个goroutine动态加载配置
	//配置为自定义类型 map[string]string
	//模拟加载配置,例如从配置文件加载,返回解析的配置信息
	var loadConfig = func() map[string]string {
		return map[string]string{
			//some config
			"title":   "马士兵go并发",
			"varConf": fmt.Sprintf("%d", rand.Int31()),
		}
	}
	//config的操作应该是并发安全的,我们选择使用原子操作来实现
	//自定义的原子操作类型,atomic.Value
	var config atomic.Value
	//每n秒加载一次配置文件(监视配置文件的修改)
	go func() {
		for {
			//原子加载配置
			config.Store(loadConfig())
			fmt.Println("lasted config was loaded", time.Now().Format("15:04:05.99999999"))
			time.Sleep(time.Second)
		}
	}()
	//在多个goroutine中使用最新配置
	//不能在加载的过程中使用配置
	for {
		go func() {
			//原子加载配置
			c := config.Load()
			fmt.Println(c, time.Now().Format("15:04:06.99999999"))
		}()
		time.Sleep(400 * time.Millisecond)
	}
	select {}
}

4.5 sync.pool并发安全池

go 并发编程_第56张图片

  • 这个类设计的目的是用来保存和复用临时对象,以减少内存分配,降低cg压力。
// sync.pool
//并发资源管理
func SyncPool() {

	//管理资源 资源为可复用的临时对象时,使用池子
	//原子计数器
	var counter int32 = 0
	//定义元素的Newer,创建器
	elementNewer := func() any {
		//原子的计数器累加
		atomic.AddInt32(&counter, 1)
		//池中元素推荐(强烈)是指针类型
		return new(bytes.Buffer)
	}
	//Pool的初始化
	pool := sync.Pool{
		New: elementNewer, //一旦池子里没有的话,调用pool.New
	}
	//并发的申请和交回元素
	wg := sync.WaitGroup{}
	workNum := 1024
	wg.Add(workNum)
	for i := 0; i < workNum; i++ {
		go func() {
			defer wg.Done()
			//申请元素,通常需要断言为特定类型
			buffer := pool.Get().(*bytes.Buffer)
			//交回元素
			defer pool.Put(buffer)
			//使用元素
			_ = buffer.String()
		}()
	}
	wg.Wait()
	//测试创建元素次数
	fmt.Println("elements number:", counter)
}

4.6 data race现象

  • 检测数据竞争情况 解决方案 锁或原子操作
  • go 并发编程_第57张图片
package main

import (
	"fmt"
	"sync"
)

func main() {
	wg := sync.WaitGroup{}
	num := 1000
	var counter int = 0
	wg.Add(num)
	for i := 0; i < num; i++ {
		go func() {
			defer wg.Done()
			//累加
			for k := 0; k < 100; k++ {
				counter++
			}
		}()
	}
	wg.Wait()
	fmt.Println("counter number is ", counter)
}

go 并发编程_第58张图片

4.7 sync.Once 执行一次

go 并发编程_第59张图片

func SyncOnce() {
	//初始化config变量
	config := make(map[string]string)
	//1.初始化 sync.Once
	once := sync.Once{}
	//加载配置函数
	loadConfig := func() {
		//2. 利用 once.Do()来执行
		once.Do(func() {
			//保证只执行一次
			config = map[string]string{
				"varInt": fmt.Sprintf("%d", rand.Int31()),
			}
			fmt.Println("config loaded")
		})
	}
	//模拟多个goroutine,多次调用加载配置
	//测试加载配置操作,执行了几次
	workers := 10
	wg := sync.WaitGroup{}
	wg.Add(workers)
	for i := 0; i < workers; i++ {
		go func() {
			defer wg.Done()
			//并发的多次加载配置
			loadConfig()
			//使用配置
			_ = config
		}()
	}
	wg.Wait()
}

4.8 sync .Cond 条件等待通知的使用

go 并发编程_第60张图片
go 并发编程_第61张图片

// 一个goroutine接收数据,多个goroutine处理数据
func SyncCond() {
	wg := sync.WaitGroup{}
	//全局(使用cond的gouroutine来说的)数据
	var data []int
	dataLen := 1024 * 1024
	//1.创建sync.Cond
	cond := sync.NewCond(&sync.Mutex{})
	//接受数据goroutine,一个
	wg.Add(1)
	go func() {
		defer wg.Done()
		cond.L.Lock() //用于输出 “cond broadcast”
		defer cond.L.Unlock()
		for i := 0; i < dataLen; i++ {
			data = append(data, i*i) //模拟数据传输
		}
		//2.在数据接收完毕后,在通知处理的goroutine进行数据处理
		//广播,可以选的需要锁定
		time.Sleep(time.Second * 2)
		cond.Broadcast() //先 wait 再 broadcast
		fmt.Println("cond broadcast")
	}()
	const workers = 8
	wg.Add(workers)
	//处理数据的goroutine,一组
	for i := 0; i < workers; i++ {
		go func() {
			defer wg.Done()
			//3.在数据未接收完之前(就是等待的条件),等待
			//wait前要加锁
			cond.L.Lock()
			//defer cond.L.Unlock()
			for len(data) < dataLen { //表明没有传输完毕,等待中 使用 for 是因为其他地方可能使用了 cond.Broadcast(),提前释放了wait
				cond.Wait() //函数里先解锁了,后加锁了
			}
			//处理数据
			fmt.Println("处理数据,数据长度:", len(data))
			//wait后要解锁,业务逻辑处理完毕
			cond.L.Unlock()
		}()
	}
	wg.Wait()
}

4.9 同步与索小节

go 并发编程_第62张图片

5 案例

go 并发编程_第63张图片
go 并发编程_第64张图片

5.1 并发目录大小统计之一 业务分析

5.2 并发目录大小统计之二 整体流程

5.3 并发目录大小统计之三 递归信息

// WalkDir 外部调用的遍历目录统计信息的方法
func WalkDir(dirs ...string) string { //多个目录,一个目录下有多个文件
	//一、保证至少有一个目录需要统计遍历
	//默认为当前目录
	if len(dirs) == 0 {
		dirs = []string{"."}
	}
	//二、初始化变量,channel用于完成Size的传递,WaitGroup用于等待调度
	filesizeCh := make(chan int64, 1)
	wg := &sync.WaitGroup{} //在多个协程传递用指针类型
	//三、启动多个Goroutine统计信息,取决于len(dirs)
	for _, dir := range dirs {
		wg.Add(1) //+1
		//并发的遍历统计每个目录的信息
		go walkDir(dir, filesizeCh, wg)
	}
	//四、启动累计运算的Goroutine
	//1.用户关闭 filesizeCh
	go func(wg *sync.WaitGroup) {
		//等待统计工作的完成
		wg.Wait()
		//关闭 filesizeCh
		close(filesizeCh)
	}(wg)
	//2.range的方式从 filesizeCh 中获取文件大小
	//3.将统计结果,用channel传递出来
	fileNumCh := make(chan int64, 1)
	sizeTotalCh := make(chan int64, 1)
	go Total(filesizeCh, fileNumCh, sizeTotalCh)
	//五、整理返回值
	//size的单位 byte
	//需要的单位 mb
	result := fmt.Sprintf("%d files %.2f MB\n", <-fileNumCh,
		float64(<-sizeTotalCh)/1e6) //1024*1024
	return result
}

// 遍历并统计某个特定目录的信息
// 核心实现函数,完成递归,统计
func walkDir(dir string, filesizeCh chan<- int64, wg *sync.WaitGroup) {
	//一、wg计数器减少
	defer wg.Done()
	//二、读取 dir 下的全部文件信息
	for _, fileinfo := range fileInfos(dir) {
		//三、根据 dir 下的文件信息
		if fileinfo.IsDir() {
			//1.如果是目录, 获取递归信息
			//子目录地址
			subDir := filepath.Join(dir, fileinfo.Name())
			//递归调用,也是并发的,也需要wg统计
			wg.Add(1)
			go walkDir(subDir, filesizeCh, wg)
		} else {
			//2.如果不是,就是文件,统计文件大小,放入channel
			filesizeCh <- fileinfo.Size() //Byte
		}
	}
}

//获取某个目录下全部信息列表
func fileInfos(dir string) []fs.FileInfo {
	//一、读取目录的全部文件
	entries, err := os.ReadDir(dir)
	if err != nil {
		log.Println("WalkDir error ", err)
		return []fs.FileInfo{} //nil
	}
	//二、获取文件的文件信息
	//DirEntry to FileInfo
	infos := make([]fs.FileInfo, 0, len(entries))
	for _, entry := range entries {
		//如果获取文件信息无错误,存储到Infos中
		if info, err := entry.Info(); err == nil {
			infos = append(infos, info)
		}
	}
	//三、返回
	return infos
}

func Total(filesizeCh <-chan int64, fileNumCh, sizeTotalCh chan<- int64) {
	//统计文件数,和文件整体大小
	var fileNum, sizeTotal int64
	//遍历全部 filesizeCh 元素,统计文件数量和大小,直到 filesizeCh 通道关闭
	for filesize := range filesizeCh { //使用 for 是防止通道阻塞
		//累计文件数,和统计文件整体大小
		fileNum++
		sizeTotal += filesize
	}
	//将统计结果写入channel
	fileNumCh <- fileNum
	sizeTotalCh <- sizeTotal
}

快速排序的并发编程思想实现

你可能感兴趣的:(go实战,golang,后端,开发语言)