Golang如何安全使用协程

有运维或运维开发方面的需求,可以联系博主QQ 452336092或Email:admin#centos.bz(收费)

文章目录
[隐藏]

什么是协程?

协程(Coroutine)是在1963年由Melvin E. Conway USAF, Bedford, MA等人提出的一个概念。而且协程的概念是早于线程(Thread)提出的。但是由于协程是非抢占式的调度,无法实现公平的任务调用。也无法直接利用多核优势。因此,我们不能武断地说协程是比线程更高级的技术。

尽管,在任务调度上,协程是弱于线程的。但是在资源消耗上,协程则是极低的。一个线程的内存在MB级别,而协程只需要KB级别。而且线程的调度需要内核态与用户的频繁切入切出,资源消耗也不小。

我们把协程的基本特点归纳为:

  • 协程调度机制无法实现公平调度
  • 协程的资源开销是非常低的,一台普通的服务器就可以支持百万协程。

那么,近几年为何协程的概念可以大热。我认为一个特殊的场景使得协程能够广泛的发挥其优势,并且屏蔽掉了劣势 — 网络编程。

与一般的计算机程序相比,网络编程有其独有的特点。

  • 高并发(每秒钟上千数万的单机访问量)
  • Request/Response。程序生命期端(毫秒,秒级)
  • 高IO,低计算(连接数据库,请求API)。

最开始的网络程序其实就是一个线程一个请求设计的(Apache)。后来,随着网络的普及,诞生了C10K问题。Nginx 通过单线程异步IO把网络程序的执行流程进行了乱序化,通过IO事件机制最大化的保证了CPU的利用率。

至此,现代网络程序的架构已经形成。基于IO事件调度的异步编程。其代表作恐怕就属NodeJS了吧。

异步编程的槽点

异步编程为了追求程序的性能,强行的将线性的程序打乱,程序变得非常的混乱与复杂。对程序状态的管理也变得异常困难。写过Nginx C Module的同学应该知道我说的是什么。

我们开始吐槽NodeJS 那恶心的层层Callback。

Golang

再我们疯狂被NodeJS的层层回调恶心到的时候,Golang 作为名门之后开始走入我们的视野。并且迅速的在Web后端极速的跑马圈地。其代表者Docker 以及围绕这Docker展开的整个容器生态圈欣欣向荣起来。其最大的卖点 – 协程 开始真正的流行与讨论起来。

我们开始向写PHP一样来写全异步IO的程序。看上去美好极了,仿佛世界就是这样了。

在网络编程中,我们可以理解为Golang 的协程本质上其实就是对IO事件的封装,并且通过语言级的支持让异步的代码看上去像同步执行的一样。

安全正确的使用Golang协程

我们在使用Golang的时候不一定就要非常深入的理解与学习协程的种种技术细节。但我们得时时刻刻的警惕起来。因为无论我们的代码写得是多么的同步化(看上去所有的IO好像都是阻塞的),但其本质还是异步。在协程里面会进行各种各种的中断与调度。

  • 谨慎的使用全局变量

当我们在定义一个全局变量的时候,我们就是在埋下一颗炸弹。它可能会以你想象不到的姿势点燃并爆炸。最容易出错的就是协程竞争与数据污染。

  • 协程竞争

协程竞争指的是不同的协程同时对一个对象(尤其是原生的map)进行数据读写操作。golang 在出现这种竞争的时候将会抛出fatal错误,并且程序退出。

无论是操作struct 、int、 string map 、 slice 都请做好资源锁。

我建议的全局变量应该是只读的,最进行一次初始化。通常在main函数或package的init函数中执行。

  • 数据污染

数据污染指的是程序经过一段运行时间之后,全局变量里面的值变得与期望值不一样。

  • 协程饿死

由于协程是非抢占的式的调度,那么就有可能某一个协程长时间的运行一个协程,导致其他协程无法被调度,直到饿死。

这样的情况,通常发生再大规模的处理程序中。比如数据迭代处理,大型多媒体资源处理等生命期长的协程中。

在Golang中,提供了runtime.Gosched() 函数来主动挂起当前协程,把CPU出让,让调度器有机会调度其他协程。

func BigJob() {

    for i := 0;i < 100000000;i ++ {
        //do something ...
        //每执行1W个任务出让CPU
        if i % 10000 == 0 {
            runtime.Gosched()
        }
    }
}

总结

Golang 再语言级别提供了协程支持。让异步编程变得更加轻松,但是它并没有解决存在于并发环境(多线程)中的种种问题。我们需要掌握并发环境中编程的技术知识用来规避和解决问题。

我们要非常了解协程的特点与调度模型。并且通过仔细的检验来保证协程尽可能的被公平调度(至少在web系统中,这是一个非常重要的特性)。

原文出处:imisko -> http://blog.imisko.com/2017/08/02/golang-coroutine/

打赏

如果此文对你有所帮助,请随意打赏鼓励作者^_^