美文网首页
协程实现原理

协程实现原理

作者: 元隐 | 来源:发表于2020-05-13 15:08 被阅读0次

    [toc]

    进程/线程/协程

    背景

    单任务处理:早期的计算机是什么样的, 卡带机? 真空管?

    多任务处理系统: 多线程, 多进程

    进程和线程的区别

    进程有独立的内存空间, 操作系统创建一个进程后,该进程会自动申请一个名为主线程或首要线程的线程。
    线程依赖于进程, 共享内存空间. 只拥有少量的私有内存空间,如程序计数器,栈.

    Linux创建进程采用fork()和exec()

    fork: 采用复制当前进程的方式来创建子进程,此时子进程与父进程的区别仅在于pid, ppid以及资源统计量(比如挂起的信号)
    exec:读取可执行文件并载入地址空间执行;一般称之为exec函数族,有一系列exec开头的函数,比如execl, execve等
    fork过程复制资源包括代码段,数据段,堆,栈。fork调用者所在进程便是父进程,新创建的进程便是子进程;在fork调用结束,从内核返回两次,一次继续执行父进程,一次进入执行子进程。

    Linux用户级线程创建:通过pthread库中的pthread_create()创建线程
    Linux内核线程创建: 通过kthread_create()

    线程的管理

    在Linux内核中,线程是以轻量级进程的形式存在的,拥有独立的进程表项;而所有的线程创建、同步、删除等操作都在核外pthread库中进行。这种模式称为基于核心轻量级进程的"一对一"线程模型,也就是一个线程实体对应一个核心轻量级进程,线程之间的管理在核外函数库中实现。内核为每一个进程构造了一个管理线程,负责处理线程相关的管理工作,这样做的好处就是线程的调度由核心完成了,而其他诸如线程取消、线程间的同步等工作,都是在核外线程库中完成的。

    1. 协程
      进程和线程都是操作系统定义的概念, 现在的软件开发语言也大多采用了操作系统的实现, 即: 在java里new Thread(), 实质是调用操作系统的pthred.
      协程操作系统并没有定义, 而且协程的概念出现很久, 但是并不清晰. 到底什么样的实现满足协程的概念, 并没有一个权威的定义, 目前能看到两种实现方式, 1是保存栈状态 2是不保存栈状态(参见javascript的多异步处理,回调,promise)

      协程解决了什么问题:
      线程说: 进程太重了, 每次创建要申请内存, 定义进程结构...., 所以有了线程的概念, 共享进程内存空间, 初始化需要做的工作很少.
      协程说: 线程太重了, 还是需要操作系统调用, 且令人讨厌的进程切换, 想像一下你正在行云流水般的处理你的程序逻辑, 来一个ding, 你思路突然被打断, 要先去处理另一个问题....
      协程普遍上调度不在交由操作系统完成, 而是开发人员定义了代码的yield点,

    协程有多古老

    1960年夏天,D. E. Knuth就是利用开车横穿美国去加州理工读研究生的时间,对着Burroughs 205机器指令集手写COBOL编译器。最早提出“协程”概念的Melvin Conway,也是从如何写一个只扫描一遍程序(one-pass)的COBOL编译器出发。

    协程的实现

    不信来看看javascript发展史
    javascript是单线程运行的, 来看一下javascript异步多任务的处理方式

    promise的链式调用

    // 例2
    Promise.resolve(1)
      .then(x => x + 1)
      .then(x => {
        throw new Error('My Error')
      })
      .catch(() => 1)
      .then(x => x + 1)
      .then(x => console.log(x)) //2
      .catch(console.error)
    
    

    生成器Generators/ yield

    function *foo(x) {
      let y = 2 * (yield (x + 1))
      let z = yield (y / 3)
      return (x + y + z)
    }
    let it = foo(5)
    console.log(it.next())   // => {value: 6, done: false}
    console.log(it.next(12)) // => {value: 8, done: false}
    console.log(it.next(13)) // => {value: 42, done: true}
    

    nodeJs 有npm install co ,co库, 专门处理上述Generator. 但是原理不会变.
    Generator 函数调用和普通函数不同,它会返回一个迭代器(funciton foo)

    function* helloWorldGenerator() {
      yield 'hello';
      yield 'world';
      return 'ending';
    }
    
    var hw = helloWorldGenerator();
    
    hw.next()
    // { value: 'hello', done: false }
    
    hw.next()
    // { value: 'world', done: false }
    
    hw.next()
    // { value: 'ending', done: true }
    
    hw.next()
    // { value: undefined, done: true }
    
    

    怎么用

    function* loadUI() { 
        showLoadingScreen(); 
        yield loadUIDataAsynchronously(); 
        hideLoadingScreen(); 
    } 
    var loader = loadUI();
    // 加载UI
    loader.next() 
    
    // 卸载UI
    loader.next()
    

    async/await

    async function async1() {
      return "1"
    }
    console.log(async1()) // -> Promise {<resolved>: "1"}
    
    

    对着javascript看java, 有种似曾相似的感觉

    @Generator
    public class HelloWorld {
    
        @GeneratorMethod
        public Iterable<Integer> it() {
            yield(1);
            yield(2);
            yield(3);
            yield(4);
    
            yield(5);
            yield(6);
            return null;
        }
    
        public static void main(String[] args) {
            HelloWorld hello = new HelloWorld();
            for (Integer message : hello.it()) {
                System.out.println(message);
            }
            System.out.println("end");
        }
    
    
    }
    
    

    Quasar

    new Fiber<V>() {
    
    @Override
    protected V run() throws SuspendExecution, InterruptedException {
           // your code
       }
    }.start();
    
    new Fiber<Void>(new SuspendableRunnable() {
    
    public void run() throws SuspendExecution, InterruptedException {
       // your code
     }
    }).start();  
    
    

    FiberScheduler是Quasar框架中核心的任务调度器,负责管理任务的工作者线程WorkerThread,之前提到的他是一个FiberForkJoinScheduler。
    ForkJoinPool的默认初始化个数为Runtime.getRuntime().availableProcessors()。

    相关文章

      网友评论

          本文标题:协程实现原理

          本文链接:https://www.haomeiwen.com/subject/gfbnnhtx.html