协程实现原理

2020-05-13  本文已影响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()。

上一篇下一篇

猜你喜欢

热点阅读