导航

协程不是轻量级线程

Posted on 2019-03-30 02:34  李日天  阅读(341)  评论(0)    收藏  举报

今天在网上看到了一种说法: "协程是一种轻量级线程"。

这种说法引发了我很深的思考。如果协程是轻量级的线程,那协程就应该是操作系统调度的基本单位。而协程根本没有参与到CPU时间的调度。线程的话,在多核心处理器里面,是并行的,你启动一个线程之后你要想控制它,你得做系统调用thread_cancel 或者最好情况是发送信号告诉线程里面的条件变量让线程自己去退出。所以说它是一种微线程这种说法是不严谨的。

协程是一组序列化的子过程,然后用户能像指挥家一样调度交叉执行。yield 可以理解成为,做到这里,然后暂停一下,然后产出结果,等待上一个上下文对自己调度。多个协程协作好比就是你一个人其实同时只能做一件事,但是你把几个任务拆成几截来交叉执行。

协程是阻塞的,而线程是非阻塞的。两个协程并不是线程,他们根本不并行,协程实际上是一个很普通的函数,或者一个代码块,或者子过程,他是基于用户自己定制的调度行为。因此协程是一串颗粒度比函数还小的用户控制的过程。

不过,从另一个角度看,协程能把小过程串起来,让人们看起来有并发的感觉,从这个角度来看或许是一种“轻量级线程”。

协程的作用

历史上是协程的出线是早于线程,是OS用来模拟多任务并发,但是因为它是非抢占式的,导致多任务时间片不能公平分享,所以后来全部废弃了协程改成抢占式的线程。

而协程现在的作用更多是把原来使用 异步+回调方式写的代码,用看似同步的方式写出来,更便于书写易读。

使用协程的目的,更多是为了创造一个可以随时中断随时恢复的函数。

譬如说你用C#写一个“依次下载这些文件”的库函数,然后要给GUI调用,而且GUI当然不能再下载的时候卡死,怎么办?

//假设有 Task<T> DownloadFile(string fileName);
async void buttonDownload_Clicked(...)
{
    buttonDownload.Enabled=false;
    foreach(var file in files)
        {
            var content = await DownloadFile(file);
            textBox.Text += content;
        }
    buttonDownload.Enabled=true;
}

原来要怎么办?一般你想用异步函数的回调来写循环,每个循环都要单独拆开成一个(或者两个,看风格)来写,整份代码简直就没法看了。

//假设有 void DownloadFile(string fileName, Action<string> callback);
void _1(string content, IEnumerator<string> e) {       
    this.BeginInvoke(new MethodInvoker(()=> 
    {
        textBox.Text += content; 
        if (e.Next()) { 
            var file = e.Current; 
            DownloadFile(file, content=>_1(content, e));
        } 
        else 
        { 
            buttonDownload.Enabled=true; 
        } 
    }); 
}
 
void buttonDownload_Clicked(...){
    buttonDownload.Enabled=false; 
    var e = files.CreateEnumerator(); 
    if (e.Next()) { 
        var file = e.Current; 
        DownloadFile(file, content=>_1(content, e)); 
    } 
    else
    { 
        buttonDownload.Enabled=true;
    } 
}
注意展开后有大量重复的代码。GacUI为了解决类似的问题,提前让lambda表达式可以给自己起名字,自己递归,简直爽爆……
写类似的代码有一个要注意的地方就是,更新控件的状态不能乱。有些时候该disable的没有disable,该enable的没有enable,很容易就造成一个异步函数没做完,然后你可以点另一个按钮,然后两个异步函数互相搞,最后程序就飞到哪里去了。
C#解决了这个async-await的问题之后,得到了大家的广泛认同,于是VC++、Python、Javascript等著名语言,都纷纷模仿这个写法(而不是lisp的那种写法)加入了这个特性。
协程的优势在于他可以不改变操作系统上下文来实现代码逻辑块的切换,确实是一种用户角度上的"线程“。