Go并发4种方法简明讲解


Posted in Golang onApril 06, 2022

一、goroutine

1、协程(Coroutine)

Golang 在语言层面对并发编程进行了支持,使用了一种协程(goroutine)机制,

协程本质上是一种用户态线程,不需要操作系统来进行抢占式调度,但是又寄生于线程中,因此系统开销极小,可以有效的提高线程的任务并发性,而避免多线程的缺点。但是协程需要语言上的支持,需要用户自己实现调度器,因为在Go语言中,实现了调度器所以我们可以很方便的能过 go关键字来使用协程。

func main() {
	for i := 0; i <10; i++ {
		go func(i int) {
			for  {
				fmt.Printf("Hello goroutine %d\n",i)
			}
		}(i)
	}
	time.Sleep(time.Millisecond)
}

最简单的一个并发编程小例子,并发输出一段话。

我们同时开了10个协程进行输出,每次在fmt.printf时交出控制权(不一定每次都会交出控制权),回到调度器中,再由调度器分配。

2、goroutine 可能切换的点

  • I/O,Select
  • channel
  • 等待锁
  • 函数调用
  • runtime.Gosched()

我们看一个小例子:

func main() {
	var a [10]int
	for i := 0; i <10; i++ {
		go func(i int) {
			for  {
				a[i]++
			}
		}(i)
	}
	time.Sleep(time.Millisecond)
	fmt.Println(a)
}

在这里,代码直接锁死,程序没有退出,因为在执行函数中没有协程的切换,因为 main函数也是一个协程。

如果想要程序退出,可以通过 runtime.Gosched()函数,在执行函数中添加一行。

for  {
  a[i]++
  runtime.Gosched()
}

加上这个函数之后,代码是可以正常执行了,但是真的是正常执行吗?不一定,我们可以使用 -reac命令来看一下数据是否有冲突:

Go并发4种方法简明讲解

这说明数据还是有冲突的,数组a中的元素一边在做自增,一边在输出。解决这个问题,我们只能使用 channel 来解决。

二、Channel

Channel 中 Go语言在语言级别提供了对 goroutine 之间通信的支持,我们可以使用 channel 在两个或者多个goroutine之间进行信息传递,能过 channel 传递对像的过程和调用函数时的参数传递行为一样,可以传递普通参数和指针。

Channel 有两种模式:

var ch1 = make(chan int)		// 无缓冲 channel,同步
var ch2 = make(chan int, 2)	// 有缓冲 channel, 异步

无缓冲的方式,数据进入 channel 只要没有被接收,就会处在阻塞状态。

var ch1 = make(chan int)		// 无缓冲 channel,同步
ch1 <- 1
ch1 <- 2
//  error: all goroutines are asleep - deadlock!
fmt.Println(<-ch1)

如果想要运行,必须要再开一个协程不停的去请求数据:

var ch1 = make(chan int)		// 无缓冲 channel,同步
go func() {
  for  {
    n := <-ch1
    fmt.Println(n)
  }
}()
ch1 <- 1
ch1 <- 2

有缓冲的方式,只要缓冲区没有满就可以一直进数据,缓冲区在填满之后没有接收也会处理阻塞状态。

func bufferChannel() {
	var ch2 = make(chan int,2)
	ch2<-1
	ch2<-2
	fmt.Println(ch2)
  // 不加这一行的话,是可以正常运行的
	ch2<-3			// error: all goroutines are asleep - deadlock!
}

1、chaanel 指定方向

比如我现在有一个函数创建一个 channel,并且不断的需要消费channel中的数据:

func worker(ch chan int) {
	for {
		fmt.Printf("hello goroutine worker %d\n", <-ch)
	}
}

func createWorker() chan int{
	ch := make(chan int)
	go worker(ch)
	return ch
}

func main() {
	ch := createWorker()
	ch<-1
	ch<-2
	ch<-3
	time.Sleep(time.Millisecond)
}

这个函数我是要给别人用的,但是我怎么保证使用 createWorker 函数创建的 channel 都是往里面传入数据的呢?

