请教 V 友们,一或两年经验的 C/C++程序员,应该具有什么样的能力才算是合格或者优秀的

2018-05-29 16:36:55 +08:00
 Applenice

想请教 V 友们一下,一或两年经验的 C/C++程序员,应该具有什么样的能力才算是合格或者优秀的,看招聘信息的要求总感觉有些乱,如果你们去面试这样的同学都会考察哪些方面呢?先谢谢大家

11470 次点击
所在节点    程序员
120 条回复
c3824363
2018-05-30 13:53:46 +08:00
@Applenice 那个 list.h 大致看一眼就全明白了,直接用是因为这个快成圈子里的标准了。
rbtree 那个就复杂很多了, 没细看过它的代码,感觉自己没有那个水平也没那个必要去看,但是一直在用。
关键的是那个 container_of 的思想让我在用 C 编程上放飞了一下。
zhicheng
2018-05-30 13:53:51 +08:00
写了这么多年 C,我可以写编译器,可以写 VM,可以写 OS,但你让我半个小时写个链表,对不起写不出来。另外如果谁在面试的时候让我写个快排,那我会在最后提问的时候让他证明一下快排为什么快。很多时候写代码和考代码是两个维度的事。
c3824363
2018-05-30 13:55:49 +08:00
@c3824363 linux 内核代码的模式可以在不用 C++的情况下也能写出清晰明了好维护易扩充易复用的代码,这就是我不用 C++的理由。
c3824363
2018-05-30 13:57:00 +08:00
@zhicheng 能写 VM OS 编译器肯定能十分钟写出链表来
zhicheng
2018-05-30 13:59:04 +08:00
@c3824363 对不起,就是不能。
shijingshijing
2018-05-30 14:07:40 +08:00
@zhicheng 链表还好吧,只要你不扩展开来,分分钟写一个能用的还是可以的。比较烦的是要考虑很多 corner case,比如 ListNode->data 不是简单的 int,float 值类型而是需要 malloc 出来的时候,还有就是插入 /删除 ListNode 到 List 中间和头这两种不同情况时要分别处理,还要尽可能写的优雅不能一堆 if 判定。同理快排也是这样,每次二分得到的两个数组元素个数是奇数还是偶数,左边界和右边界还有 pivot 重叠还是不重叠。

确实容易把纠结逼和强迫症弄疯,不过你写过 os 和 vm,那么多的 exception 都处理过,这些不都是小 case 么?
zhicheng
2018-05-30 14:17:14 +08:00
@shijingshijing 写 OS 和写 VM 本就和写链表没多大关系。我花在调试 Lemon 语言 GC 的链表都不止半个小时了。

考链表也好,考快排也好,考个思路就可以了,真要让半个小时之内写出个无错可执行的代码,基本上就是面试之前把实现记住了或者面试者是做 ACM 的,天天和这个打交道。
chenyu0532
2018-05-30 14:51:39 +08:00
刚开始用 cocos2d-x C++写手游。。天天被技术总监喷,数据结构不好,懂不懂 MVC 结构,怎么能这么传值!没办法。。加班。。写了两年终于会了一些。。也开始看源码了。。结果再其他的公司里,,压根不用 c++啊。。都 TM lua 和 ts 啊。。。
liuminghao233
2018-05-30 15:16:49 +08:00
@wekw
我唯一用到 coroutine 的地方就是 boost asio
把一大堆异步 callback 写成同步形式
我感觉两种方法其实差不多

为什么说协程编程异常复杂?
有可能是增加 debug 难度?
wekw
2018-05-30 15:40:28 +08:00
统一回复:协程产生的目的是提高性能。

另外大家先站在 提供协程功能 的角度来看待协程:

Linux 下线程就是一堆共用内存的进程,需要上下文切换:将寄存器的内存暂存到内存。

当初为了解决 C10K 问题使用了 epoll,epoll 本质就是操作系统把自己的事件驱动功能下放到应用层的产物,自 kernel 2.5.44 引入。换个角度大家想想,操作系统本身就是事件驱动的。

事件驱动能力来自于 CPU 的 exception 机制,就是中断,正式中断这个 CPU 和 kernel 合作完成的功能提供的“多用户”能力,这里的多用户可以理解成三层:1. 系统进程和用户进程宏观上同时运行 2. 多个终端登录一台机器,宏观上同时使用这台机器 3. 多个进程宏观上同时运行。实际上我们知道一个 CPU 核心微观上在任一个时刻都只运行一个指令,本质还是图灵机,只不过简单的纸带换成了复杂的寄存器、一二三级缓存、内存的集合。

