Go如何并发(二)案例 & code

本文探讨Go语言中的并发编程,介绍如何启动goroutine以及同步goroutine的方法。通过WaitGroup和channel实现goroutine的协调运行。无缓冲channel确保了数据的同步传递,而有缓冲channel则允许在满容量前进行数据缓存,避免阻塞。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


第一个goroutine

package main

import (
	"fmt"
	"time"
)

func print(){
	fmt.Println("I'm a go routine.")
}

func main(){
	fmt.Println("Go routine begin.");
	go print();
	fmt.Println("Go routine end.");
}

任何代码在运行的时候都有一个goroutine,在main函数里称为main-goroutine

这段代码没有bug,问题在于main函数结束后,不会等待别的goroutine运行结束,所以一旦main函数退出,整个进程都会退出

所以最简单的办法就是sleep一下

package main

import (
	"fmt"
	"time"
)

func print(){
	fmt.Println("I'm a go routine.")
}

func main(){
	fmt.Println("Go routine begin.");
	go print();
	time.Sleep(time.Second * 1);
	fmt.Println("Go routine end.");
}

一个小例子,当我们进行计算时,我们怎么知道到底有没有在计算呢

func spinner(delay time.Duration){
	for{
		for _,r := range "-|/"{
			fmt.Printf("\r%c",r)
			time.Sleep(delay)
		}
	}
}

对这个函数go一下看看

func fib(x int) int {
	if x<2{
		return x
	}
	return fib(x - 2) + fib(x - 1)
}

func spinner(delay time.Duration){
	for{
		for _,r := range "-|/"{
			fmt.Printf("\r%c",r)
			time.Sleep(delay)
		}
	}
}

func main(){
	fmt.Println("Go routine begin.");
	go spinner(time.Millisecond * 100);
	fmt.Printf("\n%d\n",fib(38));
}

同步

之前我们的goroutine并没有打印出来,原因是goroutine没有同步,之前的解决方法是main-routine Sleep一下,但我们并不能估算出每个go-routine得让别人sleep多久,而且浪费了时间资源,因此就需要同步机制

这里的同步指 : 协调运行各个goroutine步调一致,来控制各个goroutine的运行先后

具体的工具在go的sync包里有WaitGroup、mutex、WRmutex等

用WaitGroup举个例子

package main

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



func main() {
	var w sync.WaitGroup

	for i:=0;i<10;i++{
		w.Add(1)
		go func(num int){
			defer w.Done()
			time.Sleep(1000 * time.Millisecond)
			fmt.Printf("I'm %d go routine.\n",num)
		}(i)
		
	}

	w.Wait()
}

WaitGroup.Add()添加要监控的goroutine数量

WaitGroup.Done()表示一个goroutine结束了,监控数量–;

WaitGroup.Wait()表示它一定会阻塞,确保计数为0

但我们go一个routine后,这个routine是异步的,我们没法获得返回值,要返回值怎么办?

channel

无缓冲channel

channel两端的进程可以通过channel进行通信,并且不见不散,只有进行读写操作时才能解除彼此的阻塞,否则一方会死等另一方

package main

import (
	"fmt"
)

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

	go func (){
		defer fmt.Println("Channel input end.")
		fmt.Println("go routine running...")
		c <- 66

	}()

	num := <-c
	fmt.Printf("num is %d ,go routine end.\n",num)
}

举个例子,最后运行结果为
在这里插入图片描述
func和main通过channel c通信

func将66写进c,main通过读取c,并把结果存进num

但运行结果为什么一定是main接收到了这个66并存给num,程序才结束,而不是main还没读取到这个数程序就结束呢,为什么每次都能打印成功呢

这是因为channel具有这种同步的能力,能够保证读的一方在读的时候如果碰到没东西读,就一定阻塞,一定要读到,才继续执行后面的代码

另一方面,如果func先执行到了写入的代码,因为channel是无缓冲的,那么写的一方就一定会阻塞,等到读的代码执行到了,确保被读走了,func才会继续执行

为了验证这个机制,再写一个例子:

package main

import (
	"fmt"
	"time"
)

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

	go func(){
		msg := <-c
		fmt.Println("I'm reader,",msg)
	}()

	fmt.Println("Begin sleep.")
	time.Sleep(5 * time.Second)
	c <- "hello"
	time.Sleep(1 * time.Second)
}

一个例子,func1负责在c1写,func2负责在c1读,并平方,写进c2,main负责在c2读,并输出

package main

import (
	"fmt"
)

func main() {
	c1 := make(chan int)
	c2 := make(chan int)

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

	go func(){
		for{
			num := <- c1
			c2 <- num *num
		}
	}()

	for{
		num := <- c2
		fmt.Println(num)
	}

}

结果:
在这里插入图片描述
如果我们在func2和main的for循环里不加循环条件10次的话,就会出现上述错误,原因就是func2和main一直在等,但永远也等不到,陷入了死锁

有没有办法,第一个func写完c1后给后面的人说一下,没数据了,别等了,第二个func也应该给main说一下,没数据了

Go中可以用读数据的指示器(ok),来判断出channel是否已经关闭,同时使用close()关闭channel

package main

import (
	"fmt"

)

func main() {
	c1 := make(chan int)
	c2 := make(chan int)

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

	go func(){
		for {
			num ,ok := <- c1
			if ok{
				c2 <- num *num
			}else{
				break
			}
		}
		close(c2)
	}()

	for {
		num ,ok := <- c2
		if ok{
			fmt.Println(num)
		}else{
			break;
		}
	}

}

在这里插入图片描述

单向无缓冲channel

channel单独存在一个读或者写都是没有意义的,因此用一个单方向的channel做一个声明上的限制,用在函数参数上

单方向channel声明如下:

channel  chan <-    type
channel  <- chan    type

对上一个传递数据的过程修改如下:

package main

import (
	"fmt"
)

func main() {
	c1 := make(chan int)
	c2 := make(chan int)

	go func(out chan <- int){
		for i := 0;i<10;i++{
			out <- i
		}
		close(out)
	}(c1)

	go func(in , out chan <- int){
		for {
			num ,ok := <- in
			if ok{
				out <- num *num
			}else{
				break
			}
		}
		close(out)
	}(c1,c2)

	for {
		num ,ok := <- c2
		if ok{
			fmt.Println(num)
		}else{
			break;
		}
	}

}

有缓冲channel

无缓冲的channel当 读方 没有回应 写方 时,写方就一直在管道里阻塞,因此为了避免这种情况,产生了有缓冲的channel

	c := make(chan int,3)
package main
import(
	"fmt"
	"sync"
)

func main(){
	var w sync.WaitGroup
	c := make(chan int,3)

	fmt.Println("c.len = ",len(c),", c.cap = ",cap(c))

	w.Add(1)
	go func(){
		defer fmt.Println("go routine end")
		for i := 0;i<3;i++{
			c<-i
			fmt.Println("now c.len = ",len(c))
		}
	}()
	w.Done()

	for i := 0;i<3;i++{
		num := <- c
		fmt.Println("num is ",num)
	}

	fmt.Println("main end")
}

如果容量满了,再写就会阻塞

	w.Add(1)
	go func(){
		defer fmt.Println("go routine end")
		for i := 0;i<4;i++{
			c<-i
			fmt.Println("now c.len = ",len(c))
		}
	}()
	w.Done()
	for i := 0;i<4;i++{
		num := <- c
		fmt.Println("num is ",num)
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

朱骥伦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值