Tokio简介

什么是Tokio

Tokio是一个事件驱动的运行时(官网用词runtime),用来编写rust异步代码。所谓异步代码,即和同步代码对应而言。比如同步读磁盘上的文件:应用代码调用读文件的函数,函数封装了操作系统的系统调用,通知操作系统读入文件内容,操作系统把文件内容全部读出后才回到应用代码,继续执行之后的逻辑。在操作系统读文件内容的过程中,应用程序无所事事,默默地等待。而异步读入文件内容,异步调用通知操作系统读文件后立即返回,应用程序有机会在操作系统读文件时干其他事情,当操作系统读完文件内容后会以某种形式通知应用程序读文件已经完成

Tokio为异步编程提供了这些东西:
1. 一些基本工具。比如同步原语(synchronization primitives),管道(channels),计时器,延时,intervals(不知道这是什么东西,描述一个时间间隔么?)。
2. API。网络相关的tcp,udp异步函数,异步文件操作函数,异步的进程和信号管理。
3. 调度器,用来调度tasks(类似其他语言的异步框架的绿色线程或协程的概念)。
4. I/O驱动(官网用语IO driver),使用原生操作系统的事件队列接口。比如:linux下的epoll,freeBSD下的kqueue,windows下的IOCP。
5. 高性能的计时器。

快速 Fast

Tokio使用的是Rust编程语言,当然有资格有能力做到快速。Tokio要设计时也把速度放在非常重要的位置。

零开销抽象 Zero-cost abstractions

Tokio广泛使用Future这个异步概念(类似node.js中的promise,据我理解,不同语言的异步框架中使用的future和promise是非常近似的概念)。官方文档声称tokio的future和其他语言的future实现不一样。它是独一无二的(unique)。Tokio中的future会被编译器编译成一个状态机。做异步事件的同步处理时,分配内存,及其他future的实现中有开销的地方,tokio都是零开销。(我觉得协程类的异步框架都要维护状态机用来记录栈的信息么,状态机并不unique。unique的是零开销抽象)。

零开销抽象并不意味着tikio自身没有开销,而指是不可能再用其他什么方法减少开销,开销已经减到最少。

并发 Concurrency

Tokio提供了一个多线程,work-stealing(不知用哪个中文合适)的调度器。Tokio是开箱即用的,意味着,当你使用tokio运行时的时候,你就可能充分利用电脑上所有的cpu核心。

现代计算机通过增加中央处理器的核心来增加性能。所以,能够利用好多核心对于高性能的应用来说是至关重要的。

非阻塞I/O Non-blocking I/O

tokio使用操作系统原生的多路复用技术(linux的epoll,freeBSD的kqueue,windows的IOCP),一个线程可以同时管理多个socket。这样能减少系统调用(system call),提高应用的性能。

可靠 Reliable

Tokio在设计时就竭尽所能避免应用程序因使用tokio不当产生BUG,但tokio当然不可能完全做到这点。Tokio的API设计得不易于用错。这样,在项目的最后一天,你就可以信心满满地交付了。

所有权和类型系统 Ownership and type system

Tokio籍由Rust语言特有的所有权系统和严格的类型安全,能避免很多内存安全方面的错误。它能避免绝大多数常见的内存出错:访问未初始化内存,访问释放后内存,内存重复释放(Double Free)。并且,做到这些并不需要付出运行时的额外开销。(回忆自己用C/C++写的复杂应用时追踪偶发的内存出错BUG真是无比痛苦。)

另外,严格的类型安全系统也使得难以错误使用API。比如,Tokio中的互斥锁并不需要开发者显式释放。(这一点并不稀奇,C++的RAII,Go的defer,python的with也能做到,稍微现代的语言都有类似机制。)

反向压力传递 Backpressure