如果外面有人消费了这个 channel 中的数据,我们怎么限制?

这个时候,我们就可以给返回的channel 加上方向,指明这个 channel 中能往里传入数据,不能从中取数据:

func worker(ch <-chan int) {
	for {
		fmt.Printf("hello goroutine worker %d\n", <-ch)
	}
}

func createWorker() chan<- int{
	ch := make(chan int)
	go worker(ch)
	return ch
}

我们可以在返回 channel 的地方加上方向,指明返回的函数只能是一个往里传入数据,不能从中取数据。

并且我们还可以给专门消费的函数加上一个方向,指明这个函数只能出不能进。

2、channel 关闭

在使用 channel 的时候,随说我们可以等待channel中的函数使用完之后自己结束,或者等待 main 函数结束时关闭所有的 goroutine 函数,但是这样的方式显示不够优雅。

当一个数据我们明确知道他的结束时候,我们可以发送一个关闭信息给这个 channel ,当这个 channel 接收到这个信号之后,自己关闭。

// 方法一
func worker(ch <-chan int) {
	for {
		if c ,ok := <- ch;ok{
			fmt.Printf("hello goroutine worker %d\n", c)
		}else {
			break
		}
	}
}
// 方法二
func worker(ch <-chan int) {
	for c := range ch{
		fmt.Printf("hello goroutine worker %d\n", c)
	}
}

func main() {
	ch := createWorker()
	ch<-1
	ch<-2
	ch<-3
	close(ch)
	time.Sleep(time.Millisecond)
}

通过 Closeb函数,我们可以能过 channel 已经关闭,并且我们还可以通过两种方法判断通道内是否还有值。

三、Select

当我们在实际开发中,我们一般同时处理两个或者多个 channel 的数据,我们想要完成一个那个 channel 先来数据,我们先来处理个那 channel 怎么办呢?

此时,我们就可以使用 select 调度:

func genInt() chan int {
	ch := make(chan int)
	go func() {
		i := 0
		for {
			// 随机两秒以内生成一次数据
			time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
			ch <- i
			i++
		}
	}()
	return ch
}

func main() {
	var c1 = genInt()
	var c2 = genInt()
	for {
		select {
		case n := <-c1:
			fmt.Printf("server 1 generator %d\n", n)
		case n := <- c2:
			fmt.Printf("server 2 generator %d\n", n)
		}
	}
}

1、定时器

for {
		tick := time.Tick(time.Second)
		select {
		case n := <-c1:
			fmt.Printf("server 1 generator %d\n", n)
		case n := <-c2:
			fmt.Printf("server 2 generator %d\n", n)
		case <-tick:
			fmt.Println("定时每秒输出一次!")
		}
	}

2、超时

for {
		tick := time.Tick(time.Second)
		select {
		case n := <-c1:
			fmt.Printf("server 1 generator %d\n", n)
		case n := <-c2:
			fmt.Printf("server 2 generator %d\n", n)
		case <-tick:
			fmt.Println("定时每秒输出一次!")
		case <-time.After(1300 * time.Millisecond): // 如果 1.3秒内没有数据进来,那么就输出超时
			fmt.Println("timeout")
		}
	}

四、传统的并发控制

1、sync.Mutex

type atomicInt struct {
	value int
	lock sync.Mutex
}

func (a *atomicInt) increment() {
	a.lock.Lock()
	defer a.lock.Unlock()		// 使用 defer 解锁,以防忘记
	a.value++
}

func main() {
	var a atomicInt
	a.increment()
	go func() {
		a.increment()
	}()
	time.Sleep(time.Millisecond)
	fmt.Println(a.value)
}

2、sync.WaitGroup

type waitGrouInt struct {
	value int
	wg sync.WaitGroup
}

func (w *waitGrouInt) addInt() {
	w.wg.Add(1)
	w.value++
}

func main() {
	var w waitGrouInt
	for i := 0; i < 10; i++ {
		w.addInt()
		w.wg.Done()
	}
	w.wg.Wait()
	fmt.Println(w.value)
}

 更多关于Go并发简明讲解请查看下面的相关链接

