首页   注册   登录

mashiro233

V2EX 第 316689 号会员,加入于 2018-05-16 10:28:41 +08:00
mashiro233 最近回复了
@liuminghao233

你看他的说明,显然他说的"协程"和我们理解的协程不一样。

按照他的说明,只要做到方法级别的调用顺序控制就行了,连 yield 都没有(或许 return 就是 yield ?)。大概就是`异步代码有 event 来了直接调 callback `这一步就是“协程”调度器。

我们理解的协程就是参考 boost 的那个协程例子,能够在 foo 和 bar 函数里来回跳的那种切换,做到语句级的切换那种切换,让出后还有机会执行下面的语句。可能需要用到 setjmp 或者 ucontext 这种。
@wekw

破案了。
你这么一解释问题就解决了。
我们之间对协程这个定义理解的不一样。

我认为你所说的协程就是 goroutine,boost::coroutine 这种,很显然并不是。

举个 boost 的例子
```
void foo(){
std::cout << 1;
std::cout << 2;
std::cout << 3;
}

void bar(){
std::cout << a;
std::cout << b;
std::cout << c;
}

```
利用协程,我们可以让在单线程的情况下,通过 yield,能够让程序输出 a 1 b 2 c 3。这个是协程,能够在单线程的情况下切换 context。让它看起来像多线程。

你所说的“协程”,就是一种任务调度机制,你可以决定 foo 和 bar 的先后执行顺序输出可以是 1 2 3 a b c 或者 a b c 1 2 3。但是你无法做出 a 1 b 2 c 3 这种输出。

libuv 做的就是你说那些工作,给不同 socket 针对 read,write 等事件调用绑定的回调方法,并且还提供了 timer,线程安全的 sync 等常用工具。
@wekw

补充。在我看来,你是把协程和异步 api 混为一谈了。异步 api 能够使用协程的机制进行任务调度,但不是异步 api 必须使用协程。
@wekw

首先感谢你的回复,我确实不懂协程,我在工程中并没有使用过协程,不算 go 的话。
、我的之前回复语气可能有点冲,这里我道歉。我确实经验不足,毕竟我工作也没几年。既然 v2 是讨论技术的地方,那我继续向你请教。
另外你认错人了。

1.`第一操作系统会自动调控`
在我的理解里,单线程的调控就是把之前在 A 核的任务挪到 B 核。并没有解决一个核满了一个核空的问题。

2 `协程为什么性能高,就是因为没有上下文切换了`
我的理解里协程里依旧有上下文切换。因为需要保存当前程序运行在了哪一句。这个就是上下文,否则我无法理解协程是如何恢复到原来运行环境的,只不过开销比较小。可以话提供一个协程实现库让我学习一下。

3 ` 你管这个叫测试?好好学学英语,这是一个研究者在某一个会议上发表的论文`
可能是我的描述有误,这里的测试指的是论文里的测试数据。这篇链接是用来反驳你`是无法实现 10Gbps 和 40Gbps 的网卡速度的`这句话的。这篇文章依旧用的是 linux 网络栈,之后还有 bbr 的测试数据。根据里面的测试数据在 100Gbps 下达到了 79Gbps 的成绩。100Gbps 都能达到有个还可以的成绩,何来无法实现 40Gbps 一说?另外你给的文章里面也提到了 `使用多核编程技术替代多线程,将 OS 绑在指定核上运行` `而多核则是每个 CPU 核一个线程` `DPDK 程序启动后只能有一个主线程,然后创建一些子线程并绑定到指定 CPU 核心上运行`(这个是我贴的文档里描述)。这里面都提到了线程,只不过是用的线程池(不是无脑的开新线程还是将任务分配到线程池)。我还是没有看到里面提到了协程。

4. 同上

5.是我的描述有误,这里的 frame 指的是 ethernet frames。我没有说不是内核提供的,tun 本身就是依赖内核实现的。

6.我重新看了一遍,没有问题。goroutine 就是这么做的。