Tokio自带了压力反向传递的功能,这真是真是一个让人称赞的功能,真香。所谓反向压力传递,可以这么理解:当消费者消费的速度小于生产者生产的速度时,数据会在内存中越堆越多,最终把内存撑暴。反向压消费力传递指的是,消费者的压力会反向传递给生产者,让生产者减慢生产的速度以匹配消费者的消费速度。很多其他的库并没有提供这个功能,于是应用需要自己实现一个。但要实现一个高性能的类似功能并不是一件容易的事情。(回忆起自己有段时间用C++的asio异步网络库写的服务器。我的实现是当存放任务的队列到达一个阈值时就让生产者线程sleep很短一段时间,再从网络中读取数据,生产任务,结果性能大降。我相信tokio一定优雅高效地提供了这个功能。)

tokio官方文档中表示,Tokio中的生产者天生是lazy的,它们会轮循消费者,只有当消费者充许增加数据时,生产者才生产数据。

取消 Cancellation

应用的业务代码持有一个future,它描述了异步计算的结果。如果当业务代码认为并不需要这个结果时,则可以不再持有这个future(让它的生命期结束)。这样,异步计算就会及时结束,不再执行不必要的计算。官方文档表示这主要受益于Tokio的轮循设计。

多谢了Rust的权限模型,异步执行部分能通过实现drop这个特征(trait,类似c++,c#,java的接口),及时感知到future已经被丢弃了。

轻量级 Lightweight

Tokio的伸缩性很好,且伸缩时不给应用增加额外负担。这样,tokio能在资源受限的环境下发展得不错。

没有垃圾回收 No garbage collector

tokio使用的是rust语言,所有没有垃圾回收机制,也就避免了在有垃圾回收的语言中普遍存在的“世界暂停”问题。应用会周期性的启动垃圾回收,在极限性能要求的情况下,这个问题就会暴露出来。Tokio的这个特性使得它适合中实时环境中使用。

模块化 Modular

尽管Tokio提供了非常多开箱即用的功能,并且是用模块式的方式组织的。每一个component都使用一个独立的库library。用户可以精确的设定需要使用哪些特性而只导入相应的库,其他不需要使用的库则不会被编译进最终的应用程序中。很多其他著名的rust库也使用了tokio,比如hyper和actix。

Rust语言的宏编程极简教程Macro

Rust的宏和c/c++的异同

Rust语言的宏设计得比较复杂,当然也可以说功能非常强大,跟c/c++语言的宏非常不一样。
相同点 当然是宏能够让代码更精简,码农可以少敲很多样板代码(boilerplate code)。当然你说函数和类不也可以抽象代码,使代码精简吗?但宏能够获取很多在编译期的信息,函数和类却不能。比如,代码所在的文件和行数,这个信息只有宏才能获得。
不同点 是,C/C++的宏只是在预编译期简单地做模式替换,预编译后再交由编译器。c/c++的include头文件展开也是在预编译阶段。但Rust宏的展开不是发生在预编译期,而是编译期。于是Rust能够获得更复杂的更详细的编译器的信息。这里有两个概念Token Tree和AST。如果有大学阶段学习过的编译原理课程的背景就很容易理解。

Token Tree

Token Tree简写成TT。编译器拿到源代码后先做词法分析,即把源代码字节流分成一个一个的token。token和token之间的逻辑关系也记录下来。比如下面的一个简单语句:

a + b + (c + d[0]) + e

的Token Tree就长这个样子:

«a» «+» «b» «+» «(   )» «+» «e»
         ╭────────┴──────────╮
          «c» «+» «d» «[   ]»
                       ╭─┴─╮
                        «0»

AST

AST的全称是Abstract Syntax Tree,抽象语法树。编译器把Token Tree翻译成AST,这是一个更有利于编译器理解源代码的结构。上面的TT翻译成对应的AST后长这个样子:

              ┌─────────┐
              │ BinOp   │
              │ op: Add │
            ┌╴│ lhs: ◌  │
┌─────────┐ │ │ rhs: ◌  │╶┐ ┌─────────┐
│ Var     │╶┘ └─────────┘ └╴│ BinOp   │
│ name: a │                 │ op: Add │
└─────────┘               ┌╴│ lhs: ◌  │
              ┌─────────┐ │ │ rhs: ◌  │╶┐ ┌─────────┐
              │ Var     │╶┘ └─────────┘ └╴│ BinOp   │
              │ name: b │                 │ op: Add │
              └─────────┘               ┌╴│ lhs: ◌  │
                            ┌─────────┐ │ │ rhs: ◌  │╶┐ ┌─────────┐
                            │ BinOp   │╶┘ └─────────┘ └╴│ Var     │
                            │ op: Add │                 │ name: e │
                          ┌╴│ lhs: ◌  │                 └─────────┘
              ┌─────────┐ │ │ rhs: ◌  │╶┐ ┌─────────┐
              │ Var     │╶┘ └─────────┘ └╴│ Index   │
              │ name: c │               ┌╴│ arr: ◌  │
              └─────────┘   ┌─────────┐ │ │ ind: ◌  │╶┐ ┌─────────┐
                            │ Var     │╶┘ └─────────┘ └╴│ LitInt  │
                            │ name: d │                 │ val: 0  │
                            └─────────┘                 └─────────┘

AST图有排版有点乱,我懒得改了。这两人个图,TT和AST都是从 https://danielkeep.github.io/tlborm/book/mbe-syn-source-analysis.html 抄来的。您可以移步到那个页面。
重点来了,Rust语言的宏展开就发生在编译器生成了源代码的AST的时候。Rust语言的宏可以从AST获得非常丰富的信息,并操作AST。

Rust的宏有两种

Rust语言设计了两种宏,一种叫Declarative Macros(声明式宏),以前的版本也有Macros by example这个名字,旧的名字怪怪的。另一种是Procedure Macros(过程宏)我不明白为什么取这个名字。Declarative Macro相对Procedure Macros要简单一些,而过程宏则可以玩出另复杂的花样。

Declarative Macro

常见的这样一个定义vector的Rust语句:

let v: Vec<u32> = vec![1, 2, 3];

就使用了声明式宏。这个宏的定义如下:

#[macro_export]
macro_rules! vec {
    ( (x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            (
                temp_vec.push(x);
            )*
            temp_vec
        }
    };
}

#[macro_export]表示将定义的宏将导出crate,这样不仅在定义了这个macro的crate的内部可以使用该宏,导入该crate的其它crate亦可使用这个宏。
然后看这个声明宏的定义,非常像rust语中的匹配表达式(match expression)。(x:expr)()* 语法表,表示展开为0条或者多条表达式,其他的都比较好理解。
示匹配一个表达式并把匹配到的表达式存在变量x里。(x:expr)被(),* 包裹。这里有点像正则式的描述,它表示匹配0个或者多个被逗号,分隔的表达式。 =>后面的部分就是描述怎么展开宏了,除了也用到类似正则式的()* 语法,表示展开为0条或者多条表达式,其他的都比较好理解。
另外,这个宏的定义只使用了一条规则,实际上可以像匹配表达式一样定义多个规则。

Procedure Macros

过程宏可以为一个类自动生成特性trait(类似c++中的纯虚函数,java/c#中的interface)的实现;还能实现类似python中的decoration概念,如下代码所示:

#[route(GET, "/")]
fn index() {
// ...
}

这就跟python的http框架flask用来定义路由的decoration的写法几乎一样了。
不过,编写procedure macros需要使用两个辅助的crate用来操作AST,它们是sync和quote。挺复杂的,你们还是去看官方方档吧。

hygiene是什么

在读官方的Rust Macro相关的资料时,时不时看到hygiene这个詞。一开始不懂这是个什么玩艺儿。后来,才知道原来是rust的宏机制避免了c/c++简单的宏经常出现的副作用。在Rust的话术里,把这个东东称为Hygiene。

就写到这吧,写文章好累呀,还是写代码轻松有意思一些。

Rust语言中的单位类型unit type是什么

每个学习Rust语言的读者碰到单位类型unit type ()的时候都非常疑惑这究竟是个什么鬼?
本文总结了stackoverflow上面的贴子 What is the purpose of the unit type in Rust?加上我自己的理解。

  1. unit type是一个类型,有且仅有一个值,都写成小括号()
  2. 单元类型()类似c/c++/java语言中的void。当一个函数并不需要返回值的时候,c/c++/java中函数返回void,rust则返回()。但语法层面上,void仅仅只是一个类型,该类型没有任何值;而单位类型()既是一个类型,同时又是该类型的值。
  3. 单元类型()也类似c/c++/java中的null,但却有很大不同。 null是一个特殊值,可以赋给不可类型的值,例如java中的对象,c中指向struct实例的指针,c++中的对象指针。但在rust中,()不可以赋值给除单元类型外的其它的类型的变量,()只能赋值给()。
  4. Rust标准库中使用单元类型()的一个例子是HashSet。一个HashSet只不过是HashMap的一个非常简单地包裹,写作:HashMap<T, ()>。HashMap的第二个泛型类型参数即用了单元类型()
  5. 可以用Result<(), MyErrorType>代替Option,某些开发者认为Result<(), MyErrorType>语义上能更简明地表示一个“结果”。

You could argue that Option is ‘better’, but I disagree; the Result’s naming conventions mean it’s explicitly clear what’s going on.

使用Prettier,Husky和lint-staged,在提交javascript代码前自动格式化代码

目标

为防止提交不合格的代码排版到仓库中,我们希望在提交前格式化代码,使之符合项目的代码格式规范,而且尽可能保证逻辑正确。即目标有两点:
1. 对git staged的代码强行格式化,使符合规范。对于没有被staged的代码或文件,将完全忽略。
2. 跑单元测试。只有通过了单元测试才能提交代码。

安装依赖

$ npm install --save-dev husky lint-staged

先安装husky和lint-staged。注间在安装husky之前需要确保工程目录已经是一个合法的git仓库。因为,安装husky时需要给git仓库安装预提交钩子(precommit hook)。
Lint-staged提供了一个关键的功能,它能找到所有被git staged的文件,而不是工程下面所有的文件,然后通过管道交给Prettier做格式化。

修改npm脚本

对npm script做如下改动:

  "scripts": {
    "precommit": "lint-staged"
  },
  "lint-staged": {
    "*.{js,jsx,json}": ["prettier --write", "git add"]
  }

scripts.precommit由Husky处理,它会依照配置在把代码正式提交到仓库前运行lint-staged(当然我们也可以配置其他命令)。
Lint-staged再找到属于它自己的配置部分,即lint-staged键下面的配置。
在此例中,所有staged状态的,且扩展名是.js, .jsx, 和.json文件都会自动交由Prettier做格式化,然后再提交。

为何一定要用lint-staged

You might wonder why we don’t just use Husky to run a few npm scripts.
你可以会疑惑,为什么一定要罗里罗嗦地用lint-staged,用Husky直接调用npm脚本不更简单么。
这是因为我们用lint-staged拿到staged状态的文件,而不是所有的文件。
你的node工程文件中可能已经有如下做代码格式化的脚本

"format": "prettier --write '**/*.{js,jsx}'"

你可能会因此如下配置

"lint-staged": {
  "*.{js,jsx,json}": ["npm run format", "git add"]
}

如果是这样配置的话,就没有必要用lint-staged了。因为,这样的配置会把prettier应用到所有匹配.js,.jsx.json的文件上,而不是仅staged状态的文件。

把跑单元测试加入预提交检查

Husky还能够帮你把跑单元测试加入到预提交检查中,保证只有通过了单元测试才正式提交代码到仓库。
如下是一个典型的配置:

"scripts": {
    "test": "exit 0",
    "precommit": "lint-staged && npm test"
  },
  "lint-staged": {
    "*.{js,jsx,json}": ["prettier --write", "git add"]
  }

实际项目中要修改scripts.test部分,视工程使用的单元测试框架Jest, 或Mocha ,本示例简单的返回0,表示无条件通过。

python用multiprocessing起子进程,父进程被杀掉时,如何让子进程也自动退出

最近用python3写了一段代码,代码会起一个子进程做一些辅助的事情。但发现父进程被杀死后,这个子进程变成了孤儿进程,仍然在运行。
起进程的代码大概如下:

from multiprocessing import Process, Queue
__q = Queue()
__p = Process(target=__run, args=(__q,))
__p.start()

很不方便,那怎样让父进程退出后,子进程也跟着退出呢。
一番google之后,原来可以用一行代码解决:

from multiprocessing import Process, Queue
__q = Queue()
__p = Process(target=__run, args=(__q,))
__p.daemon = True    # 加上这一行代码
__p.start()

设置子进程的daemon属性为True。这样,当在控制台上用ctrl-c,退发送SIGTERM杀掉父进程后,子进程也会跟着退出,不会成为孤儿进程。
参考python官方的解释:

daemon
The process’s daemon flag, a Boolean value. This must be set before start() is called.
The initial value is inherited from the creating process.
When a process exits, it attempts to terminate all of its daemonic child processes.

官方解释的重点是daemon flag必须在start()前被设置。
还有一个坑人的地方,貌似父进程必须被SIGTERM信号杀死,或者自行自然退出,子进程才会退出,否则还是变成了孤儿进程。在stackoverflow上有人提到了这点:
https://stackoverflow.com/questions/25542110/kill-child-process-if-parent-is-killed-in-python

I want to mention, that if parent process was killed using SIGKILL (kill -9) then daemon processes won’t stop.

我想,这或许应该是由于-9(SIGKILL)会立刻中止父进程,于是父进程没有机会通知子进程,比如用atexit设置的进程退出时调用的hook函数就没机会执行了。

Rust中的&str和String有什么区别

&str和String让Rust初学者困惑不已。这里简单的介绍一个&str和String的区别。

String就像是Vec一样是动态分配在堆heap上的字符串类型,它指向一串UTF8编码的字节序。当需要拥有该字符串,要更改字符串的内容,要把字符串在线程之间传递,要在运行时动态创建字符串时使用此类型。

str也指向一串UTF8编码的字节序,但并不拥有指向的字符串,而仅仅只是一个“string slice”。slice的详细解释点链接。str绝大多数情况下以引用&str的形式出现。又被称为字符串字面值(string literal)。
和String指向的字符串肯定分配在堆上面不一样,str指向的字符串可能出现在各种地方:
1. 在程序的全局静态空间(.text段??)。一个string literal的类型和生命期是:&’static str。字符串的数据硬编码进生成的程序或库文件,但程序加运行时,字符串加载进内存。
2. 分配在堆上面 String有一个方法是as_str,它返回一个&str,指向String管理的堆上面的字符串。另外,String有实现Deref<Target=str>这样一个Trait,让所有接受&str作为参数的函数,都可以传String的引用。编译器会自动把&String转成&str。
3. 分配在栈上面 如下面的代码,变量stack_str的类型是&str,它指向栈上面的字符串。

use std::str;

let x: &[u8] = &[b'a', b'b', b'c'];
let stack_str: &str = str::from_utf8(x).unwrap();

如果以C++程序员的视角看String和&str。一个Rust的String类似std::string,完全拥有并管理(创建,销毁)需要的内存。而Rust的&str则类似char*(当然实际上要复杂很多),仅仅指向字符串,不负责管理对应的内存。

再看看String和&str的互转化:
&str转String可以用三种方法

    let a: String = "hello rust".into();
    let c: String = "hello rust".to_string();
    let b: String = String::from("hello rust");
    println!("{}", a);   // 都输出 hello rust
    println!("{}", b);
    println!("{}", c);

String转&str可以直接引用String类型的值或者调用String的as_str方法。

let a: String = String::from("hello rust");
let aaa: &str = &a;
let bbb: &str = a.as_str();

转载请注明出处链接 http://ykyi.net/2019/10/rust%E4%B8%AD%E7%9A%84str%E5%92%8Cstring%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB/

linux怎么把一个正在前台运行的进程用nohup放入后台运行

假设进程在控制台运行。
三个步骤:
1. ctrl + z 停止正在运行的进程
2. $ bg 命令把该进程放入后台
3. disown -h [job的序号] 后台的进程有类似%1, %2, %3的序号。

至此,一个正在控制台前台运行进程被放入后台,且忽略SIGHUP信号。这样,即使关闭了控制台,也不会杀死这个进程了。

python自制包并用pip免提交到pypi仅安装到本机

不得不说python的自制包的相关工具真是多且混乱,什么setuptools,什么distutils,什么wheel,什么egg!!怎么有这么多啊??

而且我的需求且且是创建一个自制包管理自己常用的代码,也必不想提交到PyPI,仅仅只需要安装到本机就行。

下面就是几个关键步骤。

  1. 文件目录布局
    ├── package1
    │ └──-├── init.py
    | |── mod1.py
    │ └── mod2.py
    ├── setup.py
    |── README.md
  2. 编写setup.py文件,类似如下:
from setuptools import setup, find_packages

setup(
    name="kamustools",
    version="1.0.1",
    author="Kamuszhou",
    author_email="zausiu@gmail.com",
    description="tools used by kamuszhou exclusively.",
    license="BSD",
    keywords="kamus",
    url="https://ykyi.net",
    packages=find_packages(),
    long_description="Long descrition is actually short...",
    classifiers=[
        "Development Status :: 3 - Alpha",
        "Topic :: Utilities",
        "License :: OSI Approved :: BSD License",
    ],
)
  1. python3 setup.py sdist bdist_wheel
  2. pip install ./dist/kamustools-1.0.1.tar.gz

就这四步,其他各种功能,以后要用了再慢慢看吧,实在太庞杂了。满足我自己需求的这四个简单步骤就记录在这里。

Wikipedia维基百科为什么不放广告,是因为版权原因吗?

答:不!维基百科不投放广告,不是因为版权原因。

https://en.wikipedia.org/wiki/Wikipedia:Funding_Wikipedia_through_advertisements 维基官方对投放广告的官方解释:17点理由不投放广告,没有一点提到版权。

通篇只有一处提到版权,是创始人Jimmy Wales的一段话:

I believe that if we looked at putting ads into the search results page (only), with the money earmarked for specific purposes (with strong community > input into what those would be, either liberation of copyrights ….
我相信,如果我们把广告放到搜索结果中,由此筹到的钱可用于特殊用途(社区的大量投入将是版权的解放)

有人说:

像这种权威的百科站点,例如莫种性病的解释词条边上出现一个莆田医院的广告,会怎样?难以想象!这种事也就百度做得出来吧。

上面链接中维基官方给出的不放广告的解释中就有几点的意思一致。
1. Ads cheapen the encyclopedia 会让维基掉价;
2. Threat to neutrality of content 影响内容中立;
3. Conflict of interest 和用户利益冲突.
4. Inappropriate ads 广告内容可能不恰当

上面的官方链接还有一条很强的证据能证明维基不放广告和版权无关。

The right to fork. It is inherent in the use of an open license that projects can fork. Thus far the largest fork away from Wikimedia was by a large part of the Spanish Wikipedia community who left because advertising was being considered. 

有权分叉(复制)。这个权利是维基的许可证所允许的。目前最大的分叉是西班牙语维基。他们离开了维基百科,因为他们想要投放广告。

维基百科的官方许可证页面 https://en.wikipedia.org/wiki/Wikipedia:Copyrights
维基百科的绝大多数内容的许可证是 Creative Commons Attribution-ShareAlike 3.0 和 the GNU Free Documentation License (GFDL)
这是两个非常宽松的许可证,意味着你可以随便复制并修改维基百科的内容(图片内容另有规定),商业化也是被允许的,当然包括投放广告。具体的条款,没时间细看了。

flask框架的回复中显示中文utf8字符,而不是unicode-escape字符串

今天在用flask框架写http服务的时候,发现调用flask的jsonify函数得到的http回复包中的字文是用unicode-escape string表示的。如下:

u5b57\u5173\u6ce8\u6211\u4eec\uff01

有些RESTful工具能够把unicode-escape字符串转成utf8,能够正常显示出中文。但很多编辑器和其它工具只能显示原本的unicode转义字符串,大大妨碍调试。解决方案是用flask的另一个函数:make_response解决这个问题。代码如下:

from flask import Flask, jsonify, make_response


def mk_utf8resp(js):
    '''
    传入一个字典,返回一个json格式的http回复。
    '''
    resp = make_response(json.dumps(js, ensure_ascii=False))
    resp.headers['Content-Type'] = 'application/json'
    return resp

把jsonify(js)换成mk_utf8resp(js),问题解决。