Golang 相关文章推荐
golang interface判断为空nil的实现代码
Apr 24 Golang
Go语言带缓冲的通道实现
Apr 26 Golang
go结构体嵌套的切片数组操作
Apr 28 Golang
golang 接口嵌套实现复用的操作
Apr 29 Golang
golang 在windows中设置环境变量的操作
Apr 29 Golang
Go遍历struct,map,slice的实现
Jun 13 Golang
golang实现一个简单的websocket聊天室功能
Oct 05 Golang
Golang流模式之grpc的四种数据流
Apr 13 Golang
Golang 字符串的常见操作
Apr 19 Golang
GoFrame gredis缓存DoVar Conn连接对象 自动序列化GoFrame gredisDo/DoVar方法Conn连接对象自动序列化/反序列化总结
Jun 14 Golang
Go 内联优化让程序员爱不释手
Jun 21 Golang
基于Python实现西西成语接龙小助手
Aug 05 Golang
Go归并排序算法的实现方法
Apr 06 #Golang
golang操作rocketmq的示例代码
Apr 06 #Golang
victoriaMetrics库布隆过滤器初始化及使用详解
如何解决goland,idea全局搜索快捷键失效问题
golang为什么要统一错误处理
简单聊聊Golang中defer预计算参数
Mar 25 #Golang
Go 中的空白标识符下划线
You might like
香妃
2021/03/03 冲泡冲煮
php批量更改数据库表前缀实现方法
2013/10/26 PHP
ThinkPHP分页类使用详解
2014/03/05 PHP
thinkphp数据查询和遍历数组实例
2014/11/28 PHP
javaScript Array(数组)相关方法简述
2009/07/25 Javascript
关于Aptana Studio生成自动备份文件的解决办法
2009/12/23 Javascript
jQuery fadeTo方法调整图片的透明度使用介绍
2013/05/06 Javascript
Node.js插件的正确编写方式
2014/08/03 Javascript
Javascript学习指南
2014/12/01 Javascript
JavaScript日期时间与时间戳的转换函数分享
2015/01/31 Javascript
详解JavaScript的回调函数
2015/11/20 Javascript
js实现将选中内容分享到新浪或腾讯微博
2015/12/16 Javascript
jQuery如何防止Ajax重复提交
2016/10/14 Javascript
浅谈javascript中的数据类型转换
2016/12/27 Javascript
浅谈js的解析顺序 作用域 严格模式
2017/10/23 Javascript
webpack分离css单独打包的方法
2018/06/12 Javascript
Nodejs实现爬虫抓取数据实例解析
2018/07/05 NodeJs
JavaScript变量Dom对象的所有属性
2020/04/30 Javascript
测试、预发布后用python检测网页是否有日常链接
2014/06/03 Python
用Python编写一个国际象棋AI程序
2014/11/28 Python
在Mac OS系统上安装Python的Pillow库的教程
2015/11/20 Python
举例讲解Python中metaclass元类的创建与使用
2016/06/30 Python
微信跳一跳自动运行python脚本
2018/01/08 Python
python使用锁访问共享变量实例解析
2018/02/08 Python
python opencv 图像尺寸变换方法
2018/04/02 Python
python 统计列表中不同元素的数量方法
2018/06/29 Python
Python获取基金网站网页内容、使用BeautifulSoup库分析html操作示例
2019/06/04 Python
python实现多线程端口扫描
2019/08/31 Python
基于python实现获取网页图片过程解析
2020/05/11 Python
Ray-Ban雷朋奥地利官网:全球领先的太阳眼镜品牌
2020/10/12 全球购物
strstr()的简单实现
2013/09/26 面试题
学校清明节活动总结
2014/07/04 职场文书
应届毕业生自荐信
2015/03/04 职场文书
关于迟到的检讨书
2015/05/06 职场文书
闪闪红星观后感
2015/06/08 职场文书
初中团支书竞选稿
2015/11/21 职场文书