但是进程模型无法解决更新的需求。

1. Linux 标准线程占用的内存还是太大,理论上实现 C1000K 需要的内存过大,于是就需要一种能够占用更小内存的多任务调度方案,于是协程诞生了。
2. 当今顶级 x86 CPU 的性能已经爆棚,一块一万多的至强计算能力可以秒杀售价一百万的 40G 专业路由设备,为什么我们不能用至强来做呢,多省钱呀(实际上市面上有不少 DIY 的 x86 软路由,跑个 500Mbps 不成问题)。但是很遗憾,Linux kernel 的网络栈还是要依靠中断来调度,这就导致需要频繁地进行上下文切换:读写寄存器的内容到内存。而一次内存读写需要 70-90 ns,是无法实现 10Gbps 和 40Gbps 的网卡速度的,实测能跑到 2Gbps 就算不错了。于是 DPAK 诞生了:在用户态使用单线程的方式来阻止上下文切换,在驱动层面抛弃 kernel 的网络栈来自己实现,在单线程内部,自己构造协程调度器,等于自己又在图灵机模型下实现了一遍多任务调度,成功实现了高性能 SDN:软件定义网络。目前阿里云和腾讯云的自研高性能用户态 SDN 已经上线,已经创造很大的价值了。

自己手动实现协程是很麻烦的。人脑最容易理解的是同步,其次是上下文切换,而协程这种软件实时调度的纯异步是非常难以理解的,这才是协程最难的地方。
wekw
2018-05-30 15:44:55 +08:00
订正:
1. 需要上下文切换:将寄存器的内容暂存到内存。
2. 于是 DPDK 诞生了
Applenice
2018-05-30 16:15:05 +08:00
@easylee 已经加上了,真是感谢这些大佬们
gnaggnoyil
2018-05-30 17:37:32 +08:00
我就比较佩服你们能为一个非阻塞异步 IO 水上这么多楼…… call/cc 是很难理解的东西吗?特别是像 LS 某人说的那样,现在 PC 和服务器的主流 OS 几乎都是事件驱动的,在这个基础上阻塞任务才是异类,而 thread 和 process 本身就是对这个异类的一个残废抽象而已……

@c3824363 和你讲个笑话:C 的代码清晰明了好维护易扩充易复用.OpenSSL 负责 TLS handshake 的函数区区一个带状态的状态机循环就足足写了 100 多行,而且其中功能性的子任务还是通过函数指针跳来跳去完成的,仅仅就是因为需要一个多态……
logbang
2018-05-30 18:26:59 +08:00
向楼上各位大佬学习
mashiro233
2018-05-30 18:45:39 +08:00
@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 来的要高。
mashiro233
2018-05-30 18:54:49 +08:00
@shijingshijing
arm64 是 普通参数放 r0-r7 浮点 /SIMD 是放 v0-v7 存不下的放 stack 上。
对于编译器来说,所有固定参数函数都可以当做可变参数函数来处理,函数的 arg 本质上是一个单向 list。所以 stdarg 里的那些函数你会发现完全可以用一个单向 list 来实现。
season4675
2018-05-30 20:47:00 +08:00
cpp 基本特性得熟悉,c++11 特性必须会,比如《 effective modern c++》《 more effective c++》怎么说一得一刷吧?就这些……顺便说下,简历给我看看??杭州有兴趣嘛
c3824363
2018-05-30 23:46:27 +08:00
@gnaggnoyil 能用 container_of offset_of 实现一堆代码复用我就比较满足了啊
@mashiro233 协程切换是用户态的,速度快很多, 但我还是没用这个东西,如大家所说反正都是事件驱动的。
但是有个特例是 linux 文件系统的 io,这个还是阻塞的,没有和 epoll 配合。 但是 freebsd 的 kqueue windows 的 iocp 都能配合文件系统 io 使用。
mashiro233
2018-05-30 23:53:39 +08:00
@c3824363
这个我可以补充一下。文件 io 这块,nodejs 的 Linux 异步文件 io 实现用的是线程池来做的。理由就是你说那样,不过 win 和 freebsd 我就不了解了,没开发经验。😢
YouXia
2018-05-31 00:10:30 +08:00
Mark

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://tanronggui.xyz/t/458700

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX