想请教 V 友们一下,一或两年经验的 C/C++程序员,应该具有什么样的能力才算是合格或者优秀的,看招聘信息的要求总感觉有些乱,如果你们去面试这样的同学都会考察哪些方面呢?先谢谢大家
1
pkookp8 2018-05-29 16:47:23 +08:00 via Android 4
为啥很多公司招聘喜欢把 c++和 c 放一起,明明差好多
说一个 c 的 没经验能半小时手写一个链表 一年能写代码,15 分钟手写一个双向链表 /排序等等 二年能看反汇编调试出错位置,编译失败分分钟修改好,编译步骤了然于心。 三年写代码能力精进,讲解代码或项目井井有条,有良好的各种意识例如编程规范,文档。特殊情况下能够修改二进制文件来达到让异常程序正常运行的目的 |
2
natscat 2018-05-29 16:48:13 +08:00 1
我是面试官的话可能会问下 epoll 然后由这个展开问下线程进程 还有就是些基本的数据结构
|
5
pkookp8 2018-05-29 16:57:45 +08:00 via Android
@Applenice 这是我这边的情况,因为人招不到,会写链表的基本都过了,还招不到。。。
我不了解其他的牛人。像 2 楼说的也会根据某个项目,某个特性或系统实现往深层问。当初招我的人就是拿一个 printf 开始问,入参是什么,怎么实现的,为什么可以变参,参数怎么获得等等,看看我对这个了解多深 |
8
GeruzoniAnsasu 2018-05-29 18:40:11 +08:00
@Chyroc 我理解是非常小的 patch,比如改改值,改改条件跳转,这种级别的而已
|
9
pkookp8 2018-05-29 18:48:47 +08:00 via Android 1
@Chyroc 说起原理,其实就是一个常数编译在二进制文件中,你修改这个常数就行了。因为很多时候库是不开源的,没办法从源码编译,又很难推动二进制文件的提供方。所以我也说是特殊情况下没办法的办法
我组长手撸二进制的时候我都惊呆了,第一次发现还能这么做 当然,怎么找到这个常数是关键 有两种,一种通过编译器反汇编,确定某个参数是有问题的,修改再重新编译,一种改二进制。第二种比较难,除非是特殊值,比如 0x12345678 这种有规律的,第一种要能分析汇编。单纯搞 c 的应该会接触到汇编,但又不是必须的,所以即使只是会看,也可以多一种解决问题的思路 |
10
GeruzoniAnsasu 2018-05-29 18:49:14 +08:00 1
前面有人说了 C,我来说一个我同学,搞 C++的,做主(switch)机游戏(炒冷饭)
第一年的时候我们讨论过的问题有,static object 的初始化,allocator,unique_ptr 所有权,CRTP,这种东西 第二年(今年)我已经跟不上他了:two phase name lookup 和类型推导你了解得多吗 |
11
pkookp8 2018-05-29 18:49:50 +08:00 via Android
@GeruzoniAnsasu 是,就改一个值,一条指令。而且很难做到增删,只能做到改
|
12
GeruzoniAnsasu 2018-05-29 18:53:14 +08:00 1
@pkookp8 哈哈哈哈,其实吧,这种东西对于学安全的特别是二进制相关方面的同学来说属于入门基本功,增删也是能做到的,但很蛋疼不会这么弄就是了
|
13
changnet 2018-05-29 18:57:26 +08:00 via Android
前面两位太偏底层了吧。做业务的谁整天搞那些东西,学生吗
|
14
across 2018-05-29 19:01:31 +08:00 via iPhone 1
上面说的偏 c。一两年经验面 c++一般都是 stl 和 oo 的题
|
15
shijingshijing 2018-05-29 19:08:21 +08:00
|
16
maxco292 2018-05-29 19:12:18 +08:00 2
@GeruzoniAnsasu Two phase name lookup 模板名称查找,算是 C++阴暗角落之一了( LLVM 团队博客实现这个功能单独发了一篇博客 http://blog.llvm.org/2009/12/dreaded-two-phase-name-lookup.html ),了解模板 /类型推导这东西,推荐( https://github.com/wuye9036/CppTemplateTutorial/blob/master/ReadMe.md )
|
17
dosmlp 2018-05-29 19:19:51 +08:00 4
这又不是学校你几年级应该学会哪些东西,应该能考到多少分数,60 分才及格。
实际上,工作中一般都是根据公司业务特点来招人的,你说你汇编玩的溜,人家公司又不搞二进制安全, 当然你会的多总是好的, 加入我来面试,我比较注重基础,包括操作系统和语言的,如果一些基础概念都搞不清我觉得你这人太虚, 别笑,我遇到过 5 年 经验的还不知道线程是啥 |
18
pkookp8 2018-05-29 19:30:55 +08:00 via Android
@shijingshijing 差别好大的意思。因为性价比不高,所以没人,因为没人,所以招人要求低。主要是工资低,加班多,最终就是能写个简单的链表就行就
|
19
pkookp8 2018-05-29 19:32:32 +08:00 via Android
@GeruzoniAnsasu 因为汇编级都是偏移,做增删要改非常多的东西,不过貌似听说有专业的编辑器能做到,我接触不多,还是个渣
|
20
shijingshijing 2018-05-29 19:34:56 +08:00
@pkookp8 那你觉得你招人碰到的到底是写 C 厉害的人多还是写 C++厉害的人多? 你是说写 C++的对底层了解的不够深入么?能力偏向工程所需的设计模式和 OOP ?
PS:专门搞汇编破解什么的,有一个神器叫 IDA Pro |
21
shijingshijing 2018-05-29 19:39:29 +08:00
@pkookp8 你那个 printf 的例子,要是问我我也是一脸蒙蔽的。如果不 google,我一般会答是参数列表可变是因为传入的是参数数组和参数个数,而函数调用时这些参数又是通过栈传递的,所以可以做到可变长。再不行我就给他举例 main 函数的 argc 和 argv,不知道能不能忽悠过。。。
|
22
GeruzoniAnsasu 2018-05-29 19:40:14 +08:00
@maxco292 我知道,我意思是,这种东西我已经讨论不来了,两年的优秀 c++师傅,大概应该有这种程度 的意思
|
23
shijingshijing 2018-05-29 19:43:15 +08:00
@GeruzoniAnsasu 我觉得,两年经验,能把 STL 和设计模式弄清楚就很不错了。。。
|
24
pkookp8 2018-05-29 19:46:01 +08:00 via Android
@shijingshijing 经验比我高的轮不到我来,接触不到很多厉害的真人,只能通过网络学习这样子。而且我也说了我搞的是 c,没接触过 c++,只了解语法(现在也忘差不多了)。见过的几个真人肯定不懂底层,甚至一个写上层业务的 c 工程师总是问我编译问题,接触不多,不好评论。我也就一破小公司,经验比我高的一般职位比我高,要么不写代码只 bb,要么跑路去大厂,我只能
|
26
pkookp8 2018-05-29 20:09:26 +08:00 via Android 1
@shijingshijing main 函数是固定参数,int 和二级指针。printf 是可变参数,个数不定,类型不定。c 不像 c++,没有重载这一特性,只能通过入栈和出栈来实现,即使能重载也做不到类型不定。我记不清当时怎么忽悠的了,反正肯定是错的,领导可能因为招不到人就要我了😰
|
27
Applenice OP @GeruzoniAnsasu 羡慕能一直坚持学习的人
|
28
Applenice OP @shijingshijing 手写红黑树.....给大佬跪了
|
31
liuminghao233 2018-05-29 21:51:53 +08:00 via iPhone 1
应该要对 c++各种骚操作了如指掌(
|
32
lovefantasy 2018-05-29 21:54:57 +08:00 via Android 1
半小时手写双向链表也给跪
|
33
fanyangyang 2018-05-29 22:03:18 +08:00 2
你就是那个不可替代。公司有个同事写 C 的,自己扛着三四个项目,最近由于身体原因想休息,换个轻松点的工作,老板就给他安排了一个任务:每天读《庄子》半小时。
嗯,我觉得你的身体也累垮的时候就差不多优秀啦 |
34
Cambrian07 2018-05-29 22:12:25 +08:00 via Android 1
printf 那个我之前研究过,而且还自己手写实现过,然而我现在已经完全记不住了,就记得实现的话是通过...来告诉编译器参数可变。
|
35
Applenice OP @liuminghao233 emmm,麻烦问下,这个骚操作大概都有哪些呢
|
36
Applenice OP @lovefantasy 半小时能来手写双向的创建,插入,删除....也是很厉害了
|
37
Applenice OP @fanyangyang 自己扛三四个项目....给大佬跪了 Orz,老板安排读《庄子》这个感觉也是很棒了
|
38
Applenice OP @Cambrian07 说实话,这个我还真的没看过,我要去研究研究 0.0
|
39
iceheart 2018-05-29 23:05:25 +08:00 via Android 1
手写汇编调用几个 api 就知道 printf 的变长参数是咋回事儿了。这就是考对 ABI 的熟悉程度
|
41
xiadong1994 2018-05-29 23:28:02 +08:00 2
@shijingshijing 那你这个问题就答错了,printf 和 main 不一样,main 不是可变参数的,他只有两个参数。x86 结构 printf 是在 call 指令之前把参数 push 到 stack 上( x86_64 先放到寄存器里),然后在 printf 里面用格式字符串一个个取出来。这些在 C 里面是用 macro va_list, va_start, va_end 等等来实现,不同的 arch 这些 macro 的实现都不一样。
|
43
Applenice OP @xiadong1994 真心的佩服大佬....我真的是太渣了
|
44
shijingshijing 2018-05-30 00:23:48 +08:00
@xiadong1994 惭愧。。。 大佬我要关注你。。。
|
45
msg7086 2018-05-30 02:56:03 +08:00 1
接 #41 @xiadong1994
所以说学 C 系的人,学好汇编很重要…… 不用到手撸汇编的程度,但是这些机制还是要了解到位的。 @Applenice 之前还面过一个想来做 C 开发的,号称很多年 C 语言讲师经验的中年大叔。 我随便扔了一道简单的白板应用题,写不出。 我们老大随便问了个内存对齐的问题,答不出。 再见…… |
46
PanPancf 2018-05-30 09:17:19 +08:00 1
@msg7086 $45 printf 变长参数这个问题,只要去了解一下调用栈的原理就可以知道了。。当然汇编还是能看懂比较好
|
47
mashiro233 2018-05-30 09:39:17 +08:00 via Android 3
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 编译器玩玩。 |
49
jeffson 2018-05-30 09:46:36 +08:00
觉得自己好菜怎么办
|
50
Applenice OP @mashiro233 谢谢谢谢,十分感谢了,您和楼上大佬们的回复真的让我感觉到自己差好多,要更努力搬砖学习了,感谢
|
52
feverzsj 2018-05-30 09:56:34 +08:00
2 年连 c++都还没入门
|
53
liuminghao233 2018-05-30 10:09:27 +08:00 via iPhone 1
比如说 sfinae (
另外写双向链表都要跪的怕是完全没学过数据结构 还有学 c++必须看 深度探索 C++对象模型 看完很多语言上的细节都清楚了 当然 c++11 之后又不同了 |
54
wekw 2018-05-30 10:20:03 +08:00 1
能手写一个协程调度器我觉得就步入高手行列了,因为你要懂得 进程、线程 的概念和 Linux 的设计模型。
|
55
wekw 2018-05-30 10:20:49 +08:00
写不出来的话,能一句话概括我也认。绝大多数人都不知道高性能服务器开发领域的协程概念。
|
56
c3824363 2018-05-30 10:27:11 +08:00
@wekw 协程是为了方便人类的思维习惯而搞出来的东西吧, 每个连接自己携带一堆上下文数据不是一样的么。 就是用起来不像阻塞编程那么直观,但习惯了还是一样的吧。
|
58
cgsv 2018-05-30 10:31:37 +08:00 via iPhone
哈哈,偏个题,这样 2 年工作经验比较游戏的能给多少钱啊?
|
60
easylee 2018-05-30 10:33:31 +08:00 via Android
楼主收到这么多回复,选几个稳的有用的,append 楼号吧。
|
61
c3824363 2018-05-30 10:53:57 +08:00
@wekw 协程的本质不就是用户态程序自己切换"栈"么, 目的不是为了简化同时处理多连接的编程么,弄的异常复杂了还有什么意思呢。
|
62
coderluan 2018-05-30 11:04:14 +08:00 1
干了两年 C/C++,还管自己叫 C/C++程序员,这个基本就是怎么都不合格的。
C/C++本身基本什么都做不了,但是配合别的知识能做的又太多,所以有两年工作经验,还管自己叫 C/C++工程师,就是没有自身定位和方向的表现。 你看招聘乱也是这个原因,因为他们招也不是 C/C++工程师,而是 XX 工程师( C++方向),只会 C/C++肯定是不行的。所以楼主想问这个问题,先明确自己想做什么方向。 |
63
current 2018-05-30 11:07:59 +08:00
|
64
macha 2018-05-30 11:26:38 +08:00 1
客户端程序员路过,虽然也是 C++但是基本都拿来当 JAVA 写。
|
66
c3824363 2018-05-30 11:39:01 +08:00 1
@current 我的意思是说如果不用协程,直接根据类似 epoll 的事件驱动, 然后写对应的事件处理函数,那么这样写起来就必然要用一个结构体来包含一个或多个 socket 和相关的上下文数据,这样就不是很直观了。为了简化和抽象出一个框架,还会用回调的方式,这就让初学者看起来就更不直观了。
但是如果用阻塞的简单方式编程呢, 那么这些结构体里面的上下文数据就可以展开成一些普通的变量了,然后配合阻塞的方式,能一眼从头看到尾,初学者也能一眼看明白了。 但这样只能用多进程多线程处理多连接,用于切换的损失比较大。 协程就是用用户态自己切换上下数据的栈,来同时实现高性能和编程的简单直观。 正常情况下我不用协程,直接用前面说到的第一种方式,例外是用 lua 的场景。 没有笔误,就是没有表达清楚。 |
67
c3824363 2018-05-30 11:41:09 +08:00 1
@c3824363 本身 epoll IOCP kqueue 也提供了携带一个指针的方式来让你关联一个结构体来实现我说的第一种方式。
|
68
current 2018-05-30 12:11:25 +08:00 1
@c3824363 我觉得你这个说法是没问题的,协程的存在提供了使用类似与你描述的( 2 )的形式的代码提供( 1 )的功能的能力,上下文需要分成两个方面来理解,一方面是应用层的上下文,也就是进程 /线程 /协程栈上的数据,一方面是运行时环境,也就是寄存器,TLB,Cache 等
上面一方面的上下文解决代码编写的问题,下面一部分的上下文解决切换开销的问题。当然还有一些细节是要分开考虑的。。比如是否要提供私有栈,是否要提供调度的优先级,yield 的语义等等。。 |
69
ioth 2018-05-30 12:13:01 +08:00
c++和 c 完全不是一回事。
|
70
pkookp8 2018-05-30 12:20:39 +08:00 via Android
@pere 实习 3k,应届 7k,试用 80%。
链表很简单,结构体会吧。其中一个用来存储下一个节点的地址。这样知道一个就能找到下一个 进阶一点,实现一个双向的,或循环的 以这个为原型,结构体增加一个成员用来存储数据 于是可以写出增删改查四个功能,基本就初级毕业了。 再进阶,排序链表,就 OK 了,逆转有兴趣也可以了解一下,不了解也没什么大碍 再进阶,哈西表一个简单的模型就是链表里存着链表,或者想象二维数组也行,实现一下也不难 再进阶,那就是树了。平衡树什么的 日常不是算法相关,这些就够用了,而且对指针什么的使用和理解也会有一个深入 然后就是多写,多看。刚学的时候写一两个小时也正常,熟练了之后,了解原理,熟练使用这门语言,十分钟也是可以的。 c 不像一些更高级的语言,这些都是需要自己造轮子的,需要使用者非常关心的 当然 linux 内核也提供了一个 list.h 可以用,非常方便 |
72
Applenice OP |
73
shijingshijing 2018-05-30 12:52:15 +08:00 via iPhone
@pkookp8 难怪你们招不到人的。。。
话说现在公司招 c 和 c++的,都愿意花大价钱招个很厉害的也不愿意花少点钱多招几个菜鸡,因为一般涉及到要用 c 和 c++的场景,都是没有更好的办法而不得不用(连堆硬件都无法解决的那种),这样人多没啥用,还是要找个厉害的给多点钱然后死命的压榨。 只能说你们公司比较清奇。。 |
74
shijingshijing 2018-05-30 13:04:15 +08:00 via iPhone
@xiadong1994 对了,你说 x86_64 的这些参数放在寄存器里面,这个我比较好奇,我只做过 32 位的嵌入式,没看过 64 位的 ISA 手册,不管是 intel 的还是 ARM 的,都没看过,想问一下是所有的 64 位都这样处理的还是看具体的 case ?如果跟一长串参数的话比较好奇 register file 足够大么?能详细说说么?
|
75
pkookp8 2018-05-30 13:06:10 +08:00 via Android
@shijingshijing 小厂只能招我这种菜鸡凑凑人数混混日子堆堆代码了,每天加班,不知未来在哪
|
76
xiadong1994 2018-05-30 13:13:36 +08:00 via iPhone 2
@shijingshijing 我也只知道 x86_64 的,Windows 和 Linux 的 call convention 还不一样。不过都是把前几个参数放到寄存器里( win4 个,Linux 6 个),多出来的放到 stack 上。
https://en.m.wikipedia.org/wiki/X86_calling_conventions |
77
pkookp8 2018-05-30 13:17:30 +08:00 via Android 2
@shijingshijing 64 位以及 intel 我不是很懂,我只了解 32 位 arm:参数寄存器通常是 r1-4,所以函数的入参建议 4 个以内也是这个原因。大于 4 个不是通过寄存器传参,而是压栈,然后用到的时候出栈,因此代码效率降低(非常有限)。现象可以通过分析汇编语言看出,理论解释需要看 arm 手册
一长串参数会一直入栈,直到栈爆了。栈大小有个默认值,ulimit -s |
78
shijingshijing 2018-05-30 13:28:15 +08:00 via iPhone
@xiadong1994
@pkookp8 大致了解了,这种底层细节的一般我们都是丢给供应商去处理的,我们自己了解不多,惭愧啊,一直撸 application 的真不好意思说自己是写 c 的,以后有时间还是要多看看。 |
81
c3824363 2018-05-30 13:53:46 +08:00
@Applenice 那个 list.h 大致看一眼就全明白了,直接用是因为这个快成圈子里的标准了。
rbtree 那个就复杂很多了, 没细看过它的代码,感觉自己没有那个水平也没那个必要去看,但是一直在用。 关键的是那个 container_of 的思想让我在用 C 编程上放飞了一下。 |
82
zhicheng 2018-05-30 13:53:51 +08:00 1
写了这么多年 C,我可以写编译器,可以写 VM,可以写 OS,但你让我半个小时写个链表,对不起写不出来。另外如果谁在面试的时候让我写个快排,那我会在最后提问的时候让他证明一下快排为什么快。很多时候写代码和考代码是两个维度的事。
|
83
c3824363 2018-05-30 13:55:49 +08:00
@c3824363 linux 内核代码的模式可以在不用 C++的情况下也能写出清晰明了好维护易扩充易复用的代码,这就是我不用 C++的理由。
|
86
shijingshijing 2018-05-30 14:07:40 +08:00 via iPhone
@zhicheng 链表还好吧,只要你不扩展开来,分分钟写一个能用的还是可以的。比较烦的是要考虑很多 corner case,比如 ListNode->data 不是简单的 int,float 值类型而是需要 malloc 出来的时候,还有就是插入 /删除 ListNode 到 List 中间和头这两种不同情况时要分别处理,还要尽可能写的优雅不能一堆 if 判定。同理快排也是这样,每次二分得到的两个数组元素个数是奇数还是偶数,左边界和右边界还有 pivot 重叠还是不重叠。
确实容易把纠结逼和强迫症弄疯,不过你写过 os 和 vm,那么多的 exception 都处理过,这些不都是小 case 么? |
87
zhicheng 2018-05-30 14:17:14 +08:00 2
@shijingshijing 写 OS 和写 VM 本就和写链表没多大关系。我花在调试 Lemon 语言 GC 的链表都不止半个小时了。
考链表也好,考快排也好,考个思路就可以了,真要让半个小时之内写出个无错可执行的代码,基本上就是面试之前把实现记住了或者面试者是做 ACM 的,天天和这个打交道。 |
88
chenyu0532 2018-05-30 14:51:39 +08:00 1
刚开始用 cocos2d-x C++写手游。。天天被技术总监喷,数据结构不好,懂不懂 MVC 结构,怎么能这么传值!没办法。。加班。。写了两年终于会了一些。。也开始看源码了。。结果再其他的公司里,,压根不用 c++啊。。都 TM lua 和 ts 啊。。。
|
89
liuminghao233 2018-05-30 15:16:49 +08:00 via iPhone
@wekw
我唯一用到 coroutine 的地方就是 boost asio 把一大堆异步 callback 写成同步形式 我感觉两种方法其实差不多 为什么说协程编程异常复杂? 有可能是增加 debug 难度? |
90
wekw 2018-05-30 15:40:28 +08:00 4
统一回复:协程产生的目的是提高性能。
另外大家先站在 提供协程功能 的角度来看待协程: 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 已经上线,已经创造很大的价值了。 自己手动实现协程是很麻烦的。人脑最容易理解的是同步,其次是上下文切换,而协程这种软件实时调度的纯异步是非常难以理解的,这才是协程最难的地方。 |
91
wekw 2018-05-30 15:44:55 +08:00 1
订正:
1. 需要上下文切换:将寄存器的内容暂存到内存。 2. 于是 DPDK 诞生了 |
93
gnaggnoyil 2018-05-30 17:37:32 +08:00 1
我就比较佩服你们能为一个非阻塞异步 IO 水上这么多楼…… call/cc 是很难理解的东西吗?特别是像 LS 某人说的那样,现在 PC 和服务器的主流 OS 几乎都是事件驱动的,在这个基础上阻塞任务才是异类,而 thread 和 process 本身就是对这个异类的一个残废抽象而已……
@c3824363 和你讲个笑话:C 的代码清晰明了好维护易扩充易复用.OpenSSL 负责 TLS handshake 的函数区区一个带状态的状态机循环就足足写了 100 多行,而且其中功能性的子任务还是通过函数指针跳来跳去完成的,仅仅就是因为需要一个多态…… |
94
logbang 2018-05-30 18:26:59 +08:00 via Android
向楼上各位大佬学习
|
95
mashiro233 2018-05-30 18:45:39 +08:00 2
@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 来的要高。 |
96
mashiro233 2018-05-30 18:54:49 +08:00 1
@shijingshijing
arm64 是 普通参数放 r0-r7 浮点 /SIMD 是放 v0-v7 存不下的放 stack 上。 对于编译器来说,所有固定参数函数都可以当做可变参数函数来处理,函数的 arg 本质上是一个单向 list。所以 stdarg 里的那些函数你会发现完全可以用一个单向 list 来实现。 |
97
season4675 2018-05-30 20:47:00 +08:00 1
cpp 基本特性得熟悉,c++11 特性必须会,比如《 effective modern c++》《 more effective c++》怎么说一得一刷吧?就这些……顺便说下,简历给我看看??杭州有兴趣嘛
|
98
c3824363 2018-05-30 23:46:27 +08:00 2
@gnaggnoyil 能用 container_of offset_of 实现一堆代码复用我就比较满足了啊
@mashiro233 协程切换是用户态的,速度快很多, 但我还是没用这个东西,如大家所说反正都是事件驱动的。 但是有个特例是 linux 文件系统的 io,这个还是阻塞的,没有和 epoll 配合。 但是 freebsd 的 kqueue windows 的 iocp 都能配合文件系统 io 使用。 |
99
mashiro233 2018-05-30 23:53:39 +08:00 via Android
@c3824363
这个我可以补充一下。文件 io 这块,nodejs 的 Linux 异步文件 io 实现用的是线程池来做的。理由就是你说那样,不过 win 和 freebsd 我就不了解了,没开发经验。😢 |
100
YouXia 2018-05-31 00:10:30 +08:00
Mark
|