7. 我研究过 libuv 源码,也给 libuv 提交过 pr,我现在做项目基本上都是基于 libuv 做的。所以我才会说提供给 nodejs 异步的 api,文件操作用的是线程池实现的。另外,你说的文件指的是 File descriptor,这里是单纯的 file,也是描述不清的问题。

8.`你真的要好好学习一个。“协程的开销会比手动接管 callback 来的要高”,协程调度器的本质就是手动接管 callback,你还是不懂协程,唉。 `

我这里指的是,与其在 callback 处放协程调度器,不如自己实现一套更加轻量的调度器,参考 libuv,libuv 没有使用协程,老一点的 libevent 也没有使用协程。

总结下我的观点
1. 你的协程高性能这点,是得力于异步 api,和协程本身没有任何直接关系,不是协程造就了高并发高性能,协程是用来减轻异步 api 带来的开发难度。
2. 协程依旧需要切换上下文,因为需要恢复到 yield 的地方继续执行。
@wekw
不是

同样,这里不是你误导人的地方。这句话我还给你。

期待你的驳斥。
@c3824363
这个我可以补充一下。文件 io 这块,nodejs 的 Linux 异步文件 io 实现用的是线程池来做的。理由就是你说那样,不过 win 和 freebsd 我就不了解了,没开发经验。😢
@shijingshijing
arm64 是 普通参数放 r0-r7 浮点 /SIMD 是放 v0-v7 存不下的放 stack 上。
对于编译器来说,所有固定参数函数都可以当做可变参数函数来处理,函数的 arg 本质上是一个单向 list。所以 stdarg 里的那些函数你会发现完全可以用一个单向 list 来实现。
@wekw

看了你的回答,有些地方没看懂请教一下。

1.`实际上我们知道一个 CPU 核心微观上在任一个时刻都只运行一个指令`

我从搞网络编程开始,就没有碰到过单 cpu 的服务器。最差的也是 4 核 8 线程起步。我们都知道在做 epoll 模式的编程的时候,想充分发挥 cpu 性能,通常的做法要么用线程池,要么多开几个 process,在 bind 时候使用 SO_REUSEPORT 让内核做负载均衡。协程是单线程,这种情况下你就让其他几个 CPU 核看戏?

2. `Linux 下线程就是一堆共用内存的进程,需要上下文切换:将寄存器的内存暂存到内存。`
首先协程的切换就不要消耗资源了?协程也要保存上下文的啊,也要切换啊。这点我很赞同 @c3824363,epoll 等机制都给你提供了一个 user_ptr 这种保存上下文的东西了。为什么要再封一套协程降切换上下文低性能?

3. `而一次内存读写需要 70-90 ns,是无法实现 10Gbps 和 40Gbps 的网卡速度的。`
https://meetings.internet2.edu/media/medialibrary/2016/10/24/20160927-tierney-improving-performance-40G-100G-data-transfer-nodes.pdf

这里测试了 100G 网络的测试,系统是 Centos7 机器是 Dell z9100 跑 linux 系统。测试出来结果是 100Gbps 下 70G。

4. `于是 DPDK 诞生了,在用户态使用单线程的方式来阻止上下文切换`
DPDK 和协程又有什么关系? DPDK 对任务的做法是 poll+线程池,文档里写的很清楚。
https://dpdk.org/doc/guides/prog_guide/env_abstraction_layer.html 3.1.1 节

The core initialization and launch is done in rte_eal_init() (see the API documentation). It consist of calls to the pthread library (more specifically, pthread_self(), pthread_create(), and pthread_setaffinity_np()).

5.`自己构造协程调度器,等于自己又在图灵机模型下实现了一遍多任务调度,成功实现了高性能 SDN:软件定义网络。`
SDN 和协程又有什么关系吗?如果说是在用户层实现 tcp/ip 栈,我自己也用 linux 的 tun 网卡直接收发 frame 提供给 tcp/ip 栈,并且能够成功的接入互联网。没有任何地方用到协程。

