协程与微线程——概念篇

来源:互联网 发布:淘宝红包口令 编辑:程序博客网 时间:2024/06/06 10:51

  多线程是我们日常开发中或多或少都会遇到的,它涉及了很多概念:线程安全/竞争条件、锁/死锁检测、同步/异步、阻塞/非阻塞、信号量/事件、线程池、生产消费模型等等。面对这些纷繁复杂的东西你是不是有点头大(如果你现在没有以后也会有的),你难道不想找寻一种可以让自己轻松点的方法?反正我是一直在思索尝试,直到最近了解了Erlang、Stackless Python并开始研究协程、微线程以后,我才有种豁然开朗的感觉。

  你可能要问了,“协程、微线程”这名词听起来挺酷的但是它们比起传统的多线程开发好在那里呢?这个问题我确实很难回答出来,也许只有经历过的人能感同身受。不过我倒是可以说说传统开发方式的一些弊端,让你自己来做个判断。

1. 线程安全/临界条件:这是一个很让人讨厌的东西一不小心就会出错而且还很难查找。也许你忘了在操作一个静态(全局)的List或Hashtab时加锁,亦或是你在一个异步回调函数中再次调用了当前线程的方法造成了死锁。如果这些问题在产品上线以后不规律出现那你可以惨了。

2.同步/异步:在同步且阻塞的时候你要自己开线程或者使用线程池,而异步的时候要使用回调函数或者异步委托(.net的Delegate)来处理。而无论同步、异步其实都是借助其他线程来处理,等处理完了就需要你来做一些线程同步的事情。比如通知主线程或对结果处理后再次做异步处理等。这时你就要借助semaphore/event(不是.net中的事件)、join等来帮你完成,这实在是劳心费力的事情。

3.性能:其实这个问题才是最困难的,因为线程这个东西是内核对象开多了再好的机器也负载不了,开少了无法满足需求。所以这时候你就要去学习一些并发模型来解决这个问题。比如做tcp服务端程序你不可能使用一个线程单独去处理一个socket请求,而可能的方法是用一个线程来监控一组socket(select检测),然后对真正需要I/O的再单独开线程处理。


  上面这三条是我以前开发时经常遇到的问题也许不够有代表性但我想表达的观点是:多线程开发涉及的知识点很多而且有些东西需要不断积累经验才能做好。而如果我们能合理的剔除一些关注点那就有可能大幅度降低开发的复杂度。好了终于要切入主题了,我之所以写了上面那么一大堆废话的目的就是为了让大家能够更清晰的和微线程、协程方式做比较。废话少说让我们继续。
  先来说说协程(coroutine),它是指两个子过程通过相互协作完成某个任务。对应到实际代码就是我们经常使用的yield,而更通用的概念就是迭代器模式。也许你会奇怪我们平时经常使用的for/foreach就是协程?确实如此,让我用一个例子来说明。
//子过程1
class Enumerable : IEnumerable
    {
        public IEnumerator GetEnumerator()
        {
            return new Enumerator();
        }
    }
    class Enumerator : IEnumerator
    {
        int _current;
        public object Current
        {
            get { return _current; }
        }

        public bool MoveNext()
        {
            _current += 5;
            if (_current < 100)
                return true;
            else
                return false;
        }

        public void Reset()
        {
            throw new NotImplementedException();
        }       
    }
//子过程2
foreach (var i in new Enumerable())
{
    Console.WriteLine(i);
}
  这个例子很简单是吧,但是你不觉得它很像生产/消费模型吗?Enmerator生产数据,然后在另一个子程序中使用foreach来迭代这些数据并作出处理。而如果这两个子程序是跑在独立线程中,是不是就感觉和多线程开发有点关系了。当然对于现在的大多数语言来说都提供了yield关键字它可以简化迭代器的编写。上面的代码简化后如下所示:
//子过程1
IEnumerable Enumerable()
{
    var current = 0;
    while(current < 100)
    {
        current += 5;
        yield return current;
    }
}
//子过程2
foreach (var i in new Enumerable())
{
    Console.WriteLine(i);
}
  我理解的协程概念就这么多下面来介绍微线程。你可以把它理解为相对于线程更轻量级的逻辑线程,这就好比线程相对于进程来说更轻量级不知道这算不算是纤程的概念。总之微线程都是逻辑上的概念而真实运行的只有一个线程。我们知道在同一时间片只能有一个线程针对一个cpu执行指令,而且其他的线程必须被挂起。然后内核调度程序不断的唤醒/挂起线程来模拟多个任务的执行,而如果对线程使用不当就会造成过多的线程切换开销。而实际上很多线程切换是没有必要的,因为真正工作的线程在同一时间只有一个因此我们可以写一个很简单的调度来实现微线程之间的切换。这同时还带了一个很大的好处就是微线程中的代码永远是线程安全的因为不会出现竞争条件了。但这同时引入了一个问题就是微线程中怎么实现挂起/激活操作,因为我们不可能在微线程中使用Sleep这种阻塞调用这会让所有的微线程全部阻塞。在现有的语言基础上解决这个问题的最简单办法就是使用协程,或者说使用迭代器模式,yield关键字。微线程中如果需要阻塞的时候就yield return 把把具体工作交给外部协作协程来处理,等处理完了再回到微线程中继续执行yield return后面的代码。这个过程在概念上与实际的线程挂起/唤醒是相同的,并且切换代价要小的多。这样一来协程和微线程就很好的结合在一起,如果你没理解也没关系我在下篇文章中中将给出具体的实现源码和示例程序

 

 

 

 

 

原创粉丝点击