Golang Goroutine 入门使用

  goroutine(协程)是golang最重要的特性,是唯一在语言层面支持的主流语言,也是golang主打的优势——“高并发”的实现所在。
 
1.为什么要协程?
2.goroutine怎么用

1.为什么要协程

  追求cpu的最大利用。同一台设备上同时运行着许多的进程,同时向cpu发出许多条指令,在这些指令中,一部分可以马上执行得到结果,但有一些系统IO相关的,它们需要一些输入,比如服务器监听一个端口,一直到客户端连接上才需要继续运行之后的指令;比如等待磁盘读取一个文件。
  这些碎片时间,我们也希望能够利用起来。幸好,系统内核(kernal)会帮我们处理这堆麻烦。我们把一串中途需要等待IO的指令放进一个内核线程中,当需要等待的时候,内核将这个线程的状态保存好,等满足条件的时候,再将这个线程的指令发送给cpu执行,在这期间内,cpu就能处理其他线程的指令,实现cpu的最大利用。
  内核维护线程状态的操作叫做保存现场-恢复现场,这是一个有开销的行为,如果我们的程序只有几个读写文件的操作,那么没关系。使用内核线程的除了我们的自己程序外,还有许许多多正在运行的程序和后台线程,内核线程的切换是我们单个程序控制不了也不应该控制的。但是,如果我们的程序频繁的切换内核线程,这绝对是一件不合理的事情
  于是,大部分语言有了用户级线程和线程池的概念。之前本来交给内核的事情——给cpu分配任务,现在先在程序内部自我合理规划一遍,减少内核线程切换的频率,转而在用户程序内切换。而用户级的线程,因为信息量更少,数据不需要从程序和内核间拷贝,所以消耗更少,更轻量。但这不意味这用户线程就是没有开销的,用户线程还会计入gc,所以一些高频创建销毁的场景下,还会用线程池来管理这些用户线程,尽可能复用。
  golang在内置了线程一个线程池——MPG模型,其中G就代表goroutine,可以认为是一个更轻量级的用户线程。

2.goroutine怎么用

  从上面来看,golang中的协程也并没有高级到哪去,为什么还能以此流行起来呢?
  因为使用简单!

func goHappy() {
	//好多代码
	//...
}

  我们想同时运行很多个上面的代码,也就是多线程编程。在golang中我们只需要

go goHappy()

  就完成了多线程调用,至于他们是不是在多个线程中运行,谁先谁后,不用担心,golang的MPG模型会处理好(在大多数情况下适应)。
  这是最简单的情况,在实际使用中,还要配合一些其他东西。

  2.1 控制多个协程

  当我们将一堆耗时的操作分散给多个协程同时处理,并且希望他们都处理完成再执行之后的操作。尤其go的主线程,并不会等待所有协程执行完才关闭,如果我们不等待协程,可能协程还没走完,程序进程就关闭了。
  这时候我们需要用到sync.WaitGroup

import (
	"sync"	
)

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 100; i++ {
		wg.Add(1) //计数加1
		go goHappy(&wg)	//传指针
	}
	wg.Wait()	//等待知道wg的计数变成0
}

func goHappy(wg *sync.WaitGroup) {
	defer wg.Done()	//计数减1
	println("golang so easy")
}

  2.2 关闭协程

  有一些任务是循环监听网络IO输入,有些是定时循环触发的,我们用context.Context来控制关闭它。

func oneDay() {
	ctx, cancel := context.WithCancel(context.Background())
	go Tick(ctx)
	time.AfterFunc(time.Hour*24, cancel)
}

func Tick(ctx context.Context) {
	ticker := time.NewTimer(time.Hour)
	for {
		select {
		case <-ctx.Done(): //当调用cancel时,触发这里
			return
		case <-ticker.C:
			println("A hour gone")
		}
	}
}

  这在我们优雅关闭服务器时就是这么使用的,通知各个模块处理好最后的工作,体面的结束各个协程。

  2.3 协程之间通信

  golang不鼓励我们多个协程共享数据,提出要用通信的方式让各个协程之间协作,并给出了类似linux的通道——channel。
  channel当然是线程安全的,即多个协程同时读取一个channel,也只有一个协程能读取到数据。channel可以设置缓冲,实现同步和异步通信。

	sync := make(chan interface{})
	sync <- 1

  这是一个同步通信,只有其他协程读取了这个channel中的数据,才会继续执行,否则一直等待。
  如果要异步通信,即继续执行下去,就给一个缓冲区

	async := make(chan interface{},1)
	async <- 1

  合理设置缓冲区大小,也能提高并发量
  比如写日志时,多个协程同时都有日志输出,但只能有一个协程去向文件中写,这样在日志密集的时候,日志内容先存在channel的缓冲区中,不影响业务性能,负责写日志的协程可以慢慢写,不会拖慢整个程序。
  当然,如果日志量一直很大,超过了缓冲区,还是会慢下来,这时应该考虑日志是否合理了。

  2.4 协程共享数据

  虽然channel能够满足大部分场景的需求,但有时候我们仍然不得不共享数据,尤其是大量的数据。比如读写数据库时,我们可以只用一个协程读写数据库,其他协程需要数据时通过channel请求,之后再通过channel返回数据,但是,读取数据库需要等待时间,我们想要修改一个用户的数据时,也不需要让所有用户都短暂时间内不可操作。
  假如不做限制,会造成数据不同步。我读了,还没来得及改,你先改了,我在不知情的情况下又改了等等情况。这是一大块内容,这里不细说了。
  golang中有锁的相关库(sync),包括常用的互斥锁(Mutex)、读写锁(RWMutex),可以保证在一个协程操作一处数据的时候,其他线程如果也要操作,只能先挂起等待。
  内存中的一些数据,我们为了保证它的原子性(同一时间只有一个线程能操作),golang提供了原子操作库sync/atomic。自旋锁就可以用这个库实现。
  锁不是golang的内容,而是所有多线程同步的关键内容,这里的多线程不一定是一个程序中,也不一定是一台机器中,甚至可能是分布式的,以后再专门细将,这里主要是gorountine的常用方法。

ps: 欢迎大家指出文中错误和不足,提出意见,以免误导。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Vongolar

想喝杯咖啡

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值