6. `而协程这种软件实时调度的纯异步是非常难以理解的,这才是协程最难的地方。`
纯异步和协程又有什么关系?异步是因为你调用了异步 API,那是异步 API 的功劳。协程让出后,除非被 resume 是不会用工作的,在网络编程那块,协程的调度器帮你解决了异步回调要解决的问题,你只需要按照多线程的思想去写程序就行了。举个例子

```
something = read()//这里是阻塞 api yield 依旧是要等有数据可读之后才会让出,该阻塞的还是阻塞。一直没有数据就卡死在这了。
yield()
do_something
```

```
try_read_async(call scheduler resume) //因为使用了异步 api,所以在调用时候不会阻塞,会立即执行。当有数据的时候,回调函数会通知调度器,告诉你可以从 buffer 里取数据了恢复协程运行。
yield()
something = read_from_buffer()
do_something()
```

第二种异步方法是典型 epoll 应用开发模式,刚好 @liuminghao233 提到了 boost,我记得 boost 的 asio 的 api 就是这么设计的。稍微智能一些的协程比如说 goroutine,你都不需要手动 yiled,都给你封装好了。但是为什么有的情况下明明有了 goroutine 还是要使用 epoll ?就是因为即便 goroutine 是协程依然有开销,虽然没有线程大但是并不是接近 0 ( https://medium.freecodecamp.org/million-websockets-and-go-cc58418460bb )。协程依旧有开销,100k 个协程就要保留 100k 个上下文,还要处理调度器。

所以,我不明白 `协程产生的目的是提高性能` 这个结论是怎么得出来的。协程的存在最早就是用于任务调度的,因为需要手动 yield 后来被时间片替代了(参考我第一个例子,假设一个任务永远没有让出那么就卡在那了)。后来由于异步 api 的出现,使得协程能够实现 “看起来像多线程”的编程方式,使得需要写回调处理的场景可以用看起来像多线程的方式去解决(继续看向 goroutine )。但是比起直接接管回调的方式,依然还是有开销,c1000k 协程的开销依然很大。

所以你所提到的协程目的是为了提高性能的观点我无法认同,所谓的提高性能那是因为用了异步 api,是内核的功劳。和协程没有任何关系。并且在极限环境下,协程的开销会比手动接管 callback 来的要高。
c/c++由于其特殊性能够在非常多的场合上派上用场,所以不同领域的知识累积也不一样。

比如如果是服务器开发,可能会侧重偏向 tcp/ip 实现,网络模型这块。

如果是搞 linux 内核的,可能会偏指令集,用户层的 syscall 以及在内核里是如何实现等等。

如果是搞渲染器的,对 dx 和 opengl 这套东西肯定得熟了,shared 要怎么写,对指令集也得了解。必要时候还得手写汇编,毕竟渲染器这块对于效率有极高的要求。现在大多都是 arm 和 x86,资料多。上个世代各种偏门的 soc(说的就是 x360 和 ps3)可是把一堆写渲染器折腾的死去活来。

至于语言本身,我个人是不太倾向一开始于过度深入研究,应该关注于如何使用和用好轮子。随着项目的积累你会慢慢的去了解原理。我今年过年放假的时候研究了一个礼拜的元编程,到现在项目里也没啥地方需要用到。再比如楼上提到的 main 函数 printf 这些,如果你知道 crt0/1.o 是个什么东西,写过 ld script,移植过 newlib,闲的时候看过 musl libc 源码,回答这些个问题不是什么难事。如果对这块有兴趣,看优秀库的源码是个很好的提升手段,c++这块 boost 是个非常好的库,代码可读性很高。c 的话推荐 musl libc,也是可读性非常高的 c 标准库实现。同时有精力也可以写个 c 编译器玩玩。
82 天前
回复了 sw0rd3n 创建的主题 程序员 有没有推荐的反向调试工具?
@sw0rd3n
哦,那是我看错了。反向调试应该和平台相关的,这个真不太清楚……
关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   鸣谢   ·   实用小工具   ·   850 人在线   最高记录 3762   ·  
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.1 · 7ms · UTC 23:12 · PVG 07:12 · LAX 16:12 · JFK 19:12
♥ Do have faith in what you're doing.
沪ICP备16043